on d99edee help sign in
.gitignore−1
since splitsnapshots
unifiedsplit
@@ -1,4 +1,3 @@
1/target
1/target
2**/*.rs.bk
2**/*.rs.bk
3wasm-pack.log
3wasm-pack.log
4−/.vscode
.travis.yml+9−19
@@ -25,30 +25,20 @@ matrix:
25 - name: "build (beta)"
25 - name: "build (beta)"
26 rust: beta
26 rust: beta
27 script: INSTALL=1 ./build.sh
27 script: INSTALL=1 ./build.sh
28+ - name: "build (nightly)"
29+ rust: nightly
30+ script: INSTALL=1 ./build.sh
28 - name: "headless browser tests (firefox)"
31 - name: "headless browser tests (firefox)"
29 rust: stable
32 rust: stable
30 addons:
33 addons:
31 firefox: latest
34 firefox: latest
32− script: wasm-pack test --firefox --headless -- --features xxx-unstable-internal-use-only
35+ script: wasm-pack test --firefox --headless
36+ - name: "headless browser tests (chrome)"
37+ rust: stable
38+ addons:
39+ chrome: stable
40+ script: wasm-pack test --chrome --headless
33 - name: "native tests"
41 - name: "native tests"
34 rust: stable
42 rust: stable
35 install: echo "no install"
43 install: echo "no install"
36 script: cargo test --all
44 script: cargo test --all
37−
38− # TODO: A bug in `rustc` is making `js-sys` broken on nightly:
39− # https://github.com/rust-lang/rust/issues/69315
40− # - name: "build (nightly)"
41− # rust: nightly
42− # script: INSTALL=1 ./build.sh
43− # - name: "check native benches"
44− # rust: nightly
45− # install: echo "no install"
46− # script: cargo check --benches --features xxx-unstable-internal-use-only
47−
48− # TODO: `chromedriver` is complaining about ports or something. Temporarily
49− # disable in CI until we figure it out.
50− # - name: "headless browser tests (chrome)"
51− # rust: stable
52− # addons:
53− # chrome: stable
54− # script: wasm-pack test --chrome --headless -- --features xxx-unstable-internal-use-only
Cargo.lock+315−670
@@ -1,230 +1,111 @@
1# This file is automatically @generated by Cargo.
1# This file is automatically @generated by Cargo.
2# It is not intended for manual editing.
2# It is not intended for manual editing.
3[[package]]
3[[package]]
4−name = "anyhow"
4+name = "aho-corasick"
5−version = "1.0.25"
5+version = "0.6.9"
6source = "registry+https://github.com/rust-lang/crates.io-index"
6source = "registry+https://github.com/rust-lang/crates.io-index"
7+dependencies = [
8+ "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
9+]
7
10
8[[package]]
11[[package]]
9name = "atty"
12name = "atty"
10−version = "0.2.13"
13+version = "0.2.11"
11source = "registry+https://github.com/rust-lang/crates.io-index"
14source = "registry+https://github.com/rust-lang/crates.io-index"
12dependencies = [
15dependencies = [
16+ "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
13− "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
17+ "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
14− "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
18+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
15]
19]
16
20
17[[package]]
21[[package]]
18name = "autocfg"
22name = "autocfg"
19−version = "0.1.7"
23+version = "0.1.2"
20source = "registry+https://github.com/rust-lang/crates.io-index"
24source = "registry+https://github.com/rust-lang/crates.io-index"
21
25
22[[package]]
26[[package]]
23−name = "bitflags"
27+name = "backtrace"
24−version = "1.2.1"
28+version = "0.3.13"
25source = "registry+https://github.com/rust-lang/crates.io-index"
29source = "registry+https://github.com/rust-lang/crates.io-index"
30+dependencies = [
31+ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
32+ "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
33+ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
34+ "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
35+ "rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
36+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
37+]
26
38
27[[package]]
39[[package]]
28−name = "bstr"
40+name = "backtrace-sys"
29−version = "0.2.8"
41+version = "0.1.28"
30source = "registry+https://github.com/rust-lang/crates.io-index"
42source = "registry+https://github.com/rust-lang/crates.io-index"
31dependencies = [
43dependencies = [
32− "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
33− "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
44+ "cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)",
34− "regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
35− "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
45+ "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
36]
46]
37
47
38[[package]]
48[[package]]
39name = "bumpalo"
49name = "bumpalo"
40−version = "2.6.0"
50+version = "2.1.0"
41source = "registry+https://github.com/rust-lang/crates.io-index"
51source = "registry+https://github.com/rust-lang/crates.io-index"
42
52
43[[package]]
53[[package]]
44−name = "byteorder"
54+name = "cc"
45−version = "1.3.2"
55+version = "1.0.29"
46source = "registry+https://github.com/rust-lang/crates.io-index"
56source = "registry+https://github.com/rust-lang/crates.io-index"
47
57
48−[[package]]
49−name = "cast"
50−version = "0.2.3"
51−source = "registry+https://github.com/rust-lang/crates.io-index"
52−dependencies = [
53− "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
54−]
55−
56[[package]]
58[[package]]
57name = "cfg-if"
59name = "cfg-if"
58−version = "0.1.10"
60+version = "0.1.7"
59−source = "registry+https://github.com/rust-lang/crates.io-index"
60−
61−[[package]]
62−name = "clap"
63−version = "2.33.0"
64source = "registry+https://github.com/rust-lang/crates.io-index"
61source = "registry+https://github.com/rust-lang/crates.io-index"
65−dependencies = [
66− "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
67− "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
68− "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
69−]
70
62
71[[package]]
63[[package]]
72name = "console_error_panic_hook"
64name = "console_error_panic_hook"
73version = "0.1.6"
65version = "0.1.6"
74source = "registry+https://github.com/rust-lang/crates.io-index"
66source = "registry+https://github.com/rust-lang/crates.io-index"
75dependencies = [
67dependencies = [
76− "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
68+ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
77− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
69+ "wasm-bindgen 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
78]
70]
79
71
80[[package]]
72[[package]]
81name = "console_log"
73name = "console_log"
82−version = "0.2.0"
74+version = "0.1.2"
83−source = "registry+https://github.com/rust-lang/crates.io-index"
84−dependencies = [
85− "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
86− "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
87−]
88−
89−[[package]]
90−name = "criterion"
91−version = "0.3.3"
92−source = "registry+https://github.com/rust-lang/crates.io-index"
93−dependencies = [
94− "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
95− "cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
96− "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
97− "criterion-plot 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
98− "csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
99− "itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
100− "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
101− "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
102− "oorandom 11.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
103− "plotters 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
104− "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
105− "regex 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
106− "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
107− "serde_cbor 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
108− "serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
109− "serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)",
110− "tinytemplate 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
111− "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
112−]
113−
114−[[package]]
115−name = "criterion-plot"
116−version = "0.4.3"
117−source = "registry+https://github.com/rust-lang/crates.io-index"
118−dependencies = [
119− "cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
120− "itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
121−]
122−
123−[[package]]
124−name = "crossbeam-deque"
125−version = "0.7.2"
126−source = "registry+https://github.com/rust-lang/crates.io-index"
127−dependencies = [
128− "crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
129− "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
130−]
131−
132−[[package]]
133−name = "crossbeam-epoch"
134−version = "0.8.0"
135−source = "registry+https://github.com/rust-lang/crates.io-index"
136−dependencies = [
137− "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
138− "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
139− "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
140− "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
141− "memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
142− "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
143−]
144−
145−[[package]]
146−name = "crossbeam-queue"
147−version = "0.2.0"
148−source = "registry+https://github.com/rust-lang/crates.io-index"
149−dependencies = [
150− "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
151−]
152−
153−[[package]]
154−name = "crossbeam-utils"
155−version = "0.7.0"
156−source = "registry+https://github.com/rust-lang/crates.io-index"
157−dependencies = [
158− "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
159− "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
160− "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
161−]
162−
163−[[package]]
164−name = "csv"
165−version = "1.1.1"
166−source = "registry+https://github.com/rust-lang/crates.io-index"
167−dependencies = [
168− "bstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
169− "csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
170− "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
171− "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
172− "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
173−]
174−
175−[[package]]
176−name = "csv-core"
177−version = "0.1.6"
178source = "registry+https://github.com/rust-lang/crates.io-index"
75source = "registry+https://github.com/rust-lang/crates.io-index"
179dependencies = [
76dependencies = [
77+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
180− "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
78+ "web-sys 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
181]
79]
182
80
183[[package]]
81[[package]]
184name = "dodrio"
82name = "dodrio"
185−version = "0.2.0"
186−dependencies = [
187− "bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
188− "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
189− "console_log 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
190− "criterion 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
191− "dodrio-js-api 0.2.0",
192− "futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
193− "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
194− "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
195− "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
196− "longest-increasing-subsequence 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
197− "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
198− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
199− "wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
200− "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
201− "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
202−]
203−
204−[[package]]
205−name = "dodrio-counter"
206version = "0.1.0"
83version = "0.1.0"
207dependencies = [
84dependencies = [
85+ "bumpalo 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
208− "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
86+ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
209− "console_log 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
87+ "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
210− "dodrio 0.2.0",
88+ "dodrio-js-api 0.1.0",
89+ "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
90+ "js-sys 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
211− "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
91+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
92+ "wasm-bindgen 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
212− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
93+ "wasm-bindgen-futures 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
213− "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
94+ "wasm-bindgen-test 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
214− "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
95+ "web-sys 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
215]
96]
216
97
217[[package]]
98[[package]]
218−name = "dodrio-game-of-life"
99+name = "dodrio-counter"
219version = "0.1.0"
100version = "0.1.0"
220dependencies = [
101dependencies = [
221 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
102 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
103+ "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
222− "dodrio 0.2.0",
104+ "dodrio 0.1.0",
223− "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
105+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
224− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
225− "wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
106+ "wasm-bindgen 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
226− "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
107+ "wasm-bindgen-test 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
227− "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
108+ "web-sys 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
228]
109]
229
110
230[[package]]
111[[package]]
@@ -232,10 +113,10 @@ name = "dodrio-hello-world"
232version = "0.1.0"
113version = "0.1.0"
233dependencies = [
114dependencies = [
234 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
115 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
235− "dodrio 0.2.0",
116+ "dodrio 0.1.0",
236− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
117+ "wasm-bindgen 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
237− "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
118+ "wasm-bindgen-test 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
238− "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
119+ "web-sys 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
239]
120]
240
121
241[[package]]
122[[package]]
@@ -243,21 +124,22 @@ name = "dodrio-input-form"
243version = "0.1.0"
124version = "0.1.0"
244dependencies = [
125dependencies = [
245 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
126 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
246− "dodrio 0.2.0",
127+ "dodrio 0.1.0",
247− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
128+ "wasm-bindgen 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
248− "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
129+ "wasm-bindgen-test 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
249− "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
130+ "web-sys 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
250]
131]
251
132
252[[package]]
133[[package]]
253name = "dodrio-js-api"
134name = "dodrio-js-api"
254−version = "0.2.0"
135+version = "0.1.0"
255dependencies = [
136dependencies = [
256− "dodrio 0.2.0",
137+ "dodrio 0.1.0",
138+ "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
257− "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
139+ "js-sys 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
258− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
140+ "wasm-bindgen 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
259− "wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
141+ "wasm-bindgen-futures 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
260− "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
142+ "wasm-bindgen-test 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
261]
143]
262
144
263[[package]]
145[[package]]
@@ -265,162 +147,84 @@ name = "dodrio-js-component"
265version = "0.1.0"
147version = "0.1.0"
266dependencies = [
148dependencies = [
267 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
149 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
268− "dodrio 0.2.0",
150+ "dodrio 0.1.0",
269− "dodrio-js-api 0.2.0",
151+ "dodrio-js-api 0.1.0",
270− "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
152+ "js-sys 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
271− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
153+ "wasm-bindgen 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
272− "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
154+ "web-sys 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
273]
155]
274
156
275[[package]]
157[[package]]
276name = "dodrio-moire"
158name = "dodrio-moire"
277version = "0.1.0"
159version = "0.1.0"
278dependencies = [
160dependencies = [
279− "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
280− "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
281− "console_log 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
282− "dodrio 0.2.0",
283− "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
284− "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
285− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
286− "wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
287− "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
288− "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
161+ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
289−]
290−
291−[[package]]
292−name = "dodrio-sierpinski"
293−version = "0.1.0"
294−dependencies = [
295 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
162 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
163+ "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
296− "dodrio 0.2.0",
164+ "dodrio 0.1.0",
165+ "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
297− "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
166+ "js-sys 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
298− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
167+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
168+ "wasm-bindgen 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
299− "wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
169+ "wasm-bindgen-futures 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
300− "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
170+ "wasm-bindgen-test 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
301− "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
171+ "web-sys 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
302]
172]
303
173
304[[package]]
174[[package]]
305name = "dodrio-todomvc"
175name = "dodrio-todomvc"
306version = "0.1.0"
176version = "0.1.0"
307dependencies = [
177dependencies = [
308− "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
178+ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
309 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
179 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
310− "console_log 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
180+ "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
311− "dodrio 0.2.0",
181+ "dodrio 0.1.0",
182+ "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
312− "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
183+ "js-sys 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
313− "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
184+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
314− "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
185+ "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
315− "serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)",
186+ "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
316− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
187+ "wasm-bindgen 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
317− "wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
188+ "wasm-bindgen-futures 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
318− "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
189+ "wasm-bindgen-test 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
319− "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
320−]
321−
322−[[package]]
323−name = "either"
324−version = "1.5.3"
325−source = "registry+https://github.com/rust-lang/crates.io-index"
326−
327−[[package]]
328−name = "futures"
329−version = "0.3.5"
330−source = "registry+https://github.com/rust-lang/crates.io-index"
331−dependencies = [
332− "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
333− "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
334− "futures-executor 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
335− "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
336− "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
337− "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
338− "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
339−]
340−
341−[[package]]
342−name = "futures-channel"
343−version = "0.3.5"
344−source = "registry+https://github.com/rust-lang/crates.io-index"
345−dependencies = [
346− "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
347− "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
190+ "web-sys 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
348]
191]
349
192
350[[package]]
193[[package]]
351−name = "futures-core"
194+name = "env_logger"
352−version = "0.3.5"
195+version = "0.6.0"
353−source = "registry+https://github.com/rust-lang/crates.io-index"
354−
355−[[package]]
356−name = "futures-executor"
357−version = "0.3.5"
358−source = "registry+https://github.com/rust-lang/crates.io-index"
359−dependencies = [
360− "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
361− "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
362− "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
363−]
364−
365−[[package]]
366−name = "futures-io"
367−version = "0.3.5"
368−source = "registry+https://github.com/rust-lang/crates.io-index"
369−
370−[[package]]
371−name = "futures-macro"
372−version = "0.3.5"
373−source = "registry+https://github.com/rust-lang/crates.io-index"
374−dependencies = [
375− "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
376− "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
377− "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
378− "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
379−]
380−
381−[[package]]
382−name = "futures-sink"
383−version = "0.3.5"
384−source = "registry+https://github.com/rust-lang/crates.io-index"
385−
386−[[package]]
387−name = "futures-task"
388−version = "0.3.5"
389source = "registry+https://github.com/rust-lang/crates.io-index"
196source = "registry+https://github.com/rust-lang/crates.io-index"
390dependencies = [
197dependencies = [
198+ "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
199+ "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
200+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
201+ "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
391− "once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
202+ "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
392]
203]
393
204
394[[package]]
205[[package]]
395−name = "futures-util"
206+name = "failure"
396−version = "0.3.5"
207+version = "0.1.5"
397source = "registry+https://github.com/rust-lang/crates.io-index"
208source = "registry+https://github.com/rust-lang/crates.io-index"
398dependencies = [
209dependencies = [
399− "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
400− "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
401− "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
402− "futures-macro 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
403− "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
210+ "backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
404− "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
405− "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
406− "pin-project 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)",
407− "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
408− "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
409− "proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
410− "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
211+ "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
411]
212]
412
213
413[[package]]
214[[package]]
414−name = "fxhash"
215+name = "failure_derive"
415−version = "0.2.1"
216+version = "0.1.5"
416source = "registry+https://github.com/rust-lang/crates.io-index"
217source = "registry+https://github.com/rust-lang/crates.io-index"
417dependencies = [
218dependencies = [
219+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
220+ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
221+ "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
418− "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
222+ "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
419]
223]
420
224
421[[package]]
225[[package]]
422−name = "half"
226+name = "futures"
423−version = "1.6.0"
227+version = "0.1.25"
424source = "registry+https://github.com/rust-lang/crates.io-index"
228source = "registry+https://github.com/rust-lang/crates.io-index"
425
229
426[[package]]
230[[package]]
@@ -428,489 +232,355 @@ name = "heck"
428version = "0.3.1"
232version = "0.3.1"
429source = "registry+https://github.com/rust-lang/crates.io-index"
233source = "registry+https://github.com/rust-lang/crates.io-index"
430dependencies = [
234dependencies = [
431− "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
235+ "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
432]
236]
433
237
434[[package]]
238[[package]]
435−name = "hermit-abi"
239+name = "humantime"
436−version = "0.1.3"
240+version = "1.2.0"
437source = "registry+https://github.com/rust-lang/crates.io-index"
241source = "registry+https://github.com/rust-lang/crates.io-index"
438dependencies = [
242dependencies = [
439− "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
440−]
441−
442−[[package]]
443−name = "itertools"
444−version = "0.9.0"
445−source = "registry+https://github.com/rust-lang/crates.io-index"
446−dependencies = [
447− "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
243+ "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
448]
244]
449
245
450[[package]]
246[[package]]
451name = "itoa"
247name = "itoa"
452−version = "0.4.4"
248+version = "0.4.3"
453source = "registry+https://github.com/rust-lang/crates.io-index"
249source = "registry+https://github.com/rust-lang/crates.io-index"
454
250
455[[package]]
251[[package]]
456name = "js-sys"
252name = "js-sys"
457−version = "0.3.32"
253+version = "0.3.14"
458source = "registry+https://github.com/rust-lang/crates.io-index"
254source = "registry+https://github.com/rust-lang/crates.io-index"
459dependencies = [
255dependencies = [
460− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
256+ "wasm-bindgen 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
461]
257]
462
258
463[[package]]
259[[package]]
464name = "lazy_static"
260name = "lazy_static"
465−version = "1.4.0"
261+version = "1.2.0"
466source = "registry+https://github.com/rust-lang/crates.io-index"
262source = "registry+https://github.com/rust-lang/crates.io-index"
467
263
468[[package]]
264[[package]]
469name = "libc"
265name = "libc"
470−version = "0.2.66"
266+version = "0.2.48"
471source = "registry+https://github.com/rust-lang/crates.io-index"
267source = "registry+https://github.com/rust-lang/crates.io-index"
472
268
473[[package]]
269[[package]]
474name = "log"
270name = "log"
475−version = "0.4.11"
271+version = "0.4.6"
476source = "registry+https://github.com/rust-lang/crates.io-index"
272source = "registry+https://github.com/rust-lang/crates.io-index"
477dependencies = [
273dependencies = [
478− "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
274+ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
479]
275]
480
276
481−[[package]]
482−name = "longest-increasing-subsequence"
483−version = "0.1.0"
484−source = "registry+https://github.com/rust-lang/crates.io-index"
485−
486[[package]]
277[[package]]
487name = "memchr"
278name = "memchr"
488−version = "2.2.1"
279+version = "2.2.0"
489source = "registry+https://github.com/rust-lang/crates.io-index"
280source = "registry+https://github.com/rust-lang/crates.io-index"
490−dependencies = [
491− "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
492−]
493−
494−[[package]]
495−name = "memoffset"
496−version = "0.5.3"
497−source = "registry+https://github.com/rust-lang/crates.io-index"
498−dependencies = [
499− "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
500−]
501
281
502[[package]]
282[[package]]
503name = "nom"
283name = "nom"
504−version = "4.2.3"
284+version = "4.2.0"
505source = "registry+https://github.com/rust-lang/crates.io-index"
285source = "registry+https://github.com/rust-lang/crates.io-index"
506dependencies = [
286dependencies = [
507− "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
287+ "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
508 "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
288 "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
509]
289]
510
290
511[[package]]
291[[package]]
512−name = "num-traits"
292+name = "proc-macro2"
513−version = "0.2.10"
514−source = "registry+https://github.com/rust-lang/crates.io-index"
515−dependencies = [
516− "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
517−]
518−
519−[[package]]
520−name = "num_cpus"
521−version = "1.11.1"
522−source = "registry+https://github.com/rust-lang/crates.io-index"
523−dependencies = [
524− "hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
525− "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
526−]
527−
528−[[package]]
529−name = "once_cell"
530−version = "1.3.1"
531−source = "registry+https://github.com/rust-lang/crates.io-index"
532−
533−[[package]]
534−name = "oorandom"
535−version = "11.1.0"
536−source = "registry+https://github.com/rust-lang/crates.io-index"
537−
538−[[package]]
539−name = "pin-project"
540−version = "0.4.16"
541−source = "registry+https://github.com/rust-lang/crates.io-index"
542−dependencies = [
543− "pin-project-internal 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)",
544−]
545−
546−[[package]]
547−name = "pin-project-internal"
548−version = "0.4.16"
549−source = "registry+https://github.com/rust-lang/crates.io-index"
550−dependencies = [
551− "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
552− "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
553− "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
554−]
555−
556−[[package]]
557−name = "pin-utils"
558−version = "0.1.0"
293+version = "0.4.27"
559−source = "registry+https://github.com/rust-lang/crates.io-index"
560−
561−[[package]]
562−name = "plotters"
563−version = "0.2.12"
564−source = "registry+https://github.com/rust-lang/crates.io-index"
565−dependencies = [
566− "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
567− "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
568− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
569− "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
570−]
571−
572−[[package]]
573−name = "proc-macro-hack"
574−version = "0.5.11"
575source = "registry+https://github.com/rust-lang/crates.io-index"
294source = "registry+https://github.com/rust-lang/crates.io-index"
576dependencies = [
295dependencies = [
577− "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
578− "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
579− "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
296+ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
580]
297]
581
298
582[[package]]
299[[package]]
583−name = "proc-macro-nested"
300+name = "quick-error"
584−version = "0.1.3"
585−source = "registry+https://github.com/rust-lang/crates.io-index"
586−
587−[[package]]
588−name = "proc-macro2"
589−version = "1.0.18"
301+version = "1.2.2"
590source = "registry+https://github.com/rust-lang/crates.io-index"
302source = "registry+https://github.com/rust-lang/crates.io-index"
591−dependencies = [
592− "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
593−]
594
303
595[[package]]
304[[package]]
596name = "quote"
305name = "quote"
597−version = "1.0.2"
306+version = "0.6.11"
598source = "registry+https://github.com/rust-lang/crates.io-index"
307source = "registry+https://github.com/rust-lang/crates.io-index"
599dependencies = [
308dependencies = [
600− "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
309+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
601]
310]
602
311
603[[package]]
312[[package]]
604−name = "rayon"
313+name = "redox_syscall"
605−version = "1.3.0"
314+version = "0.1.51"
606source = "registry+https://github.com/rust-lang/crates.io-index"
315source = "registry+https://github.com/rust-lang/crates.io-index"
607−dependencies = [
608− "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
609− "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
610− "rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
611−]
612
316
613[[package]]
317[[package]]
614−name = "rayon-core"
318+name = "redox_termios"
615−version = "1.7.0"
319+version = "0.1.1"
616source = "registry+https://github.com/rust-lang/crates.io-index"
320source = "registry+https://github.com/rust-lang/crates.io-index"
617dependencies = [
321dependencies = [
618− "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
619− "crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
620− "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
621− "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
622− "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
322+ "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
623]
323]
624
324
625[[package]]
325[[package]]
626name = "regex"
326name = "regex"
627−version = "1.3.3"
327+version = "1.1.0"
628−source = "registry+https://github.com/rust-lang/crates.io-index"
629−dependencies = [
630− "regex-syntax 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
631−]
632−
633−[[package]]
634−name = "regex-automata"
635−version = "0.1.8"
636source = "registry+https://github.com/rust-lang/crates.io-index"
328source = "registry+https://github.com/rust-lang/crates.io-index"
637dependencies = [
329dependencies = [
330+ "aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)",
331+ "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
332+ "regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
333+ "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
638− "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
334+ "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
639]
335]
640
336
641[[package]]
337[[package]]
642name = "regex-syntax"
338name = "regex-syntax"
643−version = "0.6.13"
339+version = "0.6.5"
644−source = "registry+https://github.com/rust-lang/crates.io-index"
645−
646−[[package]]
647−name = "rustc_version"
648−version = "0.2.3"
649source = "registry+https://github.com/rust-lang/crates.io-index"
340source = "registry+https://github.com/rust-lang/crates.io-index"
650dependencies = [
341dependencies = [
651− "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
342+ "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
652]
343]
653
344
654[[package]]
345[[package]]
655−name = "ryu"
346+name = "rustc-demangle"
656−version = "1.0.2"
347+version = "0.1.13"
657source = "registry+https://github.com/rust-lang/crates.io-index"
348source = "registry+https://github.com/rust-lang/crates.io-index"
658
349
659[[package]]
350[[package]]
660−name = "same-file"
351+name = "ryu"
661−version = "1.0.5"
352+version = "0.2.7"
662source = "registry+https://github.com/rust-lang/crates.io-index"
353source = "registry+https://github.com/rust-lang/crates.io-index"
663−dependencies = [
664− "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
665−]
666
354
667[[package]]
355[[package]]
668name = "scoped-tls"
356name = "scoped-tls"
669−version = "1.0.0"
357+version = "0.1.2"
670−source = "registry+https://github.com/rust-lang/crates.io-index"
671−
672−[[package]]
673−name = "scopeguard"
674−version = "1.0.0"
675source = "registry+https://github.com/rust-lang/crates.io-index"
358source = "registry+https://github.com/rust-lang/crates.io-index"
676
359
677[[package]]
360[[package]]
678−name = "semver"
361+name = "serde"
679−version = "0.9.0"
362+version = "1.0.89"
680source = "registry+https://github.com/rust-lang/crates.io-index"
363source = "registry+https://github.com/rust-lang/crates.io-index"
681dependencies = [
364dependencies = [
682− "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
365+ "serde_derive 1.0.87 (registry+https://github.com/rust-lang/crates.io-index)",
683]
366]
684
367
685[[package]]
368[[package]]
686−name = "semver-parser"
369+name = "serde_derive"
687−version = "0.7.0"
688−source = "registry+https://github.com/rust-lang/crates.io-index"
689−
690−[[package]]
691−name = "serde"
692−version = "1.0.114"
370+version = "1.0.87"
693source = "registry+https://github.com/rust-lang/crates.io-index"
371source = "registry+https://github.com/rust-lang/crates.io-index"
694dependencies = [
372dependencies = [
373+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
374+ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
695− "serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
375+ "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
696]
376]
697
377
698[[package]]
378[[package]]
699−name = "serde_cbor"
379+name = "serde_json"
700−version = "0.11.1"
380+version = "1.0.39"
701source = "registry+https://github.com/rust-lang/crates.io-index"
381source = "registry+https://github.com/rust-lang/crates.io-index"
702dependencies = [
382dependencies = [
383+ "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
703− "half 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
384+ "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
704− "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
385+ "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
705]
386]
706
387
707[[package]]
388[[package]]
708−name = "serde_derive"
389+name = "sourcefile"
709−version = "1.0.114"
390+version = "0.1.4"
710source = "registry+https://github.com/rust-lang/crates.io-index"
391source = "registry+https://github.com/rust-lang/crates.io-index"
711−dependencies = [
712− "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
713− "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
714− "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
715−]
716
392
717[[package]]
393[[package]]
718−name = "serde_json"
394+name = "syn"
719−version = "1.0.57"
395+version = "0.15.26"
720source = "registry+https://github.com/rust-lang/crates.io-index"
396source = "registry+https://github.com/rust-lang/crates.io-index"
721dependencies = [
397dependencies = [
722− "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
398+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
723− "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
399+ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
724− "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
400+ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
725]
401]
726
402
727[[package]]
403[[package]]
728−name = "slab"
404+name = "synstructure"
729−version = "0.4.2"
405+version = "0.10.1"
730−source = "registry+https://github.com/rust-lang/crates.io-index"
731−
732−[[package]]
733−name = "sourcefile"
734−version = "0.1.4"
735source = "registry+https://github.com/rust-lang/crates.io-index"
406source = "registry+https://github.com/rust-lang/crates.io-index"
407+dependencies = [
408+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
409+ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
410+ "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
411+ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
412+]
736
413
737[[package]]
414[[package]]
738−name = "syn"
415+name = "termcolor"
739−version = "1.0.33"
416+version = "1.0.4"
740source = "registry+https://github.com/rust-lang/crates.io-index"
417source = "registry+https://github.com/rust-lang/crates.io-index"
741dependencies = [
418dependencies = [
742− "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
743− "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
744− "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
419+ "wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
745]
420]
746
421
747[[package]]
422[[package]]
748−name = "textwrap"
423+name = "termion"
749−version = "0.11.0"
424+version = "1.5.1"
750source = "registry+https://github.com/rust-lang/crates.io-index"
425source = "registry+https://github.com/rust-lang/crates.io-index"
751dependencies = [
426dependencies = [
427+ "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
428+ "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
752− "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
429+ "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
753]
430]
754
431
755[[package]]
432[[package]]
756−name = "tinytemplate"
433+name = "thread_local"
757−version = "1.1.0"
434+version = "0.3.6"
758source = "registry+https://github.com/rust-lang/crates.io-index"
435source = "registry+https://github.com/rust-lang/crates.io-index"
759dependencies = [
436dependencies = [
760− "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
761− "serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)",
437+ "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
762]
438]
763
439
764[[package]]
440[[package]]
765−name = "unicode-segmentation"
441+name = "ucd-util"
766−version = "1.6.0"
442+version = "0.1.3"
767source = "registry+https://github.com/rust-lang/crates.io-index"
443source = "registry+https://github.com/rust-lang/crates.io-index"
768
444
769[[package]]
445[[package]]
770−name = "unicode-width"
446+name = "unicode-segmentation"
771−version = "0.1.6"
447+version = "1.2.1"
772source = "registry+https://github.com/rust-lang/crates.io-index"
448source = "registry+https://github.com/rust-lang/crates.io-index"
773
449
774[[package]]
450[[package]]
775name = "unicode-xid"
451name = "unicode-xid"
776−version = "0.2.0"
452+version = "0.1.0"
777source = "registry+https://github.com/rust-lang/crates.io-index"
453source = "registry+https://github.com/rust-lang/crates.io-index"
778
454
779[[package]]
455[[package]]
780−name = "version_check"
456+name = "utf8-ranges"
781−version = "0.1.5"
457+version = "1.0.2"
782source = "registry+https://github.com/rust-lang/crates.io-index"
458source = "registry+https://github.com/rust-lang/crates.io-index"
783
459
784[[package]]
460[[package]]
785−name = "walkdir"
461+name = "version_check"
786−version = "2.3.1"
462+version = "0.1.5"
787source = "registry+https://github.com/rust-lang/crates.io-index"
463source = "registry+https://github.com/rust-lang/crates.io-index"
788−dependencies = [
789− "same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
790− "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
791− "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
792−]
793
464
794[[package]]
465[[package]]
795name = "wasm-bindgen"
466name = "wasm-bindgen"
796−version = "0.2.55"
467+version = "0.2.37"
797source = "registry+https://github.com/rust-lang/crates.io-index"
468source = "registry+https://github.com/rust-lang/crates.io-index"
798dependencies = [
469dependencies = [
799− "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
800− "wasm-bindgen-macro 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
470+ "wasm-bindgen-macro 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
801]
471]
802
472
803[[package]]
473[[package]]
804name = "wasm-bindgen-backend"
474name = "wasm-bindgen-backend"
805−version = "0.2.55"
475+version = "0.2.37"
806source = "registry+https://github.com/rust-lang/crates.io-index"
476source = "registry+https://github.com/rust-lang/crates.io-index"
807dependencies = [
477dependencies = [
808− "bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
809− "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
478+ "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
810− "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
479+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
811− "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
480+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
812− "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
481+ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
813− "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
482+ "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
814− "wasm-bindgen-shared 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
483+ "wasm-bindgen-shared 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
815]
484]
816
485
817[[package]]
486[[package]]
818name = "wasm-bindgen-futures"
487name = "wasm-bindgen-futures"
819−version = "0.4.5"
488+version = "0.3.14"
820source = "registry+https://github.com/rust-lang/crates.io-index"
489source = "registry+https://github.com/rust-lang/crates.io-index"
821dependencies = [
490dependencies = [
822− "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
491+ "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
823− "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
492+ "js-sys 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
824− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
825− "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
493+ "wasm-bindgen 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
826]
494]
827
495
828[[package]]
496[[package]]
829name = "wasm-bindgen-macro"
497name = "wasm-bindgen-macro"
830−version = "0.2.55"
498+version = "0.2.37"
831source = "registry+https://github.com/rust-lang/crates.io-index"
499source = "registry+https://github.com/rust-lang/crates.io-index"
832dependencies = [
500dependencies = [
833− "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
501+ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
834− "wasm-bindgen-macro-support 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
502+ "wasm-bindgen-macro-support 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
835]
503]
836
504
837[[package]]
505[[package]]
838name = "wasm-bindgen-macro-support"
506name = "wasm-bindgen-macro-support"
839−version = "0.2.55"
507+version = "0.2.37"
840source = "registry+https://github.com/rust-lang/crates.io-index"
508source = "registry+https://github.com/rust-lang/crates.io-index"
841dependencies = [
509dependencies = [
842− "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
510+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
843− "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
511+ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
844− "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
512+ "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
845− "wasm-bindgen-backend 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
513+ "wasm-bindgen-backend 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
846− "wasm-bindgen-shared 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
514+ "wasm-bindgen-shared 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
847]
515]
848
516
849[[package]]
517[[package]]
850name = "wasm-bindgen-shared"
518name = "wasm-bindgen-shared"
851−version = "0.2.55"
519+version = "0.2.37"
852source = "registry+https://github.com/rust-lang/crates.io-index"
520source = "registry+https://github.com/rust-lang/crates.io-index"
853
521
854[[package]]
522[[package]]
855name = "wasm-bindgen-test"
523name = "wasm-bindgen-test"
856−version = "0.3.5"
524+version = "0.2.37"
857source = "registry+https://github.com/rust-lang/crates.io-index"
525source = "registry+https://github.com/rust-lang/crates.io-index"
858dependencies = [
526dependencies = [
859 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
527 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
528+ "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
860− "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
529+ "js-sys 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
861− "scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
530+ "scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
862− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
531+ "wasm-bindgen 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
863− "wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
532+ "wasm-bindgen-futures 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
864− "wasm-bindgen-test-macro 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
533+ "wasm-bindgen-test-macro 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
865]
534]
866
535
867[[package]]
536[[package]]
868name = "wasm-bindgen-test-macro"
537name = "wasm-bindgen-test-macro"
869−version = "0.3.5"
538+version = "0.2.37"
870source = "registry+https://github.com/rust-lang/crates.io-index"
539source = "registry+https://github.com/rust-lang/crates.io-index"
871dependencies = [
540dependencies = [
872− "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
541+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
873− "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
542+ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
874]
543]
875
544
876[[package]]
545[[package]]
877name = "wasm-bindgen-webidl"
546name = "wasm-bindgen-webidl"
878−version = "0.2.55"
547+version = "0.2.31"
879source = "registry+https://github.com/rust-lang/crates.io-index"
548source = "registry+https://github.com/rust-lang/crates.io-index"
880dependencies = [
549dependencies = [
881− "anyhow 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
550+ "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
882 "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
551 "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
883− "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
552+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
884− "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
553+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
885− "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
554+ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
886− "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
555+ "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
887− "wasm-bindgen-backend 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
556+ "wasm-bindgen-backend 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
888− "weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
557+ "weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
889]
558]
890
559
891[[package]]
560[[package]]
892name = "web-sys"
561name = "web-sys"
893−version = "0.3.32"
562+version = "0.3.14"
894source = "registry+https://github.com/rust-lang/crates.io-index"
563source = "registry+https://github.com/rust-lang/crates.io-index"
895dependencies = [
564dependencies = [
565+ "env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
896− "anyhow 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
566+ "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
897− "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
567+ "js-sys 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
898 "sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
568 "sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
899− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
569+ "wasm-bindgen 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
900− "wasm-bindgen-webidl 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
570+ "wasm-bindgen-webidl 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)",
901]
571]
902
572
903[[package]]
573[[package]]
904name = "weedle"
574name = "weedle"
905−version = "0.10.0"
575+version = "0.8.0"
906source = "registry+https://github.com/rust-lang/crates.io-index"
576source = "registry+https://github.com/rust-lang/crates.io-index"
907dependencies = [
577dependencies = [
908− "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
578+ "nom 4.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
909]
579]
910
580
911[[package]]
581[[package]]
912name = "winapi"
582name = "winapi"
913−version = "0.3.8"
583+version = "0.3.6"
914source = "registry+https://github.com/rust-lang/crates.io-index"
584source = "registry+https://github.com/rust-lang/crates.io-index"
915dependencies = [
585dependencies = [
916 "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
586 "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -927,7 +597,7 @@ name = "winapi-util"
927version = "0.1.2"
597version = "0.1.2"
928source = "registry+https://github.com/rust-lang/crates.io-index"
598source = "registry+https://github.com/rust-lang/crates.io-index"
929dependencies = [
599dependencies = [
930− "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
600+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
931]
601]
932
602
933[[package]]
603[[package]]
@@ -935,101 +605,76 @@ name = "winapi-x86_64-pc-windows-gnu"
935version = "0.4.0"
605version = "0.4.0"
936source = "registry+https://github.com/rust-lang/crates.io-index"
606source = "registry+https://github.com/rust-lang/crates.io-index"
937
607
608+[[package]]
609+name = "wincolor"
610+version = "1.0.1"
611+source = "registry+https://github.com/rust-lang/crates.io-index"
612+dependencies = [
613+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
614+ "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
615+]
616+
938[metadata]
617[metadata]
939−"checksum anyhow 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9267dff192e68f3399525901e709a48c1d3982c9c072fa32f2127a0cb0babf14"
618+"checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e"
940−"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
619+"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
620+"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799"
621+"checksum backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b5b493b66e03090ebc4343eb02f94ff944e0cbc9ac6571491d170ba026741eb5"
941−"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
622+"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6"
942−"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
943−"checksum bstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8d6c2c5b58ab920a4f5aeaaca34b4488074e8cc7596af94e6f8c6ff247c60245"
944−"checksum bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad807f2fc2bf185eeb98ff3a901bd46dc5ad58163d0fa4577ba0d25674d71708"
623+"checksum bumpalo 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "21ef2109240a377370f55ea3ef0b486b46d7b5c0f7455ab0ec676d73f875d58a"
945−"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
624+"checksum cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)" = "4390a3b5f4f6bce9c1d0c00128379df433e53777fdd30e92f16a529332baec4e"
946−"checksum cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0"
947−"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
625+"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4"
948−"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
949"checksum console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211"
626"checksum console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211"
950−"checksum console_log 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "501a375961cef1a0d44767200e66e4a559283097e91d0730b1d75dfb2f8a1494"
627+"checksum console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e7871d2947441b0fdd8e2bd1ce2a2f75304f896582c0d572162d48290683c48"
951−"checksum criterion 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "70daa7ceec6cf143990669a04c7df13391d55fb27bd4079d252fca774ba244d8"
628+"checksum env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afb070faf94c85d17d50ca44f6ad076bce18ae92f0037d350947240a36e9d42e"
952−"checksum criterion-plot 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d"
953−"checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca"
954−"checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac"
955−"checksum crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dfd6515864a82d2f877b42813d4553292c6659498c9a2aa31bab5a15243c2700"
956−"checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4"
957−"checksum csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37519ccdfd73a75821cac9319d4fce15a81b9fcf75f951df5b9988aa3a0af87d"
958−"checksum csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9b5cadb6b25c77aeff80ba701712494213f4a8418fcda2ee11b6560c3ad0bf4c"
959−"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
960−"checksum futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613"
961−"checksum futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5"
962−"checksum futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399"
963−"checksum futures-executor 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314"
964−"checksum futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789"
629+"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2"
965−"checksum futures-macro 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39"
630+"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1"
966−"checksum futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc"
631+"checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b"
967−"checksum futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626"
968−"checksum futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6"
969−"checksum fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
970−"checksum half 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d36fab90f82edc3c747f9d438e06cf0a491055896f2a279638bb5beed6c40177"
971"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
632"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
972−"checksum hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120"
633+"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114"
973−"checksum itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
974−"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
634+"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b"
975−"checksum js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)" = "1c840fdb2167497b0bd0db43d6dfe61e91637fa72f9d061f8bd17ddc44ba6414"
635+"checksum js-sys 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbd938475b240c421c270c2c0dd0f9acc3642e02d7419afd37587a34cd04e40"
976−"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
636+"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"
977−"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
637+"checksum libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)" = "e962c7641008ac010fa60a7dfdc1712449f29c44ef2d4702394aea943ee75047"
978−"checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
638+"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
979−"checksum longest-increasing-subsequence 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3bd0dd2cd90571056fdb71f6275fada10131182f84899f4b2a916e565d81d86"
639+"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39"
980−"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
640+"checksum nom 4.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b30adc557058ce00c9d0d7cb3c6e0b5bc6f36e2e2eabe74b0ba726d194abd588"
981−"checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9"
641+"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915"
982−"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
642+"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
983−"checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4"
984−"checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72"
985−"checksum once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b"
986−"checksum oorandom 11.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ebcec7c9c2a95cacc7cd0ecb89d8a8454eca13906f6deb55258ffff0adeb9405"
987−"checksum pin-project 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)" = "81d480cb4e89522ccda96d0eed9af94180b7a5f93fb28f66e1fd7d68431663d1"
988−"checksum pin-project-internal 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)" = "a82996f11efccb19b685b14b5df818de31c1edcee3daa256ab5775dd98e72feb"
989−"checksum pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
990−"checksum plotters 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "4e3bb8da247d27ae212529352020f3e5ee16e83c0c258061d27b08ab92675eeb"
991−"checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5"
992−"checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e"
993−"checksum proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
994−"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
643+"checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1"
995−"checksum rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098"
644+"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85"
996−"checksum rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9"
645+"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
997−"checksum regex 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b5508c1941e4e7cb19965abef075d35a9a8b5cdf0846f30b4050e9b55dc55e87"
998−"checksum regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "92b73c2a1770c255c240eaa4ee600df1704a38dc3feaa6e949e7fcd4f8dc09f9"
646+"checksum regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37e7cbbd370869ce2e8dff25c7018702d10b21a20ef7135316f8daecd6c25b7f"
999−"checksum regex-syntax 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e734e891f5b408a29efbf8309e656876276f49ab6a6ac208600b4419bd893d90"
647+"checksum regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8c2f35eedad5295fdf00a63d7d4b238135723f92b434ec06774dad15c7ab0861"
1000−"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
648+"checksum rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "adacaae16d02b6ec37fdc7acfcddf365978de76d1983d3ee22afc260e1ca9619"
1001−"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
649+"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7"
1002−"checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421"
650+"checksum scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28"
1003−"checksum scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
1004−"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d"
1005−"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
1006−"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
1007−"checksum serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3"
651+"checksum serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "92514fb95f900c9b5126e32d020f5c6d40564c27a5ea6d1d7d9f157a96623560"
1008−"checksum serde_cbor 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622"
1009−"checksum serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e"
652+"checksum serde_derive 1.0.87 (registry+https://github.com/rust-lang/crates.io-index)" = "633e97856567e518b59ffb2ad7c7a4fd4c5d91d9c7f32dd38a27b2bf7e8114ea"
1010−"checksum serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)" = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c"
653+"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d"
1011−"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
1012"checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3"
654"checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3"
1013−"checksum syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)" = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd"
655+"checksum syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)" = "f92e629aa1d9c827b2bb8297046c1ccffc57c99b947a680d3ccff1f136a3bee9"
1014−"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
656+"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
1015−"checksum tinytemplate 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d3dc76004a03cec1c5932bca4cdc2e39aaa798e3f82363dd94f9adf6098c12f"
657+"checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f"
1016−"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
658+"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
1017−"checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20"
659+"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
1018−"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
660+"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86"
661+"checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1"
662+"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
663+"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737"
1019"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
664"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
1020−"checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
1021−"checksum wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "29ae32af33bacd663a9a28241abecf01f2be64e6a185c6139b04f18b6385c5f2"
665+"checksum wasm-bindgen 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)" = "2d85ba4917abb23ee7788330797a7cb5ac7e5e997cdc67fde6f2c8d02cc08db3"
1022−"checksum wasm-bindgen-backend 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "1845584bd3593442dc0de6e6d9f84454a59a057722f36f005e44665d6ab19d85"
666+"checksum wasm-bindgen-backend 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)" = "51f9c26bd7d30f21ce334785ec10c5ce10cba28313979d793b8dddede19da3a4"
1023−"checksum wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1458706aa1b8fe6898d19433c9f110d93a05d1f22ae6adf55810409a94df34b4"
667+"checksum wasm-bindgen-futures 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "25ee19bc252f9fbe54dadc21713b98d7a72f1d5f4b497ef9d9c112897131c3c6"
1024−"checksum wasm-bindgen-macro 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "87fcc747e6b73c93d22c947a6334644d22cfec5abd8b66238484dc2b0aeb9fe4"
668+"checksum wasm-bindgen-macro 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)" = "316e6e568644bfa2009698217cec8f612a4e2cd7460b127f52573991c7ad9f53"
1025−"checksum wasm-bindgen-macro-support 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "3dc4b3f2c4078c8c4a5f363b92fcf62604c5913cbd16c6ff5aaf0f74ec03f570"
669+"checksum wasm-bindgen-macro-support 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)" = "29982ea76a7a35b5c3c052b6035abe9b84d8b6422d8881b68bd3472270a53eec"
1026−"checksum wasm-bindgen-shared 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "ca0b78d6d3be8589b95d1d49cdc0794728ca734adf36d7c9f07e6459508bb53d"
670+"checksum wasm-bindgen-shared 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)" = "59305c5b959d51437636db05bac82de2b52da2c05157e38e018c0bd54cfd9bbf"
1027−"checksum wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ed5833682054e941db0b7e2b4877b61bbaafd680608e40d268e070d34558b305"
671+"checksum wasm-bindgen-test 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)" = "8e8b80ffed02a5971865b6e6769398c3581b234a47c301193c44e7766b1a7a64"
1028−"checksum wasm-bindgen-test-macro 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9c5dcf70d21d0ff0ea5cdb97dba9a9b56ddad4a9bd10c8912eedc55618be022f"
672+"checksum wasm-bindgen-test-macro 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)" = "92b66e86a39d33a9d7d46ae7a8c90e692ff4b7ecc0a69075a060511bc687562d"
1029−"checksum wasm-bindgen-webidl 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "3126356474ceb717c8fb5549ae387c9fbf4872818454f4d87708bee997214bb5"
673+"checksum wasm-bindgen-webidl 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "780c16a1a26d28695ac9af7176e11dbc3c188e6d0b3095ad51cc2d0e9e31fcc9"
1030−"checksum web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)" = "98405c0a2e722ed3db341b4c5b70eb9fe0021621f7350bab76df93b09b649bbf"
674+"checksum web-sys 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "02c19f8ab50a089f9d7c8070c191a919c85d10a4a37c5777a08e8f94ec12c0fc"
1031−"checksum weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bb43f70885151e629e2a19ce9e50bd730fd436cfd4b666894c9ce4de9141164"
675+"checksum weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "26a4c67f132386d965390b8a734d5d10adbcd30eb5cc74bd9229af8b83f10044"
1032−"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
676+"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
1033"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
677"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1034"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
678"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
1035"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
679"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
680+"checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba"
Cargo.toml+15−63
@@ -1,59 +1,20 @@
1[package]
1[package]
2+name = "dodrio"
3+version = "0.1.0"
2authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
4authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
3−categories = ["wasm", "web-programming"]
4−description = "A fast, bump-allocated virtual DOM library."
5edition = "2018"
5edition = "2018"
6−keywords = ["virtual-dom", "vdom", "render", "bump"]
7−license = "MPL-2.0"
8−name = "dodrio"
9−readme = "./README.md"
10−repository = "https://github.com/fitzgen/dodrio"
11−version = "0.2.0"
12−
13−[badges]
14−travis-ci = { repository = "fitzgen/dodrio" }
15−
16−[lib]
17−path = "src/lib.rs"
18−bench = false
19−
20−[[test]]
21−name = "web"
22−path = "tests/web/main.rs"
23−required-features = ["xxx-unstable-internal-use-only"]
24−
25−[[bench]]
26−name = "benches"
27−path = "benches/benches.rs"
28−harness = false
29−required-features = ["xxx-unstable-internal-use-only"]
30−
31−[features]
32−# Don't rely on this feature! Its behavior will change and it could go away at
33−# any time. This enables monkey-patching DOM methods so that we wrap them and
34−# trace what DOM methods are used and how many times they are invoked when
35−# applying our emitted change lists to the physical DOM. This is useful for
36−# evaluating the differences between how Dodrio applies changes from virtual
37−# nodes to the physical DOM versus how other virtual DOM libraries do the same.
38−xxx-unstable-strace = []
39−
40−# Don't use this feature! It only exists for exposing otherwise-internal APIs
41−# for testing and benchmarks.
42−xxx-unstable-internal-use-only = []
43
6
44[dependencies]
7[dependencies]
45−bumpalo = "2.6.0"
8+bumpalo = "2.1.0"
46−cfg-if = "0.1.10"
9+cfg-if = "0.1.7"
47−fxhash = "0.2.1"
10+futures = "0.1.25"
11+wasm-bindgen = "0.2.37"
12+wasm-bindgen-futures = "0.3.14"
48−js-sys = "0.3.32"
13+js-sys = "0.3.14"
49−log = { version = "0.4.11", optional = true }
14+log = { version = "0.4.6", optional = true }
50−longest-increasing-subsequence = "0.1.0"
51−serde = { version = "1.0.114", optional = true }
52−wasm-bindgen = "0.2.55"
53−wasm-bindgen-futures = "0.4.5"
54
15
55[dependencies.web-sys]
16[dependencies.web-sys]
56−version = "0.3.32"
17+version = "0.3.14"
57features = [
18features = [
58 "console",
19 "console",
59 "Document",
20 "Document",
@@ -64,14 +25,13 @@ features = [
64]
25]
65
26
66[dev-dependencies]
27[dev-dependencies]
67−console_log = "0.2.0"
28+console_log = "0.1.2"
68−dodrio-js-api = { version = "=0.2.0", path = "./crates/js-api" }
29+dodrio-js-api = { version = "=0.1.0", path = "./crates/js-api" }
69−futures = "0.3.5"
70−log = "0.4.11"
30+log = "0.4.6"
71−wasm-bindgen-test = "0.3.5"
31+wasm-bindgen-test = "0.2.37"
72
32
73[dev-dependencies.web-sys]
33[dev-dependencies.web-sys]
74−version = "0.3.32"
34+version = "0.3.14"
75features = [
35features = [
76 "Attr",
36 "Attr",
77 "EventTarget",
37 "EventTarget",
@@ -80,12 +40,6 @@ features = [
80 "NodeList",
40 "NodeList",
81]
41]
82
42
83−[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
84−criterion = "0.3.3"
85−
86−[profile.bench]
87−debug = true
88−
89[profile.release]
43[profile.release]
90incremental = false
44incremental = false
91lto = true
45lto = true
@@ -95,11 +49,9 @@ opt-level = "s"
95members = [
49members = [
96 "./crates/js-api",
50 "./crates/js-api",
97 "./examples/counter",
51 "./examples/counter",
98− "./examples/game-of-life",
99 "./examples/hello-world",
52 "./examples/hello-world",
100 "./examples/input-form",
53 "./examples/input-form",
101 "./examples/js-component",
54 "./examples/js-component",
102 "./examples/moire",
55 "./examples/moire",
103− "./examples/sierpinski-triangle",
104 "./examples/todomvc",
56 "./examples/todomvc",
105]
57]
README.md+23−60
@@ -1,14 +1,15 @@
1−# Dodrio
1+# `dodrio`
2
2
3−A fast, bump-allocated virtual DOM library for Rust and WebAssembly. Note that
3+`dodrio` is an **experimental** virtual DOM library for Rust and WebAssembly. It
4−Dodrio is still **experimental**.
4+is a proving ground for a bump allocation-based virtual DOM architecture, that I
5+believe is the best way to take advantage of WebAssembly's strengths in the
6+context of a virtual DOM library.
5
7
6<!-- START doctoc generated TOC please keep comment here to allow auto update -->
8<!-- START doctoc generated TOC please keep comment here to allow auto update -->
7<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
9<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
8
10
9
11
10- [Warning](#warning)
12- [Warning](#warning)
11−- [Examples](#examples)
12- [Design](#design)
13- [Design](#design)
13 - [Bump Allocation](#bump-allocation)
14 - [Bump Allocation](#bump-allocation)
14 - [Change List as Stack Machine](#change-list-as-stack-machine)
15 - [Change List as Stack Machine](#change-list-as-stack-machine)
@@ -18,53 +19,11 @@ Dodrio is still **experimental**.
18
19
19## Warning
20## Warning
20
21
21−I reiterate that Dodrio is in a very **experimental** state. It probably has
22+I reiterate that `dodrio` is in a very **experimental** state. It has not
22−bugs, and no one is using it in production.
23+actually been profiled or tuned for performance yet, so while I think the design
23−
24+should yield a very fast virtual DOM library, `dodrio` is almost certainly not
24−## Examples
25+fast right now. Additionally, it is probably riddled with bugs, and is assuredly
25−
26+missing features that are critical for actually building Web applications.
26−Here is the classic "Hello, World!" example:
27−
28−```rust
29−struct Hello {
30− who: String,
31−}
32−
33−impl Render for Hello {
34− fn render<'a>(&self, cx: &mut RenderContext<a>) -> Node<'a> {
35− let who = bumpalo::format!(in cx.bump, "Hello, {}!", self.who);
36− div(cx)
37− .children([text(who.into_bump_str())])
38− .finish()
39− }
40−}
41−```
42−
43−More examples can be found in [the `examples`
44−directory](https://github.com/fitzgen/dodrio/tree/master/examples), including:
45−
46−* [`counter`](https://github.com/fitzgen/dodrio/tree/master/examples/counter):
47− Incrementing and decrementing a counter.
48−* [`input-form`](https://github.com/fitzgen/dodrio/tree/master/examples/input-form):
49− Reading an `<input>` and displaying its contents.
50−* [`todomvc`](https://github.com/fitzgen/dodrio/tree/master/examples/todomvc):
51− An implementation of the infamous TodoMVC application.
52−* [`moire`](https://github.com/fitzgen/dodrio/tree/master/examples/moire): The
53− WebRender Moiré patterns demo.
54−* [`game-of-life`](https://github.com/fitzgen/dodrio/tree/master/examples/game-of-life):
55− The Rust and WebAssembly book's Game of Life tutorial rendered with Dodrio
56− instead of to 2D canvas.
57−* [`js-component`](https://github.com/fitzgen/dodrio/tree/master/examples/js-component):
58− Defines a rendering component in JavaScript with the `dodrio-js-api` crate.
59−
60−## Cargo Features
61−
62−* `log` &mdash; enable debugging-oriented log messages with the `log` crate's
63− facade. You still have to initialize a logger for the messages to go anywhere,
64− such as [`console_log`](https://github.com/iamcodemaker/console_log).
65−
66−* `serde` &mdash; enable `serde::{Serialize, Deserialize}` implementations for
67− `Cached<R>` where `R` is serializable and deserializable.
68
27
69## Design
28## Design
70
29
@@ -74,7 +33,7 @@ Bump allocation is essentially the fastest method of allocating objects. It has
74constraints, but works particularly well when allocation lifetimes match program
33constraints, but works particularly well when allocation lifetimes match program
75phases. And virtual DOMs are very phase oriented.
34phases. And virtual DOMs are very phase oriented.
76
35
77−Dodrio maintains three bump allocation arenas:
36+`dodrio` maintains three bump allocation arenas:
78
37
791. The newest, most up-to-date virtual DOM. The virtual DOM nodes themselves and
381. The newest, most up-to-date virtual DOM. The virtual DOM nodes themselves and
80 any temporary containers needed while creating them are allocated into this
39 any temporary containers needed while creating them are allocated into this
@@ -87,12 +46,12 @@ Dodrio maintains three bump allocation arenas:
87
46
88Rendering happens as follows:
47Rendering happens as follows:
89
48
90−1. The application state is rendered into bump allocation arena (1).
49+* The application state is rendered into bump allocation arena (1).
91−2. (1) is diffed with (2) and the changes are emitted into (3).
50+* (1) diffed with (2) and the changes are emitted into (3).
92−3. JavaScript code applies the change list in (3) to the physical DOM.
51+* JavaScript code applies the change list in (3) to the physical DOM.
93−4. (1) and (2) are swapped, double-buffering style, and the new (1) has its bump
52+* (1) and (2) are swapped, double-buffering style, and the new (1) has its bump
94− allocation pointer reset, as does (3).
53+ allocation pointer reset, as does (3).
95−5. Rinse and repeat.
54+* Rinse and repeat.
96
55
97### Change List as Stack Machine
56### Change List as Stack Machine
98
57
@@ -103,6 +62,10 @@ diffs, a task that is essentially a tree traversal.
103
62
104### Library — Not Framework
63### Library — Not Framework
105
64
106−Dodrio is just a library. (And did I mention it is experimental?!) It is not a
65+`dodrio` is just a library. (And did I mention it is experimental?!) It is not a
107full-fledged, complete, batteries-included solution for all frontend Web
66full-fledged, complete, batteries-included solution for all frontend Web
108−development. And it never intends to become that either.
67+development. And it never intends to become that either. Its highest ambition is
68+to prove that its bump allocation-based design is a good one, and maaaayyyyyybe
69+become a production-grade virtual DOM library that you could plug into a larger
70+application or toolkit eventually. But it will never be a complete,
71+batteries-included framework.
benches/benches.rsdeleted−118
@@ -1,118 +0,0 @@
1−#![cfg(all(
2− feature = "xxx-unstable-internal-use-only",
3− not(target_arch = "wasm32")
4−))]
5−
6−use criterion::{
7− black_box, criterion_group, criterion_main, Benchmark, Criterion, ParameterizedBenchmark,
8− Throughput,
9−};
10−use dodrio::{
11− builder::*,
12− bumpalo::{self, Bump},
13− CachedSet, Node, Render, RenderContext, Vdom,
14−};
15−use std::cell::RefCell;
16−use std::convert::TryInto;
17−
18−/// The simplest thing we can render: `<div/>`.
19−struct Empty;
20−impl<'a> Render<'a> for Empty {
21− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
22− div(&cx).finish()
23− }
24−}
25−
26−/// Render a list that is `self.0` items long, has attributes and listeners.
27−struct SimpleList(usize);
28−impl<'a> Render<'a> for SimpleList {
29− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
30− let mut children = bumpalo::collections::Vec::with_capacity_in(self.0, cx.bump);
31− children.extend((0..self.0).map(|_| {
32− li(&cx)
33− .attr("class", "my-list-item")
34− .on("click", |_root, _vdom, _event| {
35− panic!("no one should call this")
36− })
37− .children([text("a list item")])
38− .finish()
39− }));
40− ol(&cx).attr("id", "my-list").children(children).finish()
41− }
42−}
43−
44−fn criterion_benchmark(c: &mut Criterion) {
45− c.bench(
46− "render",
47− Benchmark::new("empty", {
48− let mut bump = Bump::new();
49− let cached_set = RefCell::new(CachedSet::default());
50− let mut templates = Default::default();
51− move |b| {
52− bump.reset();
53− let mut cx = RenderContext::new(&bump, &cached_set, &mut templates);
54− b.iter(|| {
55− black_box(Empty.render(&mut cx));
56− })
57− }
58− }),
59− );
60−
61− c.bench(
62− "render",
63− ParameterizedBenchmark::new(
64− "list",
65− {
66− let mut bump = Bump::new();
67− let cached_set = RefCell::new(CachedSet::default());
68− let mut templates = Default::default();
69− move |b, n| {
70− bump.reset();
71− let mut cx = RenderContext::new(&bump, &cached_set, &mut templates);
72− b.iter(|| {
73− black_box(SimpleList(*n).render(&mut cx));
74− })
75− }
76− },
77− vec![100, 1_000, 10_000],
78− )
79− .throughput(|n| Throughput::Elements((*n).try_into().unwrap())),
80− );
81−
82− c.bench(
83− "render-and-diff",
84− ParameterizedBenchmark::new(
85− "same-list",
86− |b, &n| {
87− let vdom = Vdom::new(&(), SimpleList(n));
88− b.iter(|| {
89− vdom.immediately_render_and_diff(SimpleList(n));
90− black_box(&vdom);
91− })
92− },
93− vec![100, 1_000, 10_000],
94− )
95− .with_function("empty-to-full-list-to-empty", |b, &n| {
96− b.iter(|| {
97− let vdom = Vdom::new(&(), Empty);
98− vdom.immediately_render_and_diff(SimpleList(n));
99− black_box(&vdom);
100− vdom.immediately_render_and_diff(Empty);
101− black_box(&vdom);
102− });
103− })
104− .with_function("append-one-and-remove-one", |b, &n| {
105− let vdom = Vdom::new(&(), SimpleList(n));
106− b.iter(|| {
107− vdom.immediately_render_and_diff(SimpleList(n + 1));
108− black_box(&vdom);
109− vdom.immediately_render_and_diff(SimpleList(n));
110− black_box(&vdom);
111− })
112− })
113− .throughput(|n| Throughput::Elements((*n).try_into().unwrap())),
114− );
115−}
116−
117−criterion_group!(benches, criterion_benchmark);
118−criterion_main!(benches);
build.sh+2−2
@@ -4,8 +4,8 @@ cd $(dirname $0)
4set -eux
4set -eux
5
5
6cargo fmt --all || true
6cargo fmt --all || true
7−cargo build --all --target wasm32-unknown-unknown
7+cargo check --all --target wasm32-unknown-unknown
8
8
9for x in ./examples/*; do
9for x in ./examples/*; do
10− wasm-pack build --target web "$x"
10+ wasm-pack build --target no-modules "$x"
11done
11done
crates/js-api/Cargo.toml+8−15
@@ -1,22 +1,15 @@
1[package]
1[package]
2+name = "dodrio-js-api"
3+version = "0.1.0"
2authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
4authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
3−categories = ["wasm", "web-programming"]
4−description = "An API for implementing Dodrio rendering components with JavaScript"
5edition = "2018"
5edition = "2018"
6−keywords = ["javascript", "js", "vdom", "virtual-dom"]
7−license = "MPL-2.0"
8−name = "dodrio-js-api"
9−repository = "https://github.com/fitzgen/dodrio"
10−version = "0.2.0"
11−
12−[badges]
13−travis-ci = { repository = "fitzgen/dodrio" }
14
6
15[dependencies]
7[dependencies]
16−dodrio = { version = "=0.2.0", path = "../.." }
8+dodrio = { version = "=0.1.0", path = "../.." }
17−wasm-bindgen = "0.2.55"
9+wasm-bindgen = "0.2.37"
18−js-sys = "0.3.32"
10+js-sys = "0.3.14"
19−wasm-bindgen-futures = "0.4.5"
11+wasm-bindgen-futures = "0.3.14"
12+futures = "0.1.25"
20
13
21[dev-dependencies]
14[dev-dependencies]
22−wasm-bindgen-test = "0.3.5"
15+wasm-bindgen-test = "0.2.37"
crates/js-api/src/lib.rs+38−30
@@ -66,7 +66,8 @@ And here is a Rust rendering component that internally uses the JS rendering
66component:
66component:
67
67
68```rust,no_run
68```rust,no_run
69+use dodrio::bumpalo::Bump;
69−use dodrio::{Node, Render, RenderContext, Vdom};
70+use dodrio::{Node, Render, Vdom};
70use dodrio_js_api::JsRender;
71use dodrio_js_api::JsRender;
71use js_sys::Object;
72use js_sys::Object;
72use wasm_bindgen::prelude::*;
73use wasm_bindgen::prelude::*;
@@ -99,13 +100,16 @@ impl GreetingViaJs {
99
100
100/// And finally the `Render` implementation! This adds a `<p>` element and some
101/// And finally the `Render` implementation! This adds a `<p>` element and some
101/// text around whatever the inner JS `Greeting` component renders.
102/// text around whatever the inner JS `Greeting` component renders.
102−impl<'a> Render<'a> for GreetingViaJs {
103+impl Render for GreetingViaJs {
103− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
104+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
105+ where
106+ 'a: 'bump,
107+ {
104 use dodrio::builder::*;
108 use dodrio::builder::*;
105− p(&cx)
109+ p(bump)
106 .children([
110 .children([
107 text("JavaScript says: "),
111 text("JavaScript says: "),
108− self.js.render(cx),
112+ self.js.render(bump),
109 ])
113 ])
110 .finish()
114 .finish()
111 }
115 }
@@ -115,7 +119,12 @@ impl<'a> Render<'a> for GreetingViaJs {
115 */
119 */
116#![deny(missing_docs, missing_debug_implementations)]
120#![deny(missing_docs, missing_debug_implementations)]
117
121
118−use dodrio::{builder, bumpalo, Node, Render, RenderContext};
122+use dodrio::{
123+ builder,
124+ bumpalo::{self, Bump},
125+ Node, Render,
126+};
127+use futures::prelude::*;
119use js_sys::{Object, Promise, Reflect};
128use js_sys::{Object, Promise, Reflect};
120use wasm_bindgen::prelude::*;
129use wasm_bindgen::prelude::*;
121use wasm_bindgen::JsCast;
130use wasm_bindgen::JsCast;
@@ -184,16 +193,12 @@ impl VdomWeak {
184 /// Schedule re-rendering of the virtual DOM. A promise is returned that is
193 /// Schedule re-rendering of the virtual DOM. A promise is returned that is
185 /// resolved after the rendering has happened.
194 /// resolved after the rendering has happened.
186 pub fn render(&self) -> Promise {
195 pub fn render(&self) -> Promise {
187− let future = self.inner.render();
188−
189− wasm_bindgen_futures::future_to_promise(async move {
196+ wasm_bindgen_futures::future_to_promise(
190− if let Err(e) = future.await {
197+ self.inner.render().map(|_| JsValue::null()).map_err(|e| {
191 let msg = e.to_string();
198 let msg = e.to_string();
192− Err(js_sys::Error::new(&msg).into())
199+ js_sys::Error::new(&msg).into()
193− } else {
200+ }),
194− Ok(JsValue::null())
195− }
196− })
201+ )
197 }
202 }
198}
203}
199
204
@@ -216,9 +221,12 @@ impl JsRender {
216 }
221 }
217}
222}
218
223
219−impl<'a> Render<'a> for JsRender {
224+impl Render for JsRender {
220− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
225+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
226+ where
227+ 'a: 'bump,
228+ {
221− create(cx, self.render())
229+ create(bump, self.render())
222 }
230 }
223}
231}
224
232
@@ -226,9 +234,9 @@ fn has_property(obj: &Object, property: &str) -> bool {
226 Reflect::has(obj, &property.into()).unwrap_or_default()
234 Reflect::has(obj, &property.into()).unwrap_or_default()
227}
235}
228
236
229−fn create<'a>(cx: &mut RenderContext<'a>, val: JsValue) -> Node<'a> {
237+fn create(bump: &Bump, val: JsValue) -> Node {
230 if let Some(txt) = val.as_string() {
238 if let Some(txt) = val.as_string() {
231− let text = bumpalo::collections::String::from_str_in(&txt, cx.bump);
239+ let text = bumpalo::collections::String::from_str_in(&txt, bump);
232 return builder::text(text.into_bump_str());
240 return builder::text(text.into_bump_str());
233 }
241 }
234
242
@@ -243,12 +251,12 @@ fn create<'a>(cx: &mut RenderContext<'a>, val: JsValue) -> Node<'a> {
243 );
251 );
244
252
245 let tag_name = elem.tag_name();
253 let tag_name = elem.tag_name();
246− let tag_name = bumpalo::collections::String::from_str_in(&tag_name, cx.bump);
254+ let tag_name = bumpalo::collections::String::from_str_in(&tag_name, bump);
247
255
248− builder::ElementBuilder::new(cx.bump, tag_name.into_bump_str())
256+ builder::ElementBuilder::new(bump, tag_name.into_bump_str())
249 .listeners({
257 .listeners({
250 let mut listeners =
258 let mut listeners =
251− bumpalo::collections::Vec::new_in(cx.bump);
259+ bumpalo::collections::Vec::new_in(bump);
252 if has_property(&elem, "listeners") {
260 if has_property(&elem, "listeners") {
253 let js_listeners = elem.listeners();
261 let js_listeners = elem.listeners();
254 listeners.reserve(js_listeners.length() as usize);
262 listeners.reserve(js_listeners.length() as usize);
@@ -267,10 +275,10 @@ fn create<'a>(cx: &mut RenderContext<'a>, val: JsValue) -> Node<'a> {
267 "listener objects returned by JS render methods must have an `callback` property"
275 "listener objects returned by JS render methods must have an `callback` property"
268 );
276 );
269 let on = listener.on();
277 let on = listener.on();
270− let on = bumpalo::collections::String::from_str_in(&on, cx.bump);
278+ let on = bumpalo::collections::String::from_str_in(&on, bump);
271 let callback = listener.callback();
279 let callback = listener.callback();
272 let elem = elem.clone();
280 let elem = elem.clone();
273− listeners.push(builder::on(cx.bump, on.into_bump_str(), move |_root, vdom, event| {
281+ listeners.push(builder::on(bump, on.into_bump_str(), move |_root, vdom, event| {
274 let vdom = VdomWeak::new(vdom);
282 let vdom = VdomWeak::new(vdom);
275 let vdom: JsValue = vdom.into();
283 let vdom: JsValue = vdom.into();
276 if let Err(e) = callback.call2(&elem, &vdom, &event) {
284 if let Err(e) = callback.call2(&elem, &vdom, &event) {
@@ -282,7 +290,7 @@ fn create<'a>(cx: &mut RenderContext<'a>, val: JsValue) -> Node<'a> {
282 listeners
290 listeners
283 })
291 })
284 .attributes({
292 .attributes({
285− let mut attributes = bumpalo::collections::Vec::new_in(cx.bump);
293+ let mut attributes = bumpalo::collections::Vec::new_in(bump);
286 if has_property(&elem, "attributes") {
294 if has_property(&elem, "attributes") {
287 let js_attributes = elem.attributes();
295 let js_attributes = elem.attributes();
288 attributes.reserve(js_attributes.length() as usize);
296 attributes.reserve(js_attributes.length() as usize);
@@ -301,21 +309,21 @@ fn create<'a>(cx: &mut RenderContext<'a>, val: JsValue) -> Node<'a> {
301 "attribute objects returned by JS render methods must have a `value` property"
309 "attribute objects returned by JS render methods must have a `value` property"
302 );
310 );
303 let name = attribute.name();
311 let name = attribute.name();
304− let name = bumpalo::collections::String::from_str_in(&name, cx.bump);
312+ let name = bumpalo::collections::String::from_str_in(&name, bump);
305 let value = attribute.value();
313 let value = attribute.value();
306− let value = bumpalo::collections::String::from_str_in(&value, cx.bump);
314+ let value = bumpalo::collections::String::from_str_in(&value, bump);
307 attributes.push(builder::attr(name.into_bump_str(), value.into_bump_str()));
315 attributes.push(builder::attr(name.into_bump_str(), value.into_bump_str()));
308 });
316 });
309 }
317 }
310 attributes
318 attributes
311 })
319 })
312 .children({
320 .children({
313− let mut children = bumpalo::collections::Vec::new_in(cx.bump);
321+ let mut children = bumpalo::collections::Vec::new_in(bump);
314 if has_property(&elem, "children") {
322 if has_property(&elem, "children") {
315 let js_children = elem.children();
323 let js_children = elem.children();
316 children.reserve(js_children.length() as usize);
324 children.reserve(js_children.length() as usize);
317 js_children.for_each(&mut |child, _index, _array| {
325 js_children.for_each(&mut |child, _index, _array| {
318− children.push(create(cx, child));
326+ children.push(create(bump, child));
319 });
327 });
320 }
328 }
321 children
329 children
examples/counter/Cargo.toml+5−5
@@ -11,13 +11,13 @@ crate-type = ["cdylib"]
11
11
12[dependencies]
12[dependencies]
13console_error_panic_hook = "0.1.6"
13console_error_panic_hook = "0.1.6"
14−console_log = "0.2.0"
14+console_log = "0.1.2"
15dodrio = { path = "../.." }
15dodrio = { path = "../.." }
16−log = "0.4.11"
16+log = "0.4.6"
17−wasm-bindgen = "0.2.55"
17+wasm-bindgen = "0.2.37"
18
18
19[dependencies.web-sys]
19[dependencies.web-sys]
20−version = "0.3.32"
20+version = "0.3.14"
21features = [
21features = [
22 "console",
22 "console",
23 "Document",
23 "Document",
@@ -30,4 +30,4 @@ features = [
30]
30]
31
31
32[dev-dependencies]
32[dev-dependencies]
33−wasm-bindgen-test = "0.3.5"
33+wasm-bindgen-test = "0.2.37"
examples/counter/README.md+1−1
@@ -9,7 +9,7 @@ See `src/lib.rs`.
9## Build
9## Build
10
10
11```
11```
12−wasm-pack build --target web
12+wasm-pack build --target no-modules
13```
13```
14
14
15## Serve
15## Serve
examples/counter/index.html+3−3
@@ -5,9 +5,9 @@
5 <title>Counter</title>
5 <title>Counter</title>
6 </head>
6 </head>
7 <body>
7 <body>
8− <script type="module">
9− import init from "./pkg/dodrio_counter.js";
8+ <script src="pkg/dodrio_counter.js"></script>
9+ <script>
10− init("./pkg/dodrio_counter_bg.wasm");
10+ wasm_bindgen("pkg/dodrio_counter_bg.wasm");
11 </script>
11 </script>
12 </body>
12 </body>
13</html>
13</html>
examples/counter/src/lib.rsmode 100755 → 100644+13−10
@@ -1,8 +1,9 @@
1−use dodrio::{bumpalo, Node, Render, RenderContext};
1+use dodrio::bumpalo::{self, Bump};
2+use dodrio::Render;
2use log::*;
3use log::*;
3use wasm_bindgen::prelude::*;
4use wasm_bindgen::prelude::*;
4
5
5−/// A counter that can be incremented and decremented!
6+/// A counter that can be incremented and decrmented!
6struct Counter {
7struct Counter {
7 count: isize,
8 count: isize,
8}
9}
@@ -26,16 +27,19 @@ impl Counter {
26
27
27// The `Render` implementation for `Counter`s displays the current count and has
28// The `Render` implementation for `Counter`s displays the current count and has
28// buttons to increment and decrement the count.
29// buttons to increment and decrement the count.
29−impl<'a> Render<'a> for Counter {
30+impl Render for Counter {
30− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
31+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> dodrio::Node<'bump>
32+ where
33+ 'a: 'bump,
34+ {
31 use dodrio::builder::*;
35 use dodrio::builder::*;
32
36
33 // Stringify the count as a bump-allocated string.
37 // Stringify the count as a bump-allocated string.
34− let count = bumpalo::format!(in cx.bump, "{}", self.count);
38+ let count = bumpalo::format!(in bump, "{}", self.count);
35
39
36− div(&cx)
40+ div(bump)
37 .children([
41 .children([
38− button(&cx)
42+ button(bump)
39 .on("click", |root, vdom, _event| {
43 .on("click", |root, vdom, _event| {
40 // Cast the root render component to a `Counter`, since
44 // Cast the root render component to a `Counter`, since
41 // we know that's what it is.
45 // we know that's what it is.
@@ -51,11 +55,10 @@ impl<'a> Render<'a> for Counter {
51 .children([text("+")])
55 .children([text("+")])
52 .finish(),
56 .finish(),
53 text(count.into_bump_str()),
57 text(count.into_bump_str()),
54− button(&cx)
58+ button(bump)
55 .on("click", |root, vdom, _event| {
59 .on("click", |root, vdom, _event| {
56 // Same as above, but decrementing instead of incrementing.
60 // Same as above, but decrementing instead of incrementing.
57− let counter = root.unwrap_mut::<Counter>();
61+ root.unwrap_mut::<Counter>().decrement();
58− counter.decrement();
59 vdom.schedule_render();
62 vdom.schedule_render();
60 })
63 })
61 .children([text("-")])
64 .children([text("-")])
examples/game-of-life/Cargo.tomldeleted−29
@@ -1,29 +0,0 @@
1−[package]
2−name = "dodrio-game-of-life"
3−version = "0.1.0"
4−authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
5−edition = "2018"
6−
7−[lib]
8−crate-type = ["cdylib"]
9−
10−[features]
11−
12−[dependencies]
13−dodrio = { path = "../.." }
14−wasm-bindgen = "0.2.55"
15−console_error_panic_hook = "0.1.6"
16−js-sys = "0.3.32"
17−wasm-bindgen-futures = "0.4.5"
18−
19−[dependencies.web-sys]
20−version = "0.3.32"
21−features = [
22− "Document",
23− "HtmlElement",
24− "Node",
25− "Window",
26−]
27−
28−[dev-dependencies]
29−wasm-bindgen-test = "0.3.5"
examples/game-of-life/README.mddeleted−23
@@ -1,23 +0,0 @@
1−# Conway's Game of Life
2−
3−A port of the [Rust and WebAssembly tutorial's Game of Life
4−implementation](https://rustwasm.github.io/book/game-of-life/introduction.html)
5−to rendering to HTML with Dodrio instead of to a canvas via JavaScript.
6−
7−## Source
8−
9−See `src/lib.rs`.
10−
11−## Build
12−
13−```
14−wasm-pack build --target web
15−```
16−
17−## Serve
18−
19−Use any HTTP server, for example:
20−
21−```
22−python -m SimpleHTTPServer
23−```
examples/game-of-life/index.htmldeleted−44
@@ -1,44 +0,0 @@
1−<!DOCTYPE html>
2−<html>
3− <head>
4− <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
5− <title>Conway's Game of Life</title>
6− <style>
7− .cell {
8− display: inline-block;
9− width: 1vh;
10− height: 1vh;
11− border: 1px #cccccc solid;
12− margin: -1px;
13− padding: 0;
14− }
15−
16− .row {
17− height: 1vh;
18− margin: 0;
19− padding: 0;
20− }
21−
22− body {
23− width: 100vw;
24− height: 100vh;
25− display: flex;
26− align-items: center;
27− justify-content: center;
28− padding: 0;
29− margin: 0;
30− }
31−
32− html {
33− padding: 0;
34− margin: 0;
35− }
36− </style>
37− </head>
38− <body>
39− <script type="module">
40− import init from "./pkg/dodrio_game_of_life.js";
41− init("./pkg/dodrio_game_of_life_bg.wasm");
42− </script>
43− </body>
44−</html>
examples/game-of-life/src/lib.rsdeleted−218
@@ -1,218 +0,0 @@
1−use dodrio::{bumpalo, Node, Render, RenderContext, Vdom};
2−use std::cell::RefCell;
3−use std::rc::Rc;
4−use wasm_bindgen::{prelude::*, JsCast};
5−
6−#[repr(u8)]
7−#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8−pub enum Cell {
9− Dead = 0,
10− Alive = 1,
11−}
12−
13−/// The universe containing all of the cells for our Game of Life.
14−struct Universe {
15− width: u32,
16− height: u32,
17− cells: Vec<Cell>,
18−}
19−
20−impl Universe {
21− /// Construct a new universe.
22− pub fn new() -> Universe {
23− let width = 64;
24− let height = 64;
25−
26− let cells = (0..width * height)
27− .map(|i| {
28− if i % 2 == 0 || i % 7 == 0 {
29− Cell::Alive
30− } else {
31− Cell::Dead
32− }
33− })
34− .collect();
35−
36− Universe {
37− width,
38− height,
39− cells,
40− }
41− }
42−
43− /// Compute one tick of the universe.
44− pub fn tick(&mut self) {
45− let mut next = self.cells.clone();
46−
47− for row in 0..self.height {
48− for col in 0..self.width {
49− let idx = self.get_index(row, col);
50− let cell = self.cells[idx];
51− let live_neighbors = self.live_neighbor_count(row, col);
52−
53− let next_cell = match (cell, live_neighbors) {
54− // Rule 1: Any live cell with fewer than two live neighbours
55− // dies, as if caused by underpopulation.
56− (Cell::Alive, x) if x < 2 => Cell::Dead,
57− // Rule 2: Any live cell with two or three live neighbours
58− // lives on to the next generation.
59− (Cell::Alive, 2) | (Cell::Alive, 3) => Cell::Alive,
60− // Rule 3: Any live cell with more than three live
61− // neighbours dies, as if by overpopulation.
62− (Cell::Alive, x) if x > 3 => Cell::Dead,
63− // Rule 4: Any dead cell with exactly three live neighbours
64− // becomes a live cell, as if by reproduction.
65− (Cell::Dead, 3) => Cell::Alive,
66− // All other cells remain in the same state.
67− (otherwise, _) => otherwise,
68− };
69−
70− next[idx] = next_cell;
71− }
72− }
73−
74− self.cells = next;
75− }
76−
77− fn get_index(&self, row: u32, column: u32) -> usize {
78− (row * self.width + column) as usize
79− }
80−
81− fn live_neighbor_count(&self, row: u32, column: u32) -> u8 {
82− let mut count = 0;
83−
84− let north = if row == 0 { self.height - 1 } else { row - 1 };
85−
86− let south = if row == self.height - 1 { 0 } else { row + 1 };
87−
88− let west = if column == 0 {
89− self.width - 1
90− } else {
91− column - 1
92− };
93−
94− let east = if column == self.width - 1 {
95− 0
96− } else {
97− column + 1
98− };
99−
100− let nw = self.get_index(north, west);
101− count += self.cells[nw] as u8;
102−
103− let n = self.get_index(north, column);
104− count += self.cells[n] as u8;
105−
106− let ne = self.get_index(north, east);
107− count += self.cells[ne] as u8;
108−
109− let w = self.get_index(row, west);
110− count += self.cells[w] as u8;
111−
112− let e = self.get_index(row, east);
113− count += self.cells[e] as u8;
114−
115− let sw = self.get_index(south, west);
116− count += self.cells[sw] as u8;
117−
118− let s = self.get_index(south, column);
119− count += self.cells[s] as u8;
120−
121− let se = self.get_index(south, east);
122− count += self.cells[se] as u8;
123−
124− count
125− }
126−}
127−
128−/// The rendering implementation for our Game of Life.
129−impl<'a> Render<'a> for Universe {
130− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
131− use dodrio::builder::*;
132−
133− let mut rows = bumpalo::collections::Vec::with_capacity_in(self.height as usize, cx.bump);
134−
135− for row in self.cells.chunks(self.width as usize) {
136− let mut cells =
137− bumpalo::collections::Vec::with_capacity_in(self.width as usize, cx.bump);
138−
139− for cell in row {
140− cells.push(
141− span(&cx)
142− .attr("class", "cell")
143− .attr(
144− "style",
145− match cell {
146− Cell::Alive => "background-color: black",
147− Cell::Dead => "background-color: white",
148− },
149− )
150− .finish(),
151− );
152− }
153−
154− rows.push(div(&cx).attr("class", "row").children(cells).finish());
155− }
156−
157− div(&cx).attr("class", "universe").children(rows).finish()
158− }
159−}
160−
161−#[wasm_bindgen(start)]
162−pub fn run() {
163− // Set up the panic hook for debugging when things go wrong.
164− console_error_panic_hook::set_once();
165−
166− // Grab the document's `<body>`.
167− let window = web_sys::window().unwrap_throw();
168− let document = window.document().unwrap_throw();
169− let body = document.body().unwrap_throw();
170−
171− // Create a new Game of Life `Universe` render component.
172− let universe = Universe::new();
173−
174− // Create a virtual DOM and mount it and the `Universe` render component to the
175− // `<body>`.
176− let vdom = Vdom::new(body.as_ref(), universe);
177−
178− // Kick off a loop that keeps computing a tick of the universe and then
179− // re-rendering on every animation frame.
180− let rc = <Rc<RefCell<Option<Closure<dyn FnMut()>>>>>::default();
181− let rc2 = rc.clone();
182− let window2 = window.clone();
183− let weak = vdom.weak();
184− let f = Closure::wrap(Box::new(move || {
185− weak.schedule_render();
186−
187− wasm_bindgen_futures::spawn_local({
188− let weak = weak.clone();
189− async move {
190− let fut = weak.with_component(|root| {
191− let universe = root.unwrap_mut::<Universe>();
192− universe.tick();
193− });
194−
195− if fut.await.is_err() {
196− wasm_bindgen::throw_str("impossible, we always keep the vdom alive");
197− }
198− }
199− });
200−
201− window
202− .request_animation_frame(
203− rc.borrow()
204− .as_ref()
205− .unwrap_throw()
206− .as_ref()
207− .unchecked_ref::<js_sys::Function>(),
208− )
209− .unwrap_throw();
210− }) as Box<dyn FnMut()>);
211− window2
212− .request_animation_frame(f.as_ref().unchecked_ref::<js_sys::Function>())
213− .unwrap_throw();
214− *rc2.borrow_mut() = Some(f);
215−
216− // Run the virtual DOM forever and don't unmount it.
217− vdom.forget();
218−}
examples/hello-world/Cargo.toml+3−3
@@ -11,11 +11,11 @@ crate-type = ["cdylib"]
11
11
12[dependencies]
12[dependencies]
13dodrio = { path = "../.." }
13dodrio = { path = "../.." }
14−wasm-bindgen = "0.2.55"
14+wasm-bindgen = "0.2.37"
15console_error_panic_hook = "0.1.6"
15console_error_panic_hook = "0.1.6"
16
16
17[dependencies.web-sys]
17[dependencies.web-sys]
18−version = "0.3.32"
18+version = "0.3.14"
19features = [
19features = [
20 "Document",
20 "Document",
21 "HtmlElement",
21 "HtmlElement",
@@ -24,4 +24,4 @@ features = [
24]
24]
25
25
26[dev-dependencies]
26[dev-dependencies]
27−wasm-bindgen-test = "0.3.5"
27+wasm-bindgen-test = "0.2.37"
examples/hello-world/README.md+1−1
@@ -9,7 +9,7 @@ See `src/lib.rs`.
9## Build
9## Build
10
10
11```
11```
12−wasm-pack build --target web
12+wasm-pack build --target no-modules
13```
13```
14
14
15## Serve
15## Serve
examples/hello-world/index.html+3−3
@@ -5,9 +5,9 @@
5 <title>Hello, World!</title>
5 <title>Hello, World!</title>
6 </head>
6 </head>
7 <body>
7 <body>
8− <script type="module">
9− import init from "./pkg/dodrio_hello_world.js";
8+ <script src="pkg/dodrio_hello_world.js"></script>
9+ <script>
10− init("./pkg/dodrio_hello_world_bg.wasm");
10+ wasm_bindgen("pkg/dodrio_hello_world_bg.wasm");
11 </script>
11 </script>
12 </body>
12 </body>
13</html>
13</html>
examples/hello-world/src/lib.rs+14−12
@@ -1,20 +1,24 @@
1−use dodrio::{builder::*, bumpalo};
1+use dodrio::bumpalo::Bump;
2−use dodrio::{Node, Render, RenderContext, Vdom};
2+use dodrio::{Node, Render, Vdom};
3use wasm_bindgen::prelude::*;
3use wasm_bindgen::prelude::*;
4
4
5/// A rendering component that displays a greeting.
5/// A rendering component that displays a greeting.
6−struct Hello {
6+struct Hello<'who> {
7 /// Who to greet.
7 /// Who to greet.
8− who: String,
8+ who: &'who str,
9}
9}
10
10
11// The `Render` implementation describes how to render a `Hello` component into
11// The `Render` implementation describes how to render a `Hello` component into
12// HTML.
12// HTML.
13−impl<'a> Render<'a> for Hello {
13+impl<'who> Render for Hello<'who> {
14− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
14+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
15− let msg = bumpalo::format!(in cx.bump, "Hello, {}!", self.who);
15+ where
16− let msg = msg.into_bump_str();
16+ 'a: 'bump,
17+ {
18+ use dodrio::builder::*;
19+ p(bump)
17− p(&cx).children([text(msg)]).finish()
20+ .children([text("Hello, "), text(self.who), text("!")])
21+ .finish()
18 }
22 }
19}
23}
20
24
@@ -29,9 +33,7 @@ pub fn run() {
29 let body = document.body().unwrap_throw();
33 let body = document.body().unwrap_throw();
30
34
31 // Create a new `Hello` render component.
35 // Create a new `Hello` render component.
32− let component = Hello {
36+ let component = Hello { who: "World" };
33− who: String::from("World"),
34− };
35
37
36 // Create a virtual DOM and mount it and the `Hello` render component to the
38 // Create a virtual DOM and mount it and the `Hello` render component to the
37 // `<body>`.
39 // `<body>`.
examples/input-form/Cargo.toml+3−3
@@ -11,11 +11,11 @@ crate-type = ["cdylib"]
11
11
12[dependencies]
12[dependencies]
13dodrio = { path = "../.." }
13dodrio = { path = "../.." }
14−wasm-bindgen = "0.2.55"
14+wasm-bindgen = "0.2.37"
15console_error_panic_hook = "0.1.6"
15console_error_panic_hook = "0.1.6"
16
16
17[dependencies.web-sys]
17[dependencies.web-sys]
18−version = "0.3.32"
18+version = "0.3.14"
19features = [
19features = [
20 "console",
20 "console",
21 "Document",
21 "Document",
@@ -29,4 +29,4 @@ features = [
29]
29]
30
30
31[dev-dependencies]
31[dev-dependencies]
32−wasm-bindgen-test = "0.3.5"
32+wasm-bindgen-test = "0.2.37"
examples/input-form/README.md+1−1
@@ -10,7 +10,7 @@ See `src/lib.rs`.
10## Build
10## Build
11
11
12```
12```
13−wasm-pack build --target web
13+wasm-pack build --target no-modules
14```
14```
15
15
16## Serve
16## Serve
examples/input-form/index.html+3−3
@@ -5,9 +5,9 @@
5 <title>Input Form</title>
5 <title>Input Form</title>
6 </head>
6 </head>
7 <body>
7 <body>
8− <script type="module">
9− import init from "./pkg/dodrio_input_form.js";
8+ <script src="pkg/dodrio_input_form.js"></script>
9+ <script>
10− init("./pkg/dodrio_input_form_bg.wasm");
10+ wasm_bindgen("pkg/dodrio_input_form_bg.wasm");
11 </script>
11 </script>
12 </body>
12 </body>
13</html>
13</html>
examples/input-form/src/lib.rs+11−10
@@ -1,4 +1,5 @@
1−use dodrio::{bumpalo, Node, Render, RenderContext};
1+use dodrio::bumpalo::{self, Bump};
2+use dodrio::{Node, Render};
2use wasm_bindgen::prelude::*;
3use wasm_bindgen::prelude::*;
3use wasm_bindgen::JsCast;
4use wasm_bindgen::JsCast;
4
5
@@ -23,18 +24,18 @@ impl SayHelloTo {
23
24
24// The `Render` implementation has a text `<input>` and a `<div>` that shows a
25// The `Render` implementation has a text `<input>` and a `<div>` that shows a
25// greeting to the `<input>`'s value.
26// greeting to the `<input>`'s value.
26−impl<'a> Render<'a> for SayHelloTo {
27+impl Render for SayHelloTo {
27− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
28+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
29+ where
30+ 'a: 'bump,
31+ {
28 use dodrio::builder::*;
32 use dodrio::builder::*;
29
33
30− div(&cx)
34+ div(bump)
31 .children([
35 .children([
32− input(&cx)
36+ input(bump)
33 .attr("type", "text")
37 .attr("type", "text")
34− .attr(
35− "value",
38+ .attr("value", &self.who)
36− bumpalo::format!(in cx.bump, "{}", self.who).into_bump_str(),
37− )
38 .on("input", |root, vdom, event| {
39 .on("input", |root, vdom, event| {
39 // If the event's target is our input...
40 // If the event's target is our input...
40 let input = match event
41 let input = match event
@@ -54,7 +55,7 @@ impl<'a> Render<'a> for SayHelloTo {
54 vdom.schedule_render();
55 vdom.schedule_render();
55 })
56 })
56 .finish(),
57 .finish(),
57− text(bumpalo::format!(in cx.bump, "Hello, {}!", self.who).into_bump_str()),
58+ text(bumpalo::format!(in bump, "Hello, {}!", self.who).into_bump_str()),
58 ])
59 ])
59 .finish()
60 .finish()
60 }
61 }
examples/js-component/Cargo.toml+3−3
@@ -12,12 +12,12 @@ crate-type = ["cdylib"]
12[dependencies]
12[dependencies]
13dodrio = { path = "../.." }
13dodrio = { path = "../.." }
14dodrio-js-api = { path = "../../crates/js-api" }
14dodrio-js-api = { path = "../../crates/js-api" }
15−wasm-bindgen = "0.2.55"
15+wasm-bindgen = "0.2.37"
16console_error_panic_hook = "0.1.6"
16console_error_panic_hook = "0.1.6"
17−js-sys = "0.3.32"
17+js-sys = "0.3.14"
18
18
19[dependencies.web-sys]
19[dependencies.web-sys]
20−version = "0.3.32"
20+version = "0.3.14"
21features = [
21features = [
22 "Document",
22 "Document",
23 "HtmlElement",
23 "HtmlElement",
examples/js-component/README.md+1−1
@@ -11,7 +11,7 @@ See `src/lib.rs` for the Rust code.
11## Build
11## Build
12
12
13```
13```
14−wasm-pack build --target web
14+wasm-pack build --target no-modules
15```
15```
16
16
17## Serve
17## Serve
examples/js-component/index.html+3−3
@@ -6,9 +6,9 @@
6 </head>
6 </head>
7 <body>
7 <body>
8 <script src="greeting.js"></script>
8 <script src="greeting.js"></script>
9− <script type="module">
10− import init from "./pkg/dodrio_js_component.js";
9+ <script src="pkg/dodrio_js_component.js"></script>
10+ <script>
11− init("./pkg/dodrio_js_component_bg.wasm");
11+ wasm_bindgen("pkg/dodrio_js_component_bg.wasm");
12 </script>
12 </script>
13 </body>
13 </body>
14</html>
14</html>
examples/js-component/src/lib.rs+10−6
@@ -1,4 +1,5 @@
1+use dodrio::bumpalo::Bump;
1−use dodrio::{Node, Render, RenderContext, Vdom};
2+use dodrio::{Node, Render, Vdom};
2use dodrio_js_api::JsRender;
3use dodrio_js_api::JsRender;
3use js_sys::Object;
4use js_sys::Object;
4use wasm_bindgen::prelude::*;
5use wasm_bindgen::prelude::*;
@@ -31,11 +32,14 @@ impl GreetingViaJs {
31
32
32/// Here's the `Render` implementation! This adds a `<p>` element and some text
33/// Here's the `Render` implementation! This adds a `<p>` element and some text
33/// around whatever the inner JS `Greeting` component renders.
34/// around whatever the inner JS `Greeting` component renders.
34−impl<'a> Render<'a> for GreetingViaJs {
35+impl Render for GreetingViaJs {
35− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
36+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
37+ where
38+ 'a: 'bump,
39+ {
36 use dodrio::builder::*;
40 use dodrio::builder::*;
37− p(&cx)
41+ p(bump)
38− .children([text("JavaScript says: "), self.js.render(cx)])
42+ .children([text("JavaScript says: "), self.js.render(bump)])
39 .finish()
43 .finish()
40 }
44 }
41}
45}
@@ -54,7 +58,7 @@ pub fn run() {
54 // Create a new `GreetingViaJs` render component.
58 // Create a new `GreetingViaJs` render component.
55 let component = GreetingViaJs::new("World");
59 let component = GreetingViaJs::new("World");
56
60
57− // Create a virtual DOM and mount it and the `GreetingViaJs` render component to the
61+ // Create a virtual DOM and mount it and the `Hello` render component to the
58 // `<body>`.
62 // `<body>`.
59 let vdom = Vdom::new(body.as_ref(), component);
63 let vdom = Vdom::new(body.as_ref(), component);
60
64
examples/moire/Cargo.toml+9−8
@@ -8,19 +8,20 @@ edition = "2018"
8crate-type = ["cdylib"]
8crate-type = ["cdylib"]
9
9
10[dependencies]
10[dependencies]
11−cfg-if = "0.1.10"
11+cfg-if = "0.1.7"
12dodrio = { path = "../.." }
12dodrio = { path = "../.." }
13+futures = "0.1.25"
13−log = "0.4.11"
14+log = "0.4.6"
14−wasm-bindgen = "0.2.55"
15+wasm-bindgen = "0.2.37"
15−wasm-bindgen-futures = "0.4.5"
16+wasm-bindgen-futures = "0.3.14"
16
17
17# Optional dependencies for logging.
18# Optional dependencies for logging.
18console_error_panic_hook = { optional = true, version = "0.1.6" }
19console_error_panic_hook = { optional = true, version = "0.1.6" }
19−console_log = { optional = true, version = "0.2.0" }
20+console_log = { optional = true, version = "0.1.2" }
20−js-sys = "0.3.32"
21+js-sys = "0.3.14"
21
22
22[dependencies.web-sys]
23[dependencies.web-sys]
23−version = "0.3.32"
24+version = "0.3.14"
24features = [
25features = [
25 "Document",
26 "Document",
26 "Element",
27 "Element",
@@ -34,7 +35,7 @@ features = [
34]
35]
35
36
36[dev-dependencies]
37[dev-dependencies]
37−wasm-bindgen-test = "0.3.5"
38+wasm-bindgen-test = "0.2.37"
38
39
39[features]
40[features]
40logging = [
41logging = [
examples/moire/README.md+1−1
@@ -14,7 +14,7 @@ conversion and other color utilities.
14## Build
14## Build
15
15
16```
16```
17−wasm-pack build --target web
17+wasm-pack build --target no-modules
18```
18```
19
19
20## Serve
20## Serve
examples/moire/index.html+3−3
@@ -42,9 +42,9 @@
42 <body>
42 <body>
43 <code>FPS: <output id="fps"></code></code>
43 <code>FPS: <output id="fps"></code></code>
44 <div id="scene"></div>
44 <div id="scene"></div>
45− <script type="module">
46− import init from "./pkg/dodrio_moire.js";
45+ <script src="pkg/dodrio_moire.js"></script>
46+ <script>
47− init("./pkg/dodrio_moire_bg.wasm");
47+ wasm_bindgen("pkg/dodrio_moire_bg.wasm");
48 </script>
48 </script>
49 <script>
49 <script>
50 let lastTime = 0
50 let lastTime = 0
examples/moire/src/lib.rs+64−38
@@ -1,11 +1,13 @@
1mod colors;
1mod colors;
2
2
3use cfg_if::cfg_if;
3use cfg_if::cfg_if;
4−use dodrio::{bumpalo, Node, Render, RenderContext, Vdom};
4+use dodrio::bumpalo::{self, Bump};
5+use dodrio::{Node, Render, Vdom};
5use std::cell::RefCell;
6use std::cell::RefCell;
6use std::rc::Rc;
7use std::rc::Rc;
7use std::str;
8use std::str;
8−use wasm_bindgen::{prelude::*, JsCast};
9+use wasm_bindgen::prelude::*;
10+use wasm_bindgen::JsCast;
9
11
10/// This is the main function that is automatically invoked when the wasm module
12/// This is the main function that is automatically invoked when the wasm module
11/// is loaded.
13/// is loaded.
@@ -22,12 +24,12 @@ pub fn run() {
22 // Create a new `Moire` render component.
24 // Create a new `Moire` render component.
23 let component = Moire::new();
25 let component = Moire::new();
24
26
25− // Create a virtual DOM and mount it and the `Moire` render component to the
27+ // Create a virtual DOM and mount it and the `Hello` render component to the
26 // scene.
28 // scene.
27 let vdom = Vdom::new(&scene, component);
29 let vdom = Vdom::new(&scene, component);
28
30
29 // Kick off a loop that keeps re-rendering on every animation frame.
31 // Kick off a loop that keeps re-rendering on every animation frame.
30− let rc = <Rc<RefCell<Option<Closure<dyn FnMut()>>>>>::default();
32+ let rc: Rc<RefCell<Option<Closure<FnMut()>>>> = Rc::new(RefCell::new(None));
31 let rc2 = rc.clone();
33 let rc2 = rc.clone();
32 let window2 = window.clone();
34 let window2 = window.clone();
33 let weak = vdom.weak();
35 let weak = vdom.weak();
@@ -42,7 +44,7 @@ pub fn run() {
42 .unchecked_ref::<js_sys::Function>(),
44 .unchecked_ref::<js_sys::Function>(),
43 )
45 )
44 .unwrap_throw();
46 .unwrap_throw();
45− }) as Box<dyn FnMut()>);
47+ }) as Box<FnMut()>);
46 window2
48 window2
47 .request_animation_frame(f.as_ref().unchecked_ref::<js_sys::Function>())
49 .request_animation_frame(f.as_ref().unchecked_ref::<js_sys::Function>())
48 .unwrap_throw();
50 .unwrap_throw();
@@ -104,8 +106,11 @@ impl Moire {
104 }
106 }
105}
107}
106
108
107−impl<'a> Render<'a> for Moire {
109+impl Render for Moire {
108− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
110+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
111+ where
112+ 'a: 'bump,
113+ {
109 use dodrio::builder::*;
114 use dodrio::builder::*;
110
115
111 let elapsed = web_sys::window()
116 let elapsed = web_sys::window()
@@ -115,18 +120,18 @@ impl<'a> Render<'a> for Moire {
115 .now();
120 .now();
116 let elapsed = elapsed / 1600.0;
121 let elapsed = elapsed / 1600.0;
117
122
118− main(&cx)
123+ main(bump)
119− .attr("style", self.moire_style(cx, elapsed))
124+ .attr("style", self.moire_style(bump, elapsed))
120 .children([
125 .children([
121 // The `<input>` that lets users control how many circles to
126 // The `<input>` that lets users control how many circles to
122 // render.
127 // render.
123− input(&cx)
128+ input(bump)
124 .attr("id", "circle-count")
129 .attr("id", "circle-count")
125 .attr("type", "range")
130 .attr("type", "range")
126 .attr("min", "30")
131 .attr("min", "30")
127 .attr("max", "500")
132 .attr("max", "500")
128 .attr("value", {
133 .attr("value", {
129− let count = bumpalo::format!(in cx.bump, "{}", self.count);
134+ let count = bumpalo::format!(in bump, "{}", self.count);
130 count.into_bump_str()
135 count.into_bump_str()
131 })
136 })
132 .on("change", |root, _vdom, event| {
137 .on("change", |root, _vdom, event| {
@@ -139,8 +144,8 @@ impl<'a> Render<'a> for Moire {
139 })
144 })
140 .finish(),
145 .finish(),
141 // And the actual concentric circles that form the Moire patterns.
146 // And the actual concentric circles that form the Moire patterns.
142− self.orbiting_objects(cx, elapsed),
147+ self.orbiting_objects(bump, elapsed),
143− self.lemniscate_objects(cx, elapsed),
148+ self.lemniscate_objects(bump, elapsed),
144 ])
149 ])
145 .finish()
150 .finish()
146 }
151 }
@@ -149,11 +154,14 @@ impl<'a> Render<'a> for Moire {
149/// Rendering helper methods.
154/// Rendering helper methods.
150impl Moire {
155impl Moire {
151 /// Generate the main Moire element's inline CSS styles.
156 /// Generate the main Moire element's inline CSS styles.
152− fn moire_style<'a>(&self, cx: &mut RenderContext<'a>, elapsed: f64) -> &'a str {
157+ fn moire_style<'a, 'bump>(&'a self, bump: &'bump Bump, elapsed: f64) -> &'bump str
158+ where
159+ 'a: 'bump,
160+ {
153 let elapsed = elapsed / 3.0;
161 let elapsed = elapsed / 3.0;
154 let color = colors::get_interpolated_color(|(_, bg)| bg, elapsed % 1.0);
162 let color = colors::get_interpolated_color(|(_, bg)| bg, elapsed % 1.0);
155 let style = bumpalo::format!(
163 let style = bumpalo::format!(
156− in cx.bump,
164+ in bump,
157 "background-color: rgb({}, {}, {})",
165 "background-color: rgb({}, {}, {})",
158 color.r,
166 color.r,
159 color.g,
167 color.g,
@@ -163,75 +171,93 @@ impl Moire {
163 }
171 }
164
172
165 /// Generate the orbiting circle objects.
173 /// Generate the orbiting circle objects.
166− fn orbiting_objects<'a>(&self, cx: &mut RenderContext<'a>, elapsed: f64) -> Node<'a> {
174+ fn orbiting_objects<'a, 'bump>(&'a self, bump: &'bump Bump, elapsed: f64) -> Node<'bump>
175+ where
176+ 'a: 'bump,
177+ {
167 const D: f64 = 200.0;
178 const D: f64 = 200.0;
168 let x = elapsed.sin() * D;
179 let x = elapsed.sin() * D;
169 let y = elapsed.cos() * D;
180 let y = elapsed.cos() * D;
170− self.moving_object(cx, elapsed, x, y)
181+ self.moving_object(bump, elapsed, x, y)
171 }
182 }
172
183
173 /// Generate the lemniscate circle objects that interact with the orbiting
184 /// Generate the lemniscate circle objects that interact with the orbiting
174 /// circle objects to form Moire patterns.
185 /// circle objects to form Moire patterns.
175− fn lemniscate_objects<'a>(&self, cx: &mut RenderContext<'a>, elapsed: f64) -> Node<'a> {
186+ fn lemniscate_objects<'a, 'bump>(&'a self, bump: &'bump Bump, elapsed: f64) -> Node<'bump>
187+ where
188+ 'a: 'bump,
189+ {
176 const A: f64 = 150.0;
190 const A: f64 = 150.0;
177 let x = elapsed.sin() * A;
191 let x = elapsed.sin() * A;
178 let y = elapsed.sin() * elapsed.cos() * A;
192 let y = elapsed.sin() * elapsed.cos() * A;
179− self.moving_object(cx, elapsed, x, y)
193+ self.moving_object(bump, elapsed, x, y)
180 }
194 }
181
195
182− fn moving_object<'a>(
196+ fn moving_object<'a, 'bump>(
183− &self,
197+ &'a self,
184− cx: &mut RenderContext<'a>,
198+ bump: &'bump Bump,
185 elapsed: f64,
199 elapsed: f64,
186 x: f64,
200 x: f64,
187 y: f64,
201 y: f64,
188− ) -> Node<'a> {
202+ ) -> Node<'bump>
203+ where
204+ 'a: 'bump,
205+ {
189 use dodrio::builder::*;
206 use dodrio::builder::*;
190− div(&cx)
207+ div(bump)
191 .attr("class", "object")
208 .attr("class", "object")
192− .attr("style", self.moving_object_style(cx, x, y))
209+ .attr("style", self.moving_object_style(bump, x, y))
193− .children([self.circle(cx, elapsed, self.count)])
210+ .children([self.circle(bump, elapsed, self.count)])
194 .finish()
211 .finish()
195 }
212 }
196
213
197 /// Generate inline CSS styles for a moving object.
214 /// Generate inline CSS styles for a moving object.
198− fn moving_object_style<'a>(&self, cx: &mut RenderContext<'a>, x: f64, y: f64) -> &'a str {
215+ fn moving_object_style<'a, 'bump>(&'a self, bump: &'bump Bump, x: f64, y: f64) -> &'bump str
216+ where
217+ 'a: 'bump,
218+ {
199− bumpalo::format!(in cx.bump, "left: {}px; top: {}px;", x, y).into_bump_str()
219+ bumpalo::format!(in bump, "left: {}px; top: {}px;", x, y).into_bump_str()
200 }
220 }
201
221
202 /// Recursively generate `self.count` concentric circles.
222 /// Recursively generate `self.count` concentric circles.
203− fn circle<'a>(&self, cx: &mut RenderContext<'a>, elapsed: f64, n: u32) -> Node<'a> {
223+ fn circle<'a, 'bump>(&'a self, bump: &'bump Bump, elapsed: f64, n: u32) -> Node<'bump>
224+ where
225+ 'a: 'bump,
226+ {
204 use dodrio::builder::*;
227 use dodrio::builder::*;
205
228
206 let r = n * 16;
229 let r = n * 16;
207
230
208− let mut circle = div(&cx)
231+ let mut circle = div(bump)
209 .attr("class", "circle")
232 .attr("class", "circle")
210 .attr("data-radius", {
233 .attr("data-radius", {
211− let r = bumpalo::format!(in cx.bump, "{}", r);
234+ let r = bumpalo::format!(in bump, "{}", r);
212 r.into_bump_str()
235 r.into_bump_str()
213 })
236 })
214− .attr("style", self.circle_style(cx, elapsed, n, r));
237+ .attr("style", self.circle_style(bump, elapsed, n, r));
215
238
216 if n > 0 {
239 if n > 0 {
217− circle = circle.child(self.circle(cx, elapsed, n - 1));
240+ circle = circle.child(self.circle(bump, elapsed, n - 1));
218 }
241 }
219
242
220 circle.finish()
243 circle.finish()
221 }
244 }
222
245
223 /// Generate inline CSS styles for a circle.
246 /// Generate inline CSS styles for a circle.
224− fn circle_style<'a>(
247+ fn circle_style<'a, 'bump>(
225− &self,
248+ &'a self,
226− cx: &mut RenderContext<'a>,
249+ bump: &'bump Bump,
227 elapsed: f64,
250 elapsed: f64,
228 n: u32,
251 n: u32,
229 r: u32,
252 r: u32,
230− ) -> &'a str {
253+ ) -> &'bump str
254+ where
255+ 'a: 'bump,
256+ {
231 let (border, alpha) = self.circle_color(elapsed, n);
257 let (border, alpha) = self.circle_color(elapsed, n);
232 let margin = r / 2 + 3;
258 let margin = r / 2 + 3;
233 let style = bumpalo::format!(
259 let style = bumpalo::format!(
234− in cx.bump,
260+ in bump,
235 "border-color: rgba({}, {}, {}, {});\
261 "border-color: rgba({}, {}, {}, {});\
236 margin-left: -{}px;\
262 margin-left: -{}px;\
237 margin-top: -{}px;\
263 margin-top: -{}px;\
examples/sierpinski-triangle/Cargo.tomldeleted−28
@@ -1,28 +0,0 @@
1−[package]
2−name = "dodrio-sierpinski"
3−version = "0.1.0"
4−authors = ["Steven Seguin <steven.seguin@gmail.com>"]
5−edition = "2018"
6−
7−[lib]
8−crate-type = ["cdylib"]
9−
10−[dependencies]
11−dodrio = { path = "../.." }
12−wasm-bindgen = "0.2.55"
13−console_error_panic_hook = "0.1.6"
14−js-sys = "0.3.32"
15−wasm-bindgen-futures = "0.4.5"
16−
17−[dependencies.web-sys]
18−version = "0.3.32"
19−features = [
20− "Document",
21− "HtmlElement",
22− "Node",
23− "Performance",
24− "Window",
25−]
26−
27−[dev-dependencies]
28−wasm-bindgen-test = "0.3.5"
examples/sierpinski-triangle/README.mddeleted−24
@@ -1,24 +0,0 @@
1−# Sierpinski Triangle
2−
3−A Sierpinkski triangle that constantly shrinks and grows, and whose nodes have
4−a value that increments by one every second.
5−
6−Ported from the React Fiber example [here](https://github.com/claudiopro/react-fiber-vs-stack-demo).
7−
8−## Source
9−
10−See `src/lib.rs`.
11−
12−## Build
13−
14−```
15−wasm-pack build --target web
16−```
17−
18−## Serve
19−
20−Use any HTTP server, for example:
21−
22−```
23−python -m SimpleHTTPServer
24−```
examples/sierpinski-triangle/ferris.pngdeleted

Binary file not shown.

examples/sierpinski-triangle/index.htmldeleted−69
@@ -1,69 +0,0 @@
1−<!DOCTYPE html>
2−<html>
3−
4−<head>
5− <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
6− <title>Sierpinski Triangle</title>
7− <style>
8− .container {
9− position: absolute;
10− transform-origin: 0 0;
11− left: 50%;
12− top: 50%;
13− width: 10px;
14− height: 10px;
15− background: #eee;
16− }
17−
18− .dot {
19− position: absolute;
20− background: #61dafb;
21− font: normal 15px sans-serif;
22− text-align: center;
23− cursor: pointer;
24− }
25−
26− .dot:hover {
27− background: url("ferris.png") no-repeat center center;
28− z-index: 999;
29− }
30−
31− code {
32− position: relative;
33− font-size: 150%;
34− }
35− </style>
36−</head>
37−
38−<body>
39− <code>FPS: <output id="fps">...</output></code>
40− <div id="scene"></div>
41−
42− <script type="module">
43− import init from "./pkg/dodrio_sierpinski.js";
44− init("./pkg/dodrio_sierpinski_bg.wasm");
45− </script>
46−
47− <script>
48− let lastTime = 0;
49− let frameCount = 0;
50− const fps = document.getElementById('fps');
51−
52− function updateFPS(time) {
53− frameCount++;
54− if (lastTime + 1000.0 <= time) {
55− fps.textContent = frameCount;
56− lastTime = time;
57− frameCount = 0;
58− }
59− }
60−
61− requestAnimationFrame(function loop(now) {
62− updateFPS(now);
63− requestAnimationFrame(loop);
64− })
65−
66− </script>
67−</body>
68−
69−</html>
examples/sierpinski-triangle/src/lib.rsdeleted−157
@@ -1,157 +0,0 @@
1−use dodrio::{bumpalo, Node, Render, RenderContext, Vdom};
2−use std::cell::RefCell;
3−use std::rc::Rc;
4−use wasm_bindgen::{prelude::*, JsCast};
5−use web_sys::Window;
6−
7−// Triangle container defining target size.
8−struct Container {
9− size: f64,
10−}
11−
12−impl Container {
13− // Construct a new container.
14− pub fn new() -> Container {
15− Container { size: 25.0 }
16− }
17−
18− // Generate the container style to fluctuate triangle width.
19− fn container_transform<'a>(&self, cx: &mut RenderContext<'a>, elapsed: f64) -> &'a str {
20− let t = elapsed % 10.0;
21− let scale = 0.45 + (if t > 5.0 { 10.0 - t } else { t }) / 40.0;
22− let transform = bumpalo::format!(
23− in cx.bump,
24− "transform: scaleX({}) scaleY(0.7) translateZ(0.1px)",
25− scale
26− );
27− transform.into_bump_str()
28− }
29−
30− // Generate the dot's position on the grid.
31− fn dot_style<'a>(&self, cx: &mut RenderContext<'a>, x: f64, y: f64) -> &'a str {
32− let s = self.size * 1.3;
33− let radius = s / 2.0;
34− let styles = bumpalo::format!(
35− in cx.bump,
36− r#"
37− width: {}px;
38− height: {}px;
39− left: {}px;
40− top: {}px;
41− border-radius: {}px;
42− line-height: {}px;
43− "#,
44− s, s, x, y, radius, s
45− );
46− styles.into_bump_str()
47− }
48−
49− // Create a dot node.
50− fn dot<'a>(&self, cx: &mut RenderContext<'a>, x: f64, y: f64, content: u32) -> Node<'a> {
51− use dodrio::builder::{div, text};
52−
53− div(&cx)
54− .attr("class", "dot")
55− .attr("style", self.dot_style(cx, x, y))
56− .child(text(
57− bumpalo::format!(in cx.bump, "{}", content).into_bump_str(),
58− ))
59− .finish()
60− }
61−
62− // Create a flattened vector of dot nodes (arranged in recursive triangles)
63− fn triangle<'a>(
64− &self,
65− cx: &mut RenderContext<'a>,
66− x: f64,
67− y: f64,
68− s: f64,
69− content: u32,
70− children: &mut bumpalo::collections::Vec<Node<'a>>,
71− ) {
72− if s <= self.size {
73− children.push(self.dot(cx, x, y, content));
74− return;
75− }
76−
77− let s = s / 2.0;
78−
79− // Top of triangle
80− self.triangle(cx, x, y - (s / 2.0), s, content, children);
81− // Bottom-left of triangle
82− self.triangle(cx, x - s, y + (s / 2.0), s, content, children);
83− // Bottom-right of triangle
84− self.triangle(cx, x + s, y + (s / 2.0), s, content, children);
85− }
86−}
87−
88−impl<'a> Render<'a> for Container {
89− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
90− use dodrio::builder::div;
91−
92− let elapsed = web_sys::window()
93− .unwrap_throw()
94− .performance()
95− .unwrap_throw()
96− .now()
97− / 1000.0;
98−
99− let modulus = elapsed as u32 % 10;
100−
101− let mut children = bumpalo::collections::Vec::new_in(cx.bump);
102− self.triangle(cx, 0.0, 0.0, 1000.0, modulus, &mut children);
103−
104− div(&cx)
105− .attr("class", "container")
106− .attr("style", self.container_transform(cx, elapsed))
107− .children(children)
108− .finish()
109− }
110−}
111−
112−// Kick off a loop that keeps re-rendering on every animation frame.
113−fn animate(window: Window, vdom: Vdom) {
114− let rc = <Rc<RefCell<Option<Closure<dyn FnMut()>>>>>::default();
115− let rc2 = rc.clone();
116− let window2 = window.clone();
117− let weak = vdom.weak();
118− let f = Closure::wrap(Box::new(move || {
119− weak.schedule_render();
120− window
121− .request_animation_frame(
122− rc.borrow()
123− .as_ref()
124− .unwrap_throw()
125− .as_ref()
126− .unchecked_ref::<js_sys::Function>(),
127− )
128− .unwrap_throw();
129− }) as Box<dyn FnMut()>);
130− window2
131− .request_animation_frame(f.as_ref().unchecked_ref::<js_sys::Function>())
132− .unwrap_throw();
133− *rc2.borrow_mut() = Some(f);
134−
135− // Run the virtual DOM and its listeners forever.
136− vdom.forget();
137−}
138−
139−#[wasm_bindgen(start)]
140−pub fn run() {
141− // Set up the panic hook
142− console_error_panic_hook::set_once();
143−
144− // Get the scene element to render within
145− let window = web_sys::window().unwrap();
146− let document = window.document().unwrap();
147− let scene = document.get_element_by_id("scene").unwrap();
148−
149− // Construct a new Container component.
150− let container = Container::new();
151−
152− // Mount our container component to the scene div.
153− let vdom = Vdom::new(&scene, container);
154−
155− // Kick off animation loop
156− animate(window, vdom);
157−}
examples/todomvc/Cargo.toml+12−11
@@ -5,21 +5,22 @@ name = "dodrio-todomvc"
5version = "0.1.0"
5version = "0.1.0"
6
6
7[dependencies]
7[dependencies]
8−cfg-if = "0.1.10"
8+cfg-if = "0.1.7"
9−dodrio = { path = "../..", features = ["serde"] }
9+dodrio = { path = "../.." }
10+futures = "0.1.25"
10−js-sys = "0.3.32"
11+js-sys = "0.3.14"
11−serde = { features = ["derive"], version = "1.0.114" }
12+serde = { features = ["derive"], version = "1.0.89" }
12−serde_json = "1.0.57"
13+serde_json = "1.0.39"
13−wasm-bindgen = "0.2.55"
14+wasm-bindgen = "0.2.37"
14−wasm-bindgen-futures = "0.4.5"
15+wasm-bindgen-futures = "0.3.14"
15
16
16# Optional dependencies for logging.
17# Optional dependencies for logging.
17console_error_panic_hook = { optional = true, version = "0.1.6" }
18console_error_panic_hook = { optional = true, version = "0.1.6" }
18−console_log = { optional = true, version = "0.2.0" }
19+console_log = { optional = true, version = "0.1.2" }
19−log = { optional = true, version = "0.4.11" }
20+log = { optional = true, version = "0.4.6" }
20
21
21[dependencies.web-sys]
22[dependencies.web-sys]
22−version = "0.3.32"
23+version = "0.3.14"
23features = [
24features = [
24 "Document",
25 "Document",
25 "Event",
26 "Event",
@@ -34,7 +35,7 @@ features = [
34]
35]
35
36
36[dev-dependencies]
37[dev-dependencies]
37−wasm-bindgen-test = "0.3.5"
38+wasm-bindgen-test = "0.2.37"
38
39
39[features]
40[features]
40logging = [
41logging = [
examples/todomvc/README.md+1−1
@@ -21,7 +21,7 @@ most important are:
21## Build
21## Build
22
22
23```
23```
24−wasm-pack build --target web
24+wasm-pack build --target no-modules
25```
25```
26
26
27## Serve
27## Serve
examples/todomvc/index.html+3−3
@@ -13,9 +13,9 @@
13 <footer class="info">
13 <footer class="info">
14 <p>Double-click to edit a todo</p>
14 <p>Double-click to edit a todo</p>
15 </footer>
15 </footer>
16− <script type="module">
17− import init from "./pkg/dodrio_todomvc.js";
16+ <script src="pkg/dodrio_todomvc.js"></script>
17+ <script>
18− init("./pkg/dodrio_todomvc_bg.wasm");
18+ wasm_bindgen("pkg/dodrio_todomvc_bg.wasm");
19 </script>
19 </script>
20 </body>
20 </body>
21</html>
21</html>
examples/todomvc/src/router.rsmode 100755 → 100644+21−23
@@ -4,8 +4,10 @@ use crate::todos::Todos;
4use crate::utils;
4use crate::utils;
5use crate::visibility::Visibility;
5use crate::visibility::Visibility;
6use dodrio::VdomWeak;
6use dodrio::VdomWeak;
7+use futures::prelude::*;
7use std::str::FromStr;
8use std::str::FromStr;
8−use wasm_bindgen::{prelude::*, JsCast};
9+use wasm_bindgen::prelude::*;
10+use wasm_bindgen::JsCast;
9
11
10/// Start the router.
12/// Start the router.
11pub fn start(vdom: VdomWeak) {
13pub fn start(vdom: VdomWeak) {
@@ -28,27 +30,23 @@ pub fn start(vdom: VdomWeak) {
28 v
30 v
29 });
31 });
30
32
31− wasm_bindgen_futures::spawn_local({
33+ wasm_bindgen_futures::spawn_local(
32− let vdom = vdom.clone();
33− async move {
34− let _ = vdom
35− .with_component({
34+ vdom.with_component({
36− let vdom = vdom.clone();
35+ let vdom = vdom.clone();
37− move |root| {
36+ move |root| {
38− let todos = root.unwrap_mut::<Todos>();
37+ let todos = root.unwrap_mut::<Todos>();
39− // If the todos' visibility already matches the event's
38+ // If the todos' visibility already matches the event's
40− // visibility, then there is nothing to do (ha). If they
39+ // visibility, then there is nothing to do (ha). If they
41− // don't match, then we need to update the todos' visibility
40+ // don't match, then we need to update the todos' visibility
42− // and re-render.
41+ // and re-render.
43− if todos.visibility() != new_vis {
42+ if todos.visibility() != new_vis {
44− todos.set_visibility(new_vis);
43+ todos.set_visibility(new_vis);
45− vdom.schedule_render();
44+ vdom.schedule_render();
46− }
45+ }
47− }
46+ }
48− })
47+ })
49− .await;
48+ .map_err(|_| ()),
50− }
51− });
49+ );
52 };
50 };
53
51
54 // Call it once to handle the initial `#` fragment.
52 // Call it once to handle the initial `#` fragment.
@@ -59,7 +57,7 @@ pub fn start(vdom: VdomWeak) {
59 // Note that if we ever intended to unmount our todos app, we would want to
57 // Note that if we ever intended to unmount our todos app, we would want to
60 // provide a method for removing this router's event listener and cleaning
58 // provide a method for removing this router's event listener and cleaning
61 // up after ourselves.
59 // up after ourselves.
62− let on_hash_change = Closure::wrap(Box::new(on_hash_change) as Box<dyn FnMut()>);
60+ let on_hash_change = Closure::wrap(Box::new(on_hash_change) as Box<FnMut()>);
63 let window = utils::window();
61 let window = utils::window();
64 window
62 window
65 .add_event_listener_with_callback("hashchange", on_hash_change.as_ref().unchecked_ref())
63 .add_event_listener_with_callback("hashchange", on_hash_change.as_ref().unchecked_ref())
examples/todomvc/src/todo.rs+32−86
@@ -1,19 +1,14 @@
1//! Type definition and `dodrio::Render` implementation for a single todo item.
1//! Type definition and `dodrio::Render` implementation for a single todo item.
2
2
3use crate::keys;
3use crate::keys;
4−use dodrio::{Cached, Node, Render, RenderContext, RootRender, VdomWeak};
4+use dodrio::{bumpalo::Bump, Node, Render, RootRender, VdomWeak};
5use serde::{Deserialize, Serialize};
5use serde::{Deserialize, Serialize};
6use std::marker::PhantomData;
6use std::marker::PhantomData;
7use wasm_bindgen::{prelude::*, JsCast};
7use wasm_bindgen::{prelude::*, JsCast};
8
8
9/// A single todo item.
9/// A single todo item.
10−#[derive(Default)]
11−pub struct Todo<C> {
12− inner: Cached<TodoInner<C>>,
13−}
14−
15#[derive(Serialize, Deserialize)]
10#[derive(Serialize, Deserialize)]
16−struct TodoInner<C> {
11+pub struct Todo<C> {
17 id: usize,
12 id: usize,
18 title: String,
13 title: String,
19 completed: bool,
14 completed: bool,
@@ -53,110 +48,93 @@ impl<C> Todo<C> {
53 let completed = false;
48 let completed = false;
54 let edits = None;
49 let edits = None;
55 Todo {
50 Todo {
56− inner: Cached::new(TodoInner {
57− id,
51+ id,
58− title,
52+ title,
59− completed,
53+ completed,
60− edits,
54+ edits,
61− _controller: PhantomData,
55+ _controller: PhantomData,
62− }),
63 }
56 }
64 }
57 }
65
58
66 /// Set this todo item's id.
59 /// Set this todo item's id.
67 pub fn set_id(&mut self, id: usize) {
60 pub fn set_id(&mut self, id: usize) {
68− if id != self.inner.id {
69− Cached::invalidate(&self.inner);
70− }
71− self.inner.id = id;
61+ self.id = id;
72 }
62 }
73
63
74 /// Is this `Todo` complete?
64 /// Is this `Todo` complete?
75 pub fn is_complete(&self) -> bool {
65 pub fn is_complete(&self) -> bool {
76− self.inner.completed
66+ self.completed
77 }
67 }
78
68
79 /// Mark the `Todo` as complete or not.
69 /// Mark the `Todo` as complete or not.
80 pub fn set_complete(&mut self, to: bool) {
70 pub fn set_complete(&mut self, to: bool) {
81− if to != self.inner.completed {
82− Cached::invalidate(&self.inner);
83− }
84− self.inner.completed = to;
71+ self.completed = to;
85 }
72 }
86
73
87 /// Get this todo's title.
74 /// Get this todo's title.
88 pub fn title(&self) -> &str {
75 pub fn title(&self) -> &str {
89− &self.inner.title
76+ &self.title
90 }
77 }
91
78
92 /// Set this todo item's title.
79 /// Set this todo item's title.
93 pub fn set_title<S: Into<String>>(&mut self, title: S) {
80 pub fn set_title<S: Into<String>>(&mut self, title: S) {
94− let title = title.into();
81+ self.title = title.into();
95− if title != self.inner.title {
96− Cached::invalidate(&self.inner);
97− }
98− self.inner.title = title;
99 }
82 }
100
83
101 /// Set the edits for this todo.
84 /// Set the edits for this todo.
102 pub fn set_edits<S: Into<String>>(&mut self, edits: Option<S>) {
85 pub fn set_edits<S: Into<String>>(&mut self, edits: Option<S>) {
103− let edits = edits.map(Into::into);
86+ self.edits = edits.map(Into::into);
104− if edits != self.inner.edits {
105− Cached::invalidate(&self.inner);
106− }
107− self.inner.edits = edits;
108 }
87 }
109
88
110 /// Take this todo's edits, leaving `None` in their place.
89 /// Take this todo's edits, leaving `None` in their place.
111 pub fn take_edits(&mut self) -> Option<String> {
90 pub fn take_edits(&mut self) -> Option<String> {
112− if self.inner.edits.is_some() {
113− Cached::invalidate(&self.inner);
114− }
115− self.inner.edits.take()
91+ self.edits.take()
116 }
92 }
117}
93}
118
94
119−impl<'a, C: TodoActions> Render<'a> for Todo<C> {
95+impl<C: TodoActions> Render for Todo<C> {
120− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
96+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
97+ where
98+ 'a: 'bump,
99+ {
121 use dodrio::{
100 use dodrio::{
122 builder::*,
101 builder::*,
123 bumpalo::{self, collections::String},
102 bumpalo::{self, collections::String},
124 };
103 };
125
104
126− let id = self.inner.id;
105+ let id = self.id;
127− let title = self.inner.edits.as_ref().unwrap_or(&self.inner.title);
106+ let title = self.edits.as_ref().unwrap_or(&self.title);
128− let title = bumpalo::format!(in cx.bump, "{}", title).into_bump_str();
129
107
130− li(&cx)
108+ li(bump)
131 .attr("class", {
109 .attr("class", {
132− let mut class = String::new_in(cx.bump);
110+ let mut class = String::new_in(bump);
133− if self.inner.completed {
111+ if self.completed {
134 class.push_str("completed ");
112 class.push_str("completed ");
135 }
113 }
136− if self.inner.edits.is_some() {
114+ if self.edits.is_some() {
137 class.push_str("editing");
115 class.push_str("editing");
138 }
116 }
139 class.into_bump_str()
117 class.into_bump_str()
140 })
118 })
141 .children([
119 .children([
142− div(&cx)
120+ div(bump)
143 .attr("class", "view")
121 .attr("class", "view")
144 .children([
122 .children([
145− input(&cx)
123+ input(bump)
146 .attr("class", "toggle")
124 .attr("class", "toggle")
147 .attr("type", "checkbox")
125 .attr("type", "checkbox")
148− .bool_attr("checked", self.inner.completed)
126+ .bool_attr("checked", self.completed)
149 .on("click", move |root, vdom, _event| {
127 .on("click", move |root, vdom, _event| {
150 C::toggle_completed(root, vdom, id);
128 C::toggle_completed(root, vdom, id);
151 })
129 })
152 .finish(),
130 .finish(),
153− label(&cx)
131+ label(bump)
154 .on("dblclick", move |root, vdom, _event| {
132 .on("dblclick", move |root, vdom, _event| {
155 C::begin_editing(root, vdom, id);
133 C::begin_editing(root, vdom, id);
156 })
134 })
157 .children([text(title)])
135 .children([text(title)])
158 .finish(),
136 .finish(),
159− button(&cx)
137+ button(bump)
160 .attr("class", "destroy")
138 .attr("class", "destroy")
161 .on("click", move |root, vdom, _event| {
139 .on("click", move |root, vdom, _event| {
162 C::delete(root, vdom, id);
140 C::delete(root, vdom, id);
@@ -164,13 +142,13 @@ impl<'a, C: TodoActions> Render<'a> for Todo<C> {
164 .finish(),
142 .finish(),
165 ])
143 ])
166 .finish(),
144 .finish(),
167− input(&cx)
145+ input(bump)
168 .attr("class", "edit")
146 .attr("class", "edit")
169 .attr("value", title)
147 .attr("value", title)
170 .attr("name", "title")
148 .attr("name", "title")
171 .attr(
149 .attr(
172 "id",
150 "id",
173− bumpalo::format!(in cx.bump, "todo-{}", id).into_bump_str(),
151+ bumpalo::format!(in bump, "todo-{}", id).into_bump_str(),
174 )
152 )
175 .on("input", move |root, vdom, event| {
153 .on("input", move |root, vdom, event| {
176 let input = event
154 let input = event
@@ -195,35 +173,3 @@ impl<'a, C: TodoActions> Render<'a> for Todo<C> {
195 .finish()
173 .finish()
196 }
174 }
197}
175}
198−
199−impl<C> Default for TodoInner<C> {
200− fn default() -> Self {
201− TodoInner {
202− id: Default::default(),
203− title: Default::default(),
204− completed: Default::default(),
205− edits: Default::default(),
206− _controller: PhantomData,
207− }
208− }
209−}
210−
211−impl<C> serde::Serialize for Todo<C> {
212− fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
213− where
214− S: serde::Serializer,
215− {
216− self.inner.serialize(serializer)
217− }
218−}
219−
220−impl<'de, C> Deserialize<'de> for Todo<C> {
221− fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
222− where
223− D: serde::Deserializer<'de>,
224− {
225− Ok(Todo {
226− inner: <Cached<TodoInner<C>>>::deserialize(deserializer)?,
227− })
228− }
229−}
examples/todomvc/src/todos.rsmode 100755 → 100644+56−40
@@ -5,11 +5,15 @@ use crate::controller::Controller;
5use crate::todo::{Todo, TodoActions};
5use crate::todo::{Todo, TodoActions};
6use crate::visibility::Visibility;
6use crate::visibility::Visibility;
7use crate::{keys, utils};
7use crate::{keys, utils};
8+use dodrio::{
9+ bumpalo::{self, Bump},
8−use dodrio::{bumpalo, Node, Render, RenderContext, RootRender, VdomWeak};
10+ Node, Render, RootRender, VdomWeak,
11+};
9use serde::{Deserialize, Serialize};
12use serde::{Deserialize, Serialize};
10use std::marker::PhantomData;
13use std::marker::PhantomData;
11use std::mem;
14use std::mem;
12−use wasm_bindgen::{prelude::*, JsCast};
15+use wasm_bindgen::prelude::*;
16+use wasm_bindgen::JsCast;
13
17
14/// A collection of todos.
18/// A collection of todos.
15#[derive(Default, Serialize, Deserialize)]
19#[derive(Default, Serialize, Deserialize)]
@@ -28,7 +32,7 @@ pub struct Todos<C = Controller> {
28}
32}
29
33
30/// Actions for `Todos` that can be triggered by UI interactions.
34/// Actions for `Todos` that can be triggered by UI interactions.
31−pub trait TodosActions: 'static + TodoActions {
35+pub trait TodosActions: TodoActions {
32 /// Toggle the completion state of all todo items.
36 /// Toggle the completion state of all todo items.
33 fn toggle_all(root: &mut dyn RootRender, vdom: VdomWeak);
37 fn toggle_all(root: &mut dyn RootRender, vdom: VdomWeak);
34
38
@@ -49,7 +53,7 @@ pub trait TodosActions: 'static + TodoActions {
49impl<C> Todos<C> {
53impl<C> Todos<C> {
50 /// Construct a new todos set.
54 /// Construct a new todos set.
51 ///
55 ///
52− /// If an existing set is available in local storage, then use that,
56+ /// If an existing set is available in local storage, then us that,
53 /// otherwise create a new set.
57 /// otherwise create a new set.
54 pub fn new() -> Self
58 pub fn new() -> Self
55 where
59 where
@@ -132,14 +136,17 @@ impl<C> Todos<C> {
132
136
133/// Rendering helpers.
137/// Rendering helpers.
134impl<C: TodosActions> Todos<C> {
138impl<C: TodosActions> Todos<C> {
135− fn header<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
139+ fn header<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
140+ where
141+ 'a: 'bump,
142+ {
136 use dodrio::builder::*;
143 use dodrio::builder::*;
137
144
138− header(&cx)
145+ header(bump)
139 .attr("class", "header")
146 .attr("class", "header")
140 .children([
147 .children([
141− h1(&cx).children([text("todos")]).finish(),
148+ h1(bump).children([text("todos")]).finish(),
142− input(&cx)
149+ input(bump)
143 .on("input", |root, vdom, event| {
150 .on("input", |root, vdom, event| {
144 let input = event
151 let input = event
145 .target()
152 .target()
@@ -156,19 +163,19 @@ impl<C: TodosActions> Todos<C> {
156 .attr("class", "new-todo")
163 .attr("class", "new-todo")
157 .attr("placeholder", "What needs to be done?")
164 .attr("placeholder", "What needs to be done?")
158 .attr("autofocus", "")
165 .attr("autofocus", "")
159− .attr(
160− "value",
166+ .attr("value", &self.draft)
161− bumpalo::format!(in cx.bump, "{}", self.draft).into_bump_str(),
162− )
163 .finish(),
167 .finish(),
164 ])
168 ])
165 .finish()
169 .finish()
166 }
170 }
167
171
168− fn todos_list<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
172+ fn todos_list<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
173+ where
174+ 'a: 'bump,
175+ {
169 use dodrio::{builder::*, bumpalo::collections::Vec};
176 use dodrio::{builder::*, bumpalo::collections::Vec};
170
177
171− let mut todos = Vec::with_capacity_in(self.todos.len(), cx.bump);
178+ let mut todos = Vec::with_capacity_in(self.todos.len(), bump);
172 todos.extend(
179 todos.extend(
173 self.todos
180 self.todos
174 .iter()
181 .iter()
@@ -177,10 +184,10 @@ impl<C: TodosActions> Todos<C> {
177 Visibility::Active => !t.is_complete(),
184 Visibility::Active => !t.is_complete(),
178 Visibility::Completed => t.is_complete(),
185 Visibility::Completed => t.is_complete(),
179 })
186 })
180− .map(|t| t.render(cx)),
187+ .map(|t| t.render(bump)),
181 );
188 );
182
189
183− section(&cx)
190+ section(bump)
184 .attr("class", "main")
191 .attr("class", "main")
185 .attr(
192 .attr(
186 "visibility",
193 "visibility",
@@ -191,7 +198,7 @@ impl<C: TodosActions> Todos<C> {
191 },
198 },
192 )
199 )
193 .children([
200 .children([
194− input(&cx)
201+ input(bump)
195 .attr("class", "toggle-all")
202 .attr("class", "toggle-all")
196 .attr("id", "toggle-all")
203 .attr("id", "toggle-all")
197 .attr("type", "checkbox")
204 .attr("type", "checkbox")
@@ -201,16 +208,19 @@ impl<C: TodosActions> Todos<C> {
201 C::toggle_all(root, vdom);
208 C::toggle_all(root, vdom);
202 })
209 })
203 .finish(),
210 .finish(),
204− label(&cx)
211+ label(bump)
205 .attr("for", "toggle-all")
212 .attr("for", "toggle-all")
206 .children([text("Mark all as complete")])
213 .children([text("Mark all as complete")])
207 .finish(),
214 .finish(),
208− ul(&cx).attr("class", "todo-list").children(todos).finish(),
215+ ul(bump).attr("class", "todo-list").children(todos).finish(),
209 ])
216 ])
210 .finish()
217 .finish()
211 }
218 }
212
219
213− fn footer<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
220+ fn footer<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
221+ where
222+ 'a: 'bump,
223+ {
214 use dodrio::builder::*;
224 use dodrio::builder::*;
215
225
216 let completed_count = self.todos.iter().filter(|t| t.is_complete()).count();
226 let completed_count = self.todos.iter().filter(|t| t.is_complete()).count();
@@ -220,36 +230,36 @@ impl<C: TodosActions> Todos<C> {
220 } else {
230 } else {
221 " items left"
231 " items left"
222 };
232 };
223− let incomplete_count = bumpalo::format!(in cx.bump, "{}", incomplete_count);
233+ let incomplete_count = bumpalo::format!(in bump, "{}", incomplete_count);
224
234
225 let clear_completed_text = bumpalo::format!(
235 let clear_completed_text = bumpalo::format!(
226− in cx.bump,
236+ in bump,
227 "Clear completed ({})",
237 "Clear completed ({})",
228 self.todos.iter().filter(|t| t.is_complete()).count()
238 self.todos.iter().filter(|t| t.is_complete()).count()
229 );
239 );
230
240
231− footer(&cx)
241+ footer(bump)
232 .attr("class", "footer")
242 .attr("class", "footer")
233 .bool_attr("hidden", self.todos.is_empty())
243 .bool_attr("hidden", self.todos.is_empty())
234 .children([
244 .children([
235− span(&cx)
245+ span(bump)
236 .attr("class", "todo-count")
246 .attr("class", "todo-count")
237 .children([
247 .children([
238− strong(&cx)
248+ strong(bump)
239 .children([text(incomplete_count.into_bump_str())])
249 .children([text(incomplete_count.into_bump_str())])
240 .finish(),
250 .finish(),
241 text(items_left),
251 text(items_left),
242 ])
252 ])
243 .finish(),
253 .finish(),
244− ul(&cx)
254+ ul(bump)
245 .attr("class", "filters")
255 .attr("class", "filters")
246 .children([
256 .children([
247− self.visibility_swap(cx, "#/", Visibility::All),
257+ self.visibility_swap(bump, "#/", Visibility::All),
248− self.visibility_swap(cx, "#/active", Visibility::Active),
258+ self.visibility_swap(bump, "#/active", Visibility::Active),
249− self.visibility_swap(cx, "#/completed", Visibility::Completed),
259+ self.visibility_swap(bump, "#/completed", Visibility::Completed),
250 ])
260 ])
251 .finish(),
261 .finish(),
252− button(&cx)
262+ button(bump)
253 .on("click", |root, vdom, _event| {
263 .on("click", |root, vdom, _event| {
254 C::delete_completed(root, vdom);
264 C::delete_completed(root, vdom);
255 })
265 })
@@ -261,19 +271,22 @@ impl<C: TodosActions> Todos<C> {
261 .finish()
271 .finish()
262 }
272 }
263
273
264− fn visibility_swap<'a>(
274+ fn visibility_swap<'a, 'bump>(
265− &self,
275+ &'a self,
266− cx: &mut RenderContext<'a>,
276+ bump: &'bump Bump,
267 url: &'static str,
277 url: &'static str,
268 target_vis: Visibility,
278 target_vis: Visibility,
269− ) -> Node<'a> {
279+ ) -> Node<'bump>
280+ where
281+ 'a: 'bump,
282+ {
270 use dodrio::builder::*;
283 use dodrio::builder::*;
271
284
272− li(&cx)
285+ li(bump)
273 .on("click", move |root, vdom, _event| {
286 .on("click", move |root, vdom, _event| {
274 C::change_visibility(root, vdom, target_vis);
287 C::change_visibility(root, vdom, target_vis);
275 })
288 })
276− .children([a(&cx)
289+ .children([a(bump)
277 .attr("href", url)
290 .attr("href", url)
278 .attr(
291 .attr(
279 "class",
292 "class",
@@ -289,12 +302,15 @@ impl<C: TodosActions> Todos<C> {
289 }
302 }
290}
303}
291
304
292−impl<'a, C: TodosActions> Render<'a> for Todos<C> {
305+impl<C: TodosActions> Render for Todos<C> {
293− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
306+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
307+ where
308+ 'a: 'bump,
309+ {
294 use dodrio::builder::*;
310 use dodrio::builder::*;
295
311
296− div(&cx)
312+ div(bump)
297− .children([self.header(cx), self.todos_list(cx), self.footer(cx)])
313+ .children([self.header(bump), self.todos_list(bump), self.footer(bump)])
298 .finish()
314 .finish()
299 }
315 }
300}
316}
js/change-list-interpreter.jsdeleted−377
@@ -1,377 +0,0 @@
1−const decoder = new TextDecoder();
2−
3−function top(stack) {
4− return stack[stack.length - 1];
5−}
6−
7−function string(mem, pointer, length) {
8− const buf = mem.subarray(pointer, pointer + length);
9− return decoder.decode(buf);
10−}
11−
12−const OP_TABLE = [
13− // 0
14− function setText(interpreter, mem8, mem32, i) {
15− const pointer = mem32[i++];
16− const length = mem32[i++];
17− const str = string(mem8, pointer, length);
18− top(interpreter.stack).textContent = str;
19− return i;
20− },
21−
22− // 1
23− function removeSelfAndNextSiblings(interpreter, mem8, mem32, i) {
24− const node = interpreter.stack.pop();
25− let sibling = node.nextSibling;
26− while (sibling) {
27− const temp = sibling.nextSibling;
28− sibling.remove();
29− sibling = temp;
30− }
31− node.remove();
32− return i;
33− },
34−
35− // 2
36− function replaceWith(interpreter, mem8, mem32, i) {
37− const newNode = interpreter.stack.pop();
38− const oldNode = interpreter.stack.pop();
39− oldNode.replaceWith(newNode);
40− interpreter.stack.push(newNode);
41− return i;
42− },
43−
44− // 3
45− function setAttribute(interpreter, mem8, mem32, i) {
46− const nameId = mem32[i++];
47− const valueId = mem32[i++];
48− const name = interpreter.getCachedString(nameId);
49− const value = interpreter.getCachedString(valueId);
50− const node = top(interpreter.stack);
51− node.setAttribute(name, value);
52−
53− // Some attributes are "volatile" and don't work through `setAttribute`.
54− if (name === "value") {
55− node.value = value;
56− }
57− if (name === "checked") {
58− node.checked = true;
59− }
60− if (name === "selected") {
61− node.selected = true;
62− }
63−
64− return i;
65− },
66−
67− // 4
68− function removeAttribute(interpreter, mem8, mem32, i) {
69− const nameId = mem32[i++];
70− const name = interpreter.getCachedString(nameId);
71− const node = top(interpreter.stack);
72− node.removeAttribute(name);
73−
74− // Some attributes are "volatile" and don't work through `removeAttribute`.
75− if (name === "value") {
76− node.value = null;
77− }
78− if (name === "checked") {
79− node.checked = false;
80− }
81− if (name === "selected") {
82− node.selected = false;
83− }
84−
85− return i;
86− },
87−
88− // 5
89− function pushReverseChild(interpreter, mem8, mem32, i) {
90− const n = mem32[i++];
91− const parent = top(interpreter.stack);
92− const children = parent.childNodes;
93− const child = children[children.length - n - 1];
94− interpreter.stack.push(child);
95− return i;
96− },
97−
98− // 6
99− function popPushChild(interpreter, mem8, mem32, i) {
100− const n = mem32[i++];
101− interpreter.stack.pop();
102− const parent = top(interpreter.stack);
103− const children = parent.childNodes;
104− const child = children[n];
105− interpreter.stack.push(child);
106− return i;
107− },
108−
109− // 7
110− function pop(interpreter, mem8, mem32, i) {
111− interpreter.stack.pop();
112− return i;
113− },
114−
115− // 8
116− function appendChild(interpreter, mem8, mem32, i) {
117− const child = interpreter.stack.pop();
118− top(interpreter.stack).appendChild(child);
119− return i;
120− },
121−
122− // 9
123− function createTextNode(interpreter, mem8, mem32, i) {
124− const pointer = mem32[i++];
125− const length = mem32[i++];
126− const text = string(mem8, pointer, length);
127− interpreter.stack.push(document.createTextNode(text));
128− return i;
129− },
130−
131− // 10
132− function createElement(interpreter, mem8, mem32, i) {
133− const tagNameId = mem32[i++];
134− const tagName = interpreter.getCachedString(tagNameId);
135− interpreter.stack.push(document.createElement(tagName));
136− return i;
137− },
138−
139− // 11
140− function newEventListener(interpreter, mem8, mem32, i) {
141− const eventId = mem32[i++];
142− const eventType = interpreter.getCachedString(eventId);
143− const a = mem32[i++];
144− const b = mem32[i++];
145− const el = top(interpreter.stack);
146− el.addEventListener(eventType, interpreter.eventHandler);
147− el[`dodrio-a-${eventType}`] = a;
148− el[`dodrio-b-${eventType}`] = b;
149− return i;
150− },
151−
152− // 12
153− function updateEventListener(interpreter, mem8, mem32, i) {
154− const eventId = mem32[i++];
155− const eventType = interpreter.getCachedString(eventId);
156− const el = top(interpreter.stack);
157− el[`dodrio-a-${eventType}`] = mem32[i++];
158− el[`dodrio-b-${eventType}`] = mem32[i++];
159− return i;
160− },
161−
162− // 13
163− function removeEventListener(interpreter, mem8, mem32, i) {
164− const eventId = mem32[i++];
165− const eventType = interpreter.getCachedString(eventId);
166− const el = top(interpreter.stack);
167− el.removeEventListener(eventType, interpreter.eventHandler);
168− return i;
169− },
170−
171− // 14
172− function addCachedString(interpreter, mem8, mem32, i) {
173− const pointer = mem32[i++];
174− const length = mem32[i++];
175− const id = mem32[i++];
176− const str = string(mem8, pointer, length);
177− interpreter.addCachedString(str, id);
178− return i;
179− },
180−
181− // 15
182− function dropCachedString(interpreter, mem8, mem32, i) {
183− const id = mem32[i++];
184− interpreter.dropCachedString(id);
185− return i;
186− },
187−
188− // 16
189− function createElementNS(interpreter, mem8, mem32, i) {
190− const tagNameId = mem32[i++];
191− const tagName = interpreter.getCachedString(tagNameId);
192− const nsId = mem32[i++];
193− const ns = interpreter.getCachedString(nsId);
194− interpreter.stack.push(document.createElementNS(ns, tagName));
195− return i;
196− },
197−
198− // 17
199− function saveChildrenToTemporaries(interpreter, mem8, mem32, i) {
200− let temp = mem32[i++];
201− const start = mem32[i++];
202− const end = mem32[i++];
203− const parent = top(interpreter.stack);
204− const children = parent.childNodes;
205− for (let i = start; i < end; i++) {
206− interpreter.temporaries[temp++] = children[i];
207− }
208− return i;
209− },
210−
211− // 18
212− function pushChild(interpreter, mem8, mem32, i) {
213− const parent = top(interpreter.stack);
214− const n = mem32[i++];
215− const child = parent.childNodes[n];
216− interpreter.stack.push(child);
217− return i;
218− },
219−
220− // 19
221− function pushTemporary(interpreter, mem8, mem32, i) {
222− const temp = mem32[i++];
223− interpreter.stack.push(interpreter.temporaries[temp]);
224− return i;
225− },
226−
227− // 20
228− function insertBefore(interpreter, mem8, mem32, i) {
229− const before = interpreter.stack.pop();
230− const after = interpreter.stack.pop();
231− after.parentNode.insertBefore(before, after);
232− interpreter.stack.push(before);
233− return i;
234− },
235−
236− // 21
237− function popPushReverseChild(interpreter, mem8, mem32, i) {
238− const n = mem32[i++];
239− interpreter.stack.pop();
240− const parent = top(interpreter.stack);
241− const children = parent.childNodes;
242− const child = children[children.length - n - 1];
243− interpreter.stack.push(child);
244− return i;
245− },
246−
247− // 22
248− function removeChild(interpreter, mem8, mem32, i) {
249− const n = mem32[i++];
250− const parent = top(interpreter.stack);
251− const child = parent.childNodes[n];
252− child.remove();
253− return i;
254− },
255−
256− // 23
257− function setClass(interpreter, mem8, mem32, i) {
258− const classId = mem32[i++];
259− const className = interpreter.getCachedString(classId);
260− top(interpreter.stack).className = className;
261− return i;
262− },
263−
264− // 24
265− function saveTemplate(interpreter, mem8, mem32, i) {
266− const id = mem32[i++];
267− const template = top(interpreter.stack);
268− interpreter.saveTemplate(id, template.cloneNode(true));
269− return i;
270− },
271−
272− // 25
273− function pushTemplate(interpreter, mem8, mem32, i) {
274− const id = mem32[i++];
275− const template = interpreter.getTemplate(id);
276− interpreter.stack.push(template.cloneNode(true));
277− return i;
278− }
279−];
280−
281−export class ChangeListInterpreter {
282− constructor(container) {
283− this.trampoline = null;
284− this.container = container;
285− this.ranges = [];
286− this.stack = [];
287− this.strings = new Map();
288− this.temporaries = [];
289− this.templates = new Map();
290− }
291−
292− unmount() {
293− this.trampoline.mounted = false;
294−
295− // Null out all of our properties just to ensure that if we mistakenly ever
296− // call a method on this instance again, it will throw.
297− this.trampoline = null;
298− this.container = null;
299− this.ranges = null;
300− this.stack = null;
301− this.strings = null;
302− this.temporaries = null;
303− this.templates = null;
304− }
305−
306− addChangeListRange(start, len) {
307− this.ranges.push(start);
308− this.ranges.push(len);
309− }
310−
311− applyChanges(memory) {
312− if (this.ranges.length == 0) {
313− return;
314− }
315−
316− this.stack.push(this.container.firstChild);
317− const mem8 = new Uint8Array(memory.buffer);
318− const mem32 = new Uint32Array(memory.buffer);
319−
320− for (let i = 0; i < this.ranges.length; i += 2) {
321− const start = this.ranges[i];
322− const len = this.ranges[i + 1];
323− this.applyChangeRange(mem8, mem32, start, len);
324− }
325−
326− this.ranges.length = 0;
327− this.stack.length = 0;
328− this.temporaries.length = 0;
329− }
330−
331− applyChangeRange(mem8, mem32, start, len) {
332− const end = (start + len) / 4;
333− for (let i = start / 4; i < end; ) {
334− const op = mem32[i++];
335− i = OP_TABLE[op](this, mem8, mem32, i);
336− }
337− }
338−
339− addCachedString(str, id) {
340− this.strings.set(id, str);
341− }
342−
343− dropCachedString(id) {
344− this.strings.delete(id);
345− }
346−
347− getCachedString(id) {
348− return this.strings.get(id);
349− }
350−
351− saveTemplate(id, template) {
352− this.templates.set(id, template);
353− }
354−
355− getTemplate(id) {
356− return this.templates.get(id);
357− }
358−
359− initEventsTrampoline(trampoline) {
360− this.trampoline = trampoline;
361− trampoline.mounted = true;
362− this.eventHandler = function(event) {
363− if (!trampoline.mounted) {
364− throw new Error("invocation of listener after VDOM has been unmounted");
365− }
366−
367− // `this` always refers to the element the handler was added to.
368− // Since we're adding the handler to all elements our content wants
369− // to listen for events on, this ensures that we always get the right
370− // values for `a` and `b`.
371− const type = event.type;
372− const a = this[`dodrio-a-${type}`];
373− const b = this[`dodrio-b-${type}`];
374− trampoline(event, a, b);
375− }
376− }
377−}
js/change-list.jsnew file+259
@@ -0,0 +1,259 @@
1+const decoder = new TextDecoder();
2+
3+function top(stack) {
4+ return stack[stack.length - 1];
5+}
6+
7+function string(mem, pointer, length) {
8+ const buf = mem.subarray(pointer, pointer + length);
9+ return decoder.decode(buf);
10+}
11+
12+const OP_TABLE = [
13+ // 0
14+ function setText(changeList, mem8, mem32, i) {
15+ const pointer = mem32[i++];
16+ const length = mem32[i++];
17+ const str = string(mem8, pointer, length);
18+ top(changeList.stack).textContent = str;
19+ return i;
20+ },
21+
22+ // 1
23+ function removeSelfAndNextSiblings(changeList, mem8, mem32, i) {
24+ const node = changeList.stack.pop();
25+ let sibling = node.nextSibling;
26+ while (sibling) {
27+ const temp = sibling.nextSibling;
28+ sibling.remove();
29+ sibling = temp;
30+ }
31+ node.remove();
32+ return i;
33+ },
34+
35+ // 2
36+ function replaceWith(changeList, mem8, mem32, i) {
37+ const newNode = changeList.stack.pop();
38+ const oldNode = changeList.stack.pop();
39+ oldNode.replaceWith(newNode);
40+ changeList.stack.push(newNode);
41+ return i;
42+ },
43+
44+ // 3
45+ function setAttribute(changeList, mem8, mem32, i) {
46+ const pointer1 = mem32[i++];
47+ const length1 = mem32[i++];
48+ const name = string(mem8, pointer1, length1);
49+ const pointer2 = mem32[i++];
50+ const length2 = mem32[i++];
51+ const value = string(mem8, pointer2, length2);
52+ const node = top(changeList.stack);
53+ node.setAttribute(name, value);
54+
55+ // Some attributes are "volatile" and don't work through `setAttribute`.
56+ if (name === "value") {
57+ node.value = value;
58+ }
59+ if (name === "checked") {
60+ node.checked = true;
61+ }
62+ if (name === "selected") {
63+ node.selected = true;
64+ }
65+
66+ return i;
67+ },
68+
69+ // 4
70+ function removeAttribute(changeList, mem8, mem32, i) {
71+ const pointer = mem32[i++];
72+ const length = mem32[i++];
73+ const name = string(mem8, pointer, length);
74+ const node = top(changeList.stack);
75+ node.removeAttribute(name);
76+
77+ // Some attributes are "volatile" and don't work through `removeAttribute`.
78+ if (name === "value") {
79+ node.value = null;
80+ }
81+ if (name === "checked") {
82+ node.checked = false;
83+ }
84+ if (name === "selected") {
85+ node.selected = false;
86+ }
87+
88+ return i;
89+ },
90+
91+ // 5
92+ function pushFirstChild(changeList, mem8, mem32, i) {
93+ changeList.stack.push(top(changeList.stack).firstChild);
94+ return i;
95+ },
96+
97+ // 6
98+ function popPushNextSibling(changeList, mem8, mem32, i) {
99+ const node = changeList.stack.pop();
100+ changeList.stack.push(node.nextSibling);
101+ return i;
102+ },
103+
104+ // 7
105+ function pop(changeList, mem8, mem32, i) {
106+ changeList.stack.pop();
107+ return i;
108+ },
109+
110+ // 8
111+ function appendChild(changeList, mem8, mem32, i) {
112+ const child = changeList.stack.pop();
113+ top(changeList.stack).appendChild(child);
114+ return i;
115+ },
116+
117+ // 9
118+ function createTextNode(changeList, mem8, mem32, i) {
119+ const pointer = mem32[i++];
120+ const length = mem32[i++];
121+ const text = string(mem8, pointer, length);
122+ changeList.stack.push(document.createTextNode(text));
123+ return i;
124+ },
125+
126+ // 10
127+ function createElement(changeList, mem8, mem32, i) {
128+ const pointer = mem32[i++];
129+ const length = mem32[i++];
130+ const tagName = string(mem8, pointer, length);
131+ changeList.stack.push(document.createElement(tagName));
132+ return i;
133+ },
134+
135+ // 11
136+ function newEventListener(changeList, mem8, mem32, i) {
137+ const pointer = mem32[i++];
138+ const length = mem32[i++];
139+ const eventType = string(mem8, pointer, length);
140+ const a = mem32[i++];
141+ const b = mem32[i++];
142+ const el = top(changeList.stack);
143+ const listener = new Listener(a, b, eventType, changeList.eventsTrampoline, el);
144+ changeList.listeners.add(listener);
145+ el.addEventListener(eventType, listener.callback);
146+ el[`dodrio-${eventType}`] = listener;
147+ return i;
148+ },
149+
150+ // 12
151+ function updateEventListener(changeList, mem8, mem32, i) {
152+ const pointer = mem32[i++];
153+ const length = mem32[i++];
154+ const eventType = string(mem8, pointer, length);
155+ const el = top(changeList.stack);
156+ const listener = el[`dodrio-${eventType}`];
157+ listener.a = mem32[i++];
158+ listener.b = mem32[i++];
159+ return i;
160+ },
161+
162+ // 13
163+ function removeEventListener(changeList, mem8, mem32, i) {
164+ const pointer = mem32[i++];
165+ const length = mem32[i++];
166+ const eventType = string(mem8, pointer, length);
167+ const el = top(changeList.stack);
168+ const listener = el[`dodrio-${eventType}`];
169+ el.removeEventListener(eventType, listener.callback);
170+ changeList.listeners.delete(listener);
171+ return i;
172+ }
173+];
174+
175+class Listener {
176+ constructor(a, b, eventType, trampoline, el) {
177+ this.a = a;
178+ this.b = b;
179+ this.eventType = eventType;
180+ this.trampoline = trampoline;
181+ this.el = el;
182+ this.callback = this.callback.bind(this);
183+ }
184+
185+ callback(event) {
186+ this.trampoline(event, this.a, this.b);
187+ }
188+}
189+
190+// export
191+class ChangeList {
192+ constructor(container) {
193+ this.listeners = new Set();
194+ this.trampoline = null;
195+ this.container = container;
196+ this.ranges = [];
197+ this.stack = [];
198+ }
199+
200+ unmount() {
201+ for (const listener of this.listeners) {
202+ listener.el.removeEventListener(listener.eventType, listener.callback);
203+ listener.trampoline = () => {
204+ throw new Error("invocation of listener after it has been removed");
205+ };
206+ listener.a = 0;
207+ listener.b = 0;
208+ }
209+
210+ // Null out all of our properties just to ensure that if we mistakenly ever
211+ // call a method on this instance again, it will throw.
212+ this.listeners = null;
213+ this.trampoline = null;
214+ this.container = null;
215+ this.ranges = null;
216+ this.stack = null;
217+ }
218+
219+ addChangeListRange(start, len) {
220+ this.ranges.push(start);
221+ this.ranges.push(len);
222+ }
223+
224+ applyChanges(memory) {
225+ if (this.ranges.length == 0) {
226+ return;
227+ }
228+
229+ this.stack.push(this.container.firstChild);
230+ const mem8 = new Uint8Array(memory.buffer);
231+ const mem32 = new Uint32Array(memory.buffer);
232+
233+ for (let i = 0; i < this.ranges.length; i += 2) {
234+ const start = this.ranges[i];
235+ const len = this.ranges[i + 1];
236+ this.applyChangeRange(mem8, mem32, start, len);
237+ }
238+
239+ this.ranges.length = 0;
240+ this.stack.length = 0;
241+ }
242+
243+ applyChangeRange(mem8, mem32, start, len) {
244+ const end = (start + len) / 4;
245+ for (let i = start / 4; i < end; ) {
246+ const op = mem32[i++];
247+ i = OP_TABLE[op](this, mem8, mem32, i);
248+ }
249+ }
250+
251+ initEventsTrampoline(trampoline) {
252+ this.eventsTrampoline = (...args) => {
253+ trampoline(...args);
254+ };
255+ }
256+}
257+window.ChangeList = ChangeList;
258+
259+//# sourceURL=dodrio/change-list.js
js/package-lock.jsonnew file+5
@@ -0,0 +1,5 @@
1+{
2+ "name": "dodrio",
3+ "version": "0.1.0",
4+ "lockfileVersion": 1
5+}
js/package.jsonnew file+11
@@ -0,0 +1,11 @@
1+{
2+ "name": "dodrio",
3+ "version": "0.1.0",
4+ "description": "Dodrio",
5+ "main": "dodrio.js",
6+ "scripts": {
7+ "test": "echo \"Error: no test specified\" && exit 1"
8+ },
9+ "author": "Nick Fitzgerald <fitzgen@gmail.com>",
10+ "license": "MPL-2.0"
11+}
js/strace.jsdeleted−80
@@ -1,80 +0,0 @@
1−export function initStrace() {
2− instrumentAll(Element.prototype);
3− instrumentAll(Document.prototype);
4− instrumentAll(Node.prototype);
5− instrumentAll(EventTarget.prototype);
6−}
7−
8−const counts = new Map;
9−counts.total = 0;
10−
11−let timer = null;
12−
13−function log(ctorName, kind, key) {
14− counts.total += 1;
15−
16− const entry = `${kind} ${ctorName}#${key}`
17− const count = counts.get(entry);
18− if (count === undefined) {
19− counts.set(entry, 1);
20− } else {
21− counts.set(entry, count + 1);
22− }
23−
24− if (timer !== null) {
25− clearTimeout(timer);
26− }
27− timer = setTimeout(dumpAndReset, 100);
28−}
29−
30−function dumpAndReset() {
31− const data = [...counts]
32− .sort((a, b) => b[1] - a[1])
33− .map(a => ({ "DOM Method": a[0], "Count": a[1] }));
34−
35− data.push({ "DOM Method": "<total>", "Count": counts.total });
36−
37− console.table(data, ["DOM Method", "Count"]);
38−
39− counts.clear();
40− counts.total = 0;
41−}
42−
43−function instrument(proto, key, desc) {
44− const ctorName = proto.constructor.name;
45−
46− if (typeof desc.value == "function") {
47− const f = desc.value;
48− desc.value = function (...args) {
49− log(ctorName, "call", key);
50− return f.apply(this, args);
51− };
52− }
53−
54− if (typeof desc.get == "function") {
55− const get = desc.get;
56− desc.get = function (...args) {
57− log(ctorName, "get", key);
58− return get.apply(this, args);
59− };
60− }
61−
62− if (typeof desc.set == "function") {
63− const set = desc.set;
64− desc.set = function (...args) {
65− log(ctorName, "set", key);
66− return set.apply(this, args);
67− };
68− }
69−
70− Object.defineProperty(proto, key, desc);
71−}
72−
73−function instrumentAll(proto) {
74− for (const key of Object.keys(proto)) {
75− const desc = Object.getOwnPropertyDescriptor(proto, key);
76− if (desc.configurable) {
77− instrument(proto, key, desc);
78− }
79− }
80−}
publish.shdeleted−11
@@ -1,11 +0,0 @@
1−#!/usr/bin/env bash
2−
3−set -ex
4−
5−cd "$(dirname "$0")"
6−
7−cd crates/js-api
8−cargo publish --no-verify $PUBLISH_FLAGS
9−cd ../..
10−
11−cargo publish $PUBLISH_FLAGS
src/builder.rs+6−161
@@ -1,6 +1,6 @@
1//! Helpers for building virtual DOM nodes.
1//! Helpers for building virtual DOM nodes.
2
2
3−use crate::{node::NodeKey, Attribute, Listener, Node, RootRender, VdomWeak};
3+use crate::{Attribute, Listener, Node, RootRender, VdomWeak};
4use bumpalo::Bump;
4use bumpalo::Bump;
5
5
6/// A virtual DOM element builder.
6/// A virtual DOM element builder.
@@ -16,12 +16,10 @@ where
16 Children: 'a + AsRef<[Node<'a>]>,
16 Children: 'a + AsRef<[Node<'a>]>,
17{
17{
18 bump: &'a Bump,
18 bump: &'a Bump,
19− key: NodeKey,
20 tag_name: &'a str,
19 tag_name: &'a str,
21 listeners: Listeners,
20 listeners: Listeners,
22 attributes: Attributes,
21 attributes: Attributes,
23 children: Children,
22 children: Children,
24− namespace: Option<&'a str>,
25}
23}
26
24
27impl<'a>
25impl<'a>
@@ -55,19 +53,13 @@ impl<'a>
55 /// let my_element_builder = ElementBuilder::new(&b, tag_name);
53 /// let my_element_builder = ElementBuilder::new(&b, tag_name);
56 /// # fn flip_coin() -> bool { true }
54 /// # fn flip_coin() -> bool { true }
57 /// ```
55 /// ```
58− pub fn new<B>(bump: B, tag_name: &'a str) -> Self
56+ pub fn new(bump: &'a Bump, tag_name: &'a str) -> Self {
59− where
60− B: Into<&'a Bump>,
61− {
62− let bump = bump.into();
63 ElementBuilder {
57 ElementBuilder {
64 bump,
58 bump,
65− key: NodeKey::NONE,
66 tag_name,
59 tag_name,
67 listeners: bumpalo::collections::Vec::new_in(bump),
60 listeners: bumpalo::collections::Vec::new_in(bump),
68 attributes: bumpalo::collections::Vec::new_in(bump),
61 attributes: bumpalo::collections::Vec::new_in(bump),
69 children: bumpalo::collections::Vec::new_in(bump),
62 children: bumpalo::collections::Vec::new_in(bump),
70− namespace: None,
71 }
63 }
72 }
64 }
73}
65}
@@ -112,12 +104,10 @@ where
112 {
104 {
113 ElementBuilder {
105 ElementBuilder {
114 bump: self.bump,
106 bump: self.bump,
115− key: self.key,
116 tag_name: self.tag_name,
107 tag_name: self.tag_name,
117 listeners,
108 listeners,
118 attributes: self.attributes,
109 attributes: self.attributes,
119 children: self.children,
110 children: self.children,
120− namespace: self.namespace,
121 }
111 }
122 }
112 }
123
113
@@ -151,12 +141,10 @@ where
151 {
141 {
152 ElementBuilder {
142 ElementBuilder {
153 bump: self.bump,
143 bump: self.bump,
154− key: self.key,
155 tag_name: self.tag_name,
144 tag_name: self.tag_name,
156 listeners: self.listeners,
145 listeners: self.listeners,
157 attributes,
146 attributes,
158 children: self.children,
147 children: self.children,
159− namespace: self.namespace,
160 }
148 }
161 }
149 }
162
150
@@ -190,81 +178,13 @@ where
190 {
178 {
191 ElementBuilder {
179 ElementBuilder {
192 bump: self.bump,
180 bump: self.bump,
193− key: self.key,
194 tag_name: self.tag_name,
181 tag_name: self.tag_name,
195 listeners: self.listeners,
182 listeners: self.listeners,
196 attributes: self.attributes,
183 attributes: self.attributes,
197 children,
184 children,
198− namespace: self.namespace,
199− }
200− }
201−
202− /// Set the namespace for this element.
203− ///
204− /// # Example
205− ///
206− /// ```no_run
207− /// use dodrio::{builder::*, bumpalo::Bump};
208− ///
209− /// let b = Bump::new();
210− ///
211− /// // Create a `<td>` tag with an xhtml namespace
212− /// let my_td = td(&b)
213− /// .namespace(Some("http://www.w3.org/1999/xhtml"))
214− /// .finish();
215− /// ```
216− #[inline]
217− pub fn namespace(self, namespace: Option<&'a str>) -> Self {
218− ElementBuilder {
219− bump: self.bump,
220− key: self.key,
221− tag_name: self.tag_name,
222− listeners: self.listeners,
223− attributes: self.attributes,
224− children: self.children,
225− namespace,
226 }
185 }
227 }
186 }
228
187
229− /// Set this element's key.
230− ///
231− /// When diffing sets of siblings, if an old sibling and new sibling share a
232− /// key, then they will always reuse the same physical DOM node. This is
233− /// important when using CSS animations, web components, third party JS, or
234− /// anything else that makes the diffing implementation observable.
235− ///
236− /// Do not use keys if such a scenario does not apply. Keyed diffing is
237− /// generally more expensive than not, since it is putting greater
238− /// constraints on the diffing algorithm.
239− ///
240− /// # Invariants You Must Uphold
241− ///
242− /// The key may not be `u32::MAX`, which is a reserved key value.
243− ///
244− /// Keys must be unique among siblings.
245− ///
246− /// All sibling nodes must be keyed, or they must all not be keyed. You may
247− /// not mix keyed and unkeyed siblings.
248− ///
249− /// # Example
250− ///
251− /// ```no_run
252− /// use dodrio::{builder::*, bumpalo::Bump};
253− ///
254− /// let b = Bump::new();
255− ///
256− /// let my_li = li(&b)
257− /// .key(1337)
258− /// .finish();
259− /// ```
260− #[inline]
261− pub fn key(mut self, key: u32) -> Self {
262− use std::u32;
263− debug_assert!(key != u32::MAX);
264− self.key = NodeKey(key);
265− self
266− }
267−
268 /// Create the virtual DOM node described by this builder.
188 /// Create the virtual DOM node described by this builder.
269 ///
189 ///
270 /// # Example
190 /// # Example
@@ -282,23 +202,12 @@ where
282 /// ```
202 /// ```
283 #[inline]
203 #[inline]
284 pub fn finish(self) -> Node<'a> {
204 pub fn finish(self) -> Node<'a> {
285− let children: &'a Children = self.bump.alloc(self.children);
286− let children: &'a [Node<'a>] = children.as_ref();
287−
288− let listeners: &'a Listeners = self.bump.alloc(self.listeners);
289− let listeners: &'a [Listener<'a>] = listeners.as_ref();
290−
291− let attributes: &'a Attributes = self.bump.alloc(self.attributes);
292− let attributes: &'a [Attribute<'a>] = attributes.as_ref();
293−
294 Node::element(
205 Node::element(
295 self.bump,
206 self.bump,
296− self.key,
297 self.tag_name,
207 self.tag_name,
298− listeners,
208+ self.listeners,
299− attributes,
209+ self.attributes,
300− children,
210+ self.children,
301− self.namespace,
302 )
211 )
303 }
212 }
304}
213}
@@ -433,28 +342,6 @@ macro_rules! builder_constructors {
433 ( $(
342 ( $(
434 $(#[$attr:meta])*
343 $(#[$attr:meta])*
435 $name:ident;
344 $name:ident;
436− )* ) => {
437− $(
438− $(#[$attr])*
439− #[inline]
440− pub fn $name<'a, B>(
441− bump: B,
442− ) -> ElementBuilder<
443− 'a,
444− bumpalo::collections::Vec<'a, Listener<'a>>,
445− bumpalo::collections::Vec<'a, Attribute<'a>>,
446− bumpalo::collections::Vec<'a, Node<'a>>,
447− >
448− where
449− B: Into<&'a Bump>
450− {
451− ElementBuilder::new(bump, stringify!($name))
452− }
453− )*
454− };
455− ( $(
456− $(#[$attr:meta])*
457− $name:ident <> $namespace:tt;
458 )* ) => {
345 )* ) => {
459 $(
346 $(
460 $(#[$attr])*
347 $(#[$attr])*
@@ -467,8 +354,7 @@ macro_rules! builder_constructors {
467 bumpalo::collections::Vec<'a, Attribute<'a>>,
354 bumpalo::collections::Vec<'a, Attribute<'a>>,
468 bumpalo::collections::Vec<'a, Node<'a>>,
355 bumpalo::collections::Vec<'a, Node<'a>>,
469 > {
356 > {
470− let builder = ElementBuilder::new(bump, stringify!($name));
357+ ElementBuilder::new(bump, stringify!($name))
471− builder.namespace(Some($namespace))
472 }
358 }
473 )*
359 )*
474 }
360 }
@@ -975,47 +861,6 @@ builder_constructors! {
975 template;
861 template;
976}
862}
977
863
978−builder_constructors! {
979− // SVG components
980−
981− /// Build a
982− /// [`<svg>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg)
983− /// element.
984− svg <> "http://www.w3.org/2000/svg" ;
985− /// Build a
986− /// [`<path>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path)
987− /// element.
988− path <> "http://www.w3.org/2000/svg";
989− /// Build a
990− /// [`<circle>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/circle)
991− /// element.
992− circle <> "http://www.w3.org/2000/svg";
993− /// Build a
994− /// [`<ellipse>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/ellipse)
995− /// element.
996− ellipse <> "http://www.w3.org/2000/svg";
997− /// Build a
998− /// [`<line>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line)
999− /// element.
1000− line <> "http://www.w3.org/2000/svg";
1001− /// Build a
1002− /// [`<polygon>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polygon)
1003− /// element.
1004− polygon <> "http://www.w3.org/2000/svg";
1005− /// Build a
1006− /// [`<polyline>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polyline)
1007− /// element.
1008− polyline <> "http://www.w3.org/2000/svg";
1009− /// Build a
1010− /// [`<rect>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect)
1011− /// element.
1012− rect <> "http://www.w3.org/2000/svg";
1013− /// Build a
1014− /// [`<image>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image)
1015− /// element.
1016− image <> "http://www.w3.org/2000/svg";
1017−}
1018−
1019/// Construct a text node.
864/// Construct a text node.
1020///
865///
1021/// This is `dodrio`'s virtual DOM equivalent of `document.createTextNode`.
866/// This is `dodrio`'s virtual DOM equivalent of `document.createTextNode`.
src/cached.rs+73−139
@@ -1,52 +1,46 @@
1−use crate::{
1+use crate::{Node, Render};
2− cached_set::CachedSet,
2+use bumpalo::Bump;
3− node::{CachedNode, NodeKey},
4− Node, Render, RenderContext,
5−};
6−use std::any::TypeId;
7−use std::cell::Cell;
3+use std::cell::RefCell;
4+use std::mem;
8use std::ops::{Deref, DerefMut};
5use std::ops::{Deref, DerefMut};
9
6
7+// TODO: This implementation will only cache the rendering (generation of the
8+// virtual DOM) but not the diffing of the cached subtree. We could skip diffing
9+// for cached tree by adding `fn is_cached(&self) -> bool` to `Node` that we can
10+// check during diffing. This comes at the cost of bloating `Node`'s size (since
11+// we don't have any padding to sneak an extra `bool` field into). We should
12+// investigate whether it is worth adding this or not.
13+
10/// A renderable that supports caching for when rendering is expensive but can
14/// A renderable that supports caching for when rendering is expensive but can
11/// generate the same DOM tree.
15/// generate the same DOM tree.
12−///
13−/// # Example
14−///
15−/// The [TodoMVC
16−/// example](https://github.com/fitzgen/dodrio/blob/master/examples/todomvc/src/todo.rs)
17−/// caches individual todo items.
18−#[derive(Clone, Debug, Default)]
16+#[derive(Debug)]
19−pub struct Cached<R>
17+pub struct Cached<R> {
20−where
21− R: Default,
22−{
23 inner: R,
18 inner: R,
19+ bump: bumpalo::Bump,
20+ // Actually a self-reference into `self.bump`. Safe because we ensure that
21+ // whenever we hand out a cached node, we use a lifetime that cannot outlive
22+ // its owning `Cached<R>`.
24− cached: Cell<Option<CachedNode>>,
23+ cached: RefCell<Option<Node<'static>>>,
25−}
26−
27−pub_unstable_internal! {
28− #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
29− pub(crate) struct TemplateId(TypeId);
30}
24}
31
25
32−impl<R> Cached<R>
26+impl<R> Cached<R> {
33−where
34− R: Default,
35−{
36 /// Construct a new `Cached<R>` of an inner `R`.
27 /// Construct a new `Cached<R>` of an inner `R`.
37 ///
28 ///
38 /// # Example
29 /// # Example
39 ///
30 ///
40 /// ```
31 /// ```
32+ /// use bumpalo::Bump;
41− /// use dodrio::{Cached, Node, Render, RenderContext};
33+ /// use dodrio::{Cached, Node, Render};
42 ///
34 ///
43− /// #[derive(Default)]
44 /// pub struct Counter {
35 /// pub struct Counter {
45 /// count: u32,
36 /// count: u32,
46 /// }
37 /// }
47 ///
38 ///
48− /// impl<'a> Render<'a> for Counter {
39+ /// impl Render for Counter {
49− /// fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
40+ /// fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
41+ /// where
42+ /// 'a: 'bump
43+ /// {
50 /// // ...
44 /// // ...
51 /// # unimplemented!()
45 /// # unimplemented!()
52 /// }
46 /// }
@@ -60,8 +54,13 @@ where
60 /// ```
54 /// ```
61 #[inline]
55 #[inline]
62 pub fn new(inner: R) -> Cached<R> {
56 pub fn new(inner: R) -> Cached<R> {
57+ let bump = Bump::new();
63− let cached = Cell::new(None);
58+ let cached = RefCell::new(None);
64− Cached { inner, cached }
59+ Cached {
60+ inner,
61+ bump,
62+ cached,
63+ }
65 }
64 }
66
65
67 /// Invalidate the cached rendering.
66 /// Invalidate the cached rendering.
@@ -73,24 +72,26 @@ where
73 /// # Example
72 /// # Example
74 ///
73 ///
75 /// The `Cached<Hello>` component must have its cache invalidated whenever
74 /// The `Cached<Hello>` component must have its cache invalidated whenever
76− /// the `who` string is changed, or else the cached rendering will keep
75+ /// the `who` string is changed, or else the cached rendering whill keep
77 /// displaying greetings to old `who`s.
76 /// displaying greetings to old `who`s.
78 ///
77 ///
79 /// ```
78 /// ```
79+ /// use bumpalo::Bump;
80− /// use dodrio::{bumpalo, Cached, Node, Render, RenderContext};
80+ /// use dodrio::{Cached, Node, Render};
81 ///
81 ///
82 /// /// A component that renders to "<p>Hello, {who}!</p>"
82 /// /// A component that renders to "<p>Hello, {who}!</p>"
83− /// #[derive(Default)]
84 /// pub struct Hello {
83 /// pub struct Hello {
85 /// who: String
84 /// who: String
86 /// }
85 /// }
87 ///
86 ///
88− /// impl<'a> Render<'a> for Hello {
87+ /// impl Render for Hello {
89− /// fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
88+ /// fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
89+ /// where
90+ /// 'a: 'bump,
91+ /// {
90 /// use dodrio::builder::*;
92 /// use dodrio::builder::*;
91− /// let greeting = bumpalo::format!(in cx.bump, "Hello, {}!", self.who);
93+ /// p(bump)
92− /// p(&cx)
94+ /// .children([text("Hello, "), text(&self.who), text("!")])
93− /// .children([text(greeting.into_bump_str())])
94 /// .finish()
95 /// .finish()
95 /// }
96 /// }
96 /// }
97 /// }
@@ -103,30 +104,18 @@ where
103 /// }
104 /// }
104 /// ```
105 /// ```
105 #[inline]
106 #[inline]
106− pub fn invalidate(cached: &Self) {
107+ pub fn invalidate(cached: &mut Self) {
107− cached.cached.set(None);
108+ *cached.cached.borrow_mut() = None;
108 }
109 }
109
110
110 /// Convert a `Cached<R>` back into a plain `R`.
111 /// Convert a `Cached<R>` back into a plain `R`.
111 #[inline]
112 #[inline]
112− pub fn into_inner(cached: Self) -> R {
113+ pub fn into_inner(cached: Cached<R>) -> R {
113 cached.inner
114 cached.inner
114 }
115 }
115}
116}
116
117
117−impl<R> Cached<R>
118−where
119− R: 'static + Default,
120−{
121− pub(crate) fn template_id() -> TemplateId {
122− TemplateId(TypeId::of::<R>())
123− }
124−}
125−
126−impl<R> Deref for Cached<R>
118+impl<R> Deref for Cached<R> {
127−where
128− R: Default,
129−{
130 type Target = R;
119 type Target = R;
131
120
132 fn deref(&self) -> &R {
121 fn deref(&self) -> &R {
@@ -134,96 +123,41 @@ where
134 }
123 }
135}
124}
136
125
137−impl<R> DerefMut for Cached<R>
126+impl<R> DerefMut for Cached<R> {
138−where
139− R: Default,
140−{
141 fn deref_mut(&mut self) -> &mut R {
127 fn deref_mut(&mut self) -> &mut R {
142 &mut self.inner
128 &mut self.inner
143 }
129 }
144}
130}
145
131
146−impl<'a, R> Render<'a> for Cached<R>
132+unsafe fn extend_node_lifetime<'a>(node: Node<'a>) -> Node<'static> {
147−where
133+ mem::transmute(node)
148− R: 'static + Default + for<'b> Render<'b>,
149−{
150− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
151− let template = cx.template::<R>();
152− let cached = match self.cached.get() {
153− // This does-the-cache-contain-this-id check is necessary because
154− // the same `Cached<R>` instance can be rendered into vdom A, which
155− // will save the results into A's cached set and yield id X. Then,
156− // it can be rendered *again* into a second vdom B, and B's cached
157− // set does not have the saved results for id X. If we didn't have
158− // this check, instead of a cache miss, we would have a panic.
159− //
160− // This scenario should basically never happen in the real world,
161− // however. If we ever find that it is worth sharing a cached render
162− // component between multiple vdoms, and want to avoid these
163− // "unnecessary" cache misses, we can do this:
164− //
165− // * Make each `Cached<R>` have an instance id and generation
166− // counter.
167− //
168− // * On invalidation, bump the generation counter.
169− //
170− // * Add a generation member to the `CacheEntry`.
171− //
172− // * Each vdom maintains a map from `Cached<R>` instance id to
173− // `CacheEntry`s.
174− //
175− // * We only re-use the cached results if the `Cached<R>`'s
176− // generation counter matches the entry in the vdom's cached set.
177− //
178− // This is all do-able but is a bit more book keeping than we really
179− // want to do unless it is well motivated.
180− Some(cached)
181− if {
182− let cached_set = cx.cached_set.borrow();
183− cached_set.contains(cached.id)
184− } =>
185− {
186− cached
187− }
188− _ => {
189− let mut key = NodeKey::NONE;
190− let id = CachedSet::insert(cx, false, template, |nested_cx| {
191− let node = self.inner.render(nested_cx);
192− key = node.key();
193− node
194− });
195− let cached = CachedNode { id, key };
196− self.cached.set(Some(cached));
197− cached
198− }
199− };
200−
201− cached.into()
202− }
203}
134}
204
135
205−#[cfg(feature = "serde")]
206−impl<R> serde::Serialize for Cached<R>
136+impl<R: Render> Render for Cached<R> {
207−where
137+ fn render<'a, 'bump>(&'a self, _: &'bump Bump) -> Node<'bump>
208− R: Default + serde::Serialize,
209−{
210− fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
211 where
138 where
212− S: serde::Serializer,
139+ 'a: 'bump,
213 {
140 {
214− self.inner.serialize(serializer)
141+ let mut cached = self.cached.borrow_mut();
215− }
216−}
217
142
218−#[cfg(feature = "serde")]
143+ if let Some(cached) = cached.as_ref() {
219−impl<'de, R> serde::Deserialize<'de> for Cached<R>
144+ // The cached node is actually a self-reference, so it has the `'a`
220−where
145+ // lifetime.
221− R: Default + serde::Deserialize<'de>,
146+ let cached: Node<'a> = cached.clone();
222−{
147+
223− fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
148+ // But the `'a` lifetime outlives `'bump`, so we can safely convert
224− where
149+ // it to the narrower `'bump` lifetime.
225− D: serde::Deserializer<'de>,
150+ let cached: Node<'bump> = cached;
226− {
151+
227− R::deserialize(deserializer).map(Cached::new)
152+ // Return the cached rendering!
153+ return cached;
154+ }
155+
156+ // We don't have a cached node. Render into our own `Bump`, cache it for
157+ // future renders, and return it. Same lifetimes as above.
158+ let node: Node<'a> = self.inner.render(&self.bump);
159+ *cached = Some(unsafe { extend_node_lifetime(node.clone()) });
160+ let node: Node<'bump> = node;
161+ node
228 }
162 }
229}
163}
src/cached_set.rsdeleted−186
@@ -1,186 +0,0 @@
1−use crate::{
2− events::EventsRegistry,
3− node::{Node, NodeKind},
4− render_context::RenderContext,
5−};
6−use bumpalo::Bump;
7−use fxhash::{FxHashMap, FxHashSet};
8−use std::sync::atomic::{AtomicUsize, Ordering};
9−use std::u32;
10−use wasm_bindgen::prelude::*;
11−
12−static ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
13−
14−pub_unstable_internal! {
15− #[derive(Debug, Default)]
16− pub(crate) struct CachedSet {
17− items: FxHashMap<CacheId, CacheEntry>,
18− }
19−}
20−
21−pub_unstable_internal! {
22− #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
23− pub(crate) struct CacheId(u32);
24−}
25−
26−#[derive(Debug)]
27−pub(crate) struct CacheEntry {
28− bump: Bump,
29−
30− // Actually a reference into `bump`.
31− node: *const Node<'static>,
32−
33− // All the other cached entries that `node` references (including transitive
34− // edges). By eagerly following transitive edges and recording them the
35− // once, we don't have to repeat the tracing every time we want to garbage
36− // collect cache entries.
37− edges: FxHashSet<CacheId>,
38−
39− // The template for this cached virtual subtree.
40− //
41− // When a cache entry has a template, that means that the `ChangeList` will
42− // lazily build a physical DOM subtree based on the template, and when we
43− // create new versions of this cached result, we clone the template's
44− // physical DOM subtree and then modify it, rather than building the cached
45− // result up from scratch. This is a nice win because we bounce between JS
46− // and DOM methods less often, and get the C++ DOM implementation to do most
47− // of the subtree construction for us.
48− template: Option<CacheId>,
49−
50− // Whether this entry should never be garbage collected. Typically only
51− // templates are pinned.
52− pinned: bool,
53−}
54−
55−impl From<CacheId> for u32 {
56− #[inline]
57− fn from(id: CacheId) -> u32 {
58− id.0
59− }
60−}
61−
62−impl CachedSet {
63− pub(crate) fn new_roots_set(&self) -> FxHashSet<CacheId> {
64− let mut roots = FxHashSet::default();
65− roots.reserve(self.items.len());
66− roots
67− }
68−
69− pub(crate) fn gc(&mut self, registry: &mut EventsRegistry, roots: FxHashSet<CacheId>) {
70− let mut marked = FxHashSet::default();
71− marked.reserve(self.items.len());
72−
73− for root in roots {
74− if marked.insert(root) {
75− let entry = self
76− .items
77− .get(&root)
78− .expect_throw("CachedSet::gc: should have root in cached set");
79−
80− // NB: no need to recurse here because `edges` already contains
81− // transitive edges!
82− marked.extend(entry.edges.iter().cloned());
83− }
84− }
85−
86− self.items.retain(|id, entry| {
87− let keep = entry.pinned || marked.contains(id);
88− if !keep {
89− let node: &Node = unsafe { &*entry.node };
90− registry.remove_subtree(node);
91− }
92− keep
93− });
94− }
95−
96− // Trace all the transitive edges to other cached entries that the given
97− // node has.
98− fn trace(&self, node: &Node) -> FxHashSet<CacheId> {
99− let mut edges = FxHashSet::default();
100− self.trace_recursive(&mut edges, node);
101− edges
102− }
103−
104− fn trace_recursive(&self, edges: &mut FxHashSet<CacheId>, node: &Node) {
105− match &node.kind {
106− NodeKind::Text(_) => return,
107− NodeKind::Cached(c) => {
108− debug_assert!(self.items.contains_key(&c.id));
109− edges.insert(c.id);
110− edges.extend(
111− self.items
112− .get(&c.id)
113− .expect_throw("CachedSet::trace: should have c.id in cached set")
114− .edges
115− .iter()
116− .cloned(),
117− );
118− }
119− NodeKind::Element(el) => {
120− for child in el.children {
121− self.trace_recursive(edges, child);
122− }
123− }
124− }
125− }
126−
127− fn next_id(&mut self) -> CacheId {
128− let next = ID_COUNTER.fetch_add(1, Ordering::AcqRel) as u32;
129− let next = if next == u32::MAX { None } else { Some(next) };
130− CacheId(next.expect_throw("ID_COUNTER overflowed"))
131− }
132−
133− pub(crate) fn insert<F>(
134− cx: &mut RenderContext,
135− pinned: bool,
136− template: Option<CacheId>,
137− f: F,
138− ) -> CacheId
139− where
140− F: for<'a> FnOnce(&mut RenderContext<'a>) -> Node<'a>,
141− {
142− let set = cx.cached_set;
143− let bump = Bump::new();
144− let (node, edges) = {
145− let mut nested_cx = RenderContext::new(&bump, cx.cached_set, cx.templates);
146− let node = f(&mut nested_cx);
147− let node = bump.alloc(node);
148− let edges = {
149− let set = set.borrow();
150− set.trace(node)
151− };
152− (
153− node as *mut Node<'_> as usize as *const Node<'static>,
154− edges,
155− )
156− };
157−
158− let entry = CacheEntry {
159− bump,
160− node,
161− edges,
162− template,
163− pinned,
164− };
165−
166− let mut set = set.borrow_mut();
167− let id = set.next_id();
168− set.items.insert(id, entry);
169− id
170− }
171−
172− /// Does the cached set contain a cached node with the given id?
173− pub fn contains(&self, id: CacheId) -> bool {
174− self.items.contains_key(&id)
175− }
176−
177− /// Get the cached node and its template (if any) for the given cache id.
178− pub fn get(&self, id: CacheId) -> (&Node, Option<CacheId>) {
179− let entry = self
180− .items
181− .get(&id)
182− .expect_throw("CachedSet::get: should have id in set");
183− let node: &Node = unsafe { &*entry.node };
184− (node, entry.template)
185− }
186−}
src/change_list.rsnew file+379
@@ -0,0 +1,379 @@
1+use crate::Listener;
2+use bumpalo::Bump;
3+use std::fmt;
4+use std::sync::Once;
5+use wasm_bindgen::prelude::*;
6+
7+pub mod js {
8+ use wasm_bindgen::prelude::*;
9+
10+ #[wasm_bindgen]
11+ extern "C" {
12+ #[derive(Clone, Debug)]
13+ pub type ChangeList;
14+
15+ #[wasm_bindgen(constructor)]
16+ pub fn new(container: &web_sys::Node) -> ChangeList;
17+
18+ #[wasm_bindgen(structural, method)]
19+ pub fn unmount(this: &ChangeList);
20+
21+ #[wasm_bindgen(structural, method, js_name = addChangeListRange)]
22+ pub fn add_change_list_range(this: &ChangeList, start: usize, len: usize);
23+
24+ #[wasm_bindgen(structural, method, js_name = applyChanges)]
25+ pub fn apply_changes(this: &ChangeList, memory: JsValue);
26+
27+ #[wasm_bindgen(structural, method, js_name = initEventsTrampoline)]
28+ pub fn init_events_trampoline(
29+ this: &ChangeList,
30+ trampoline: &Closure<Fn(web_sys::Event, u32, u32)>,
31+ );
32+ }
33+}
34+
35+pub(crate) struct ChangeList {
36+ bump: Bump,
37+ js: js::ChangeList,
38+ events_trampoline: Option<Closure<Fn(web_sys::Event, u32, u32)>>,
39+}
40+
41+impl Drop for ChangeList {
42+ fn drop(&mut self) {
43+ debug!("Dropping ChangeList");
44+ self.js.unmount();
45+ }
46+}
47+
48+impl fmt::Debug for ChangeList {
49+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50+ f.debug_struct("ChangeList")
51+ .field("bump", &self.bump)
52+ .field("js", &self.js)
53+ .field("events_trampoline", &"..")
54+ .finish()
55+ }
56+}
57+
58+impl ChangeList {
59+ pub(crate) fn new(container: &web_sys::Node) -> ChangeList {
60+ // XXX: Because wasm-bindgen-test doesn't support third party JS
61+ // dependencies, we can't use `wasm_bindgen(module = "...")` for our
62+ // `ChangeList` JS import. Instead, this *should* be a local JS snippet,
63+ // but that isn't implemented yet:
64+ // https://github.com/rustwasm/rfcs/pull/6
65+ static EVAL: Once = Once::new();
66+ EVAL.call_once(|| {
67+ js_sys::eval(include_str!("../js/change-list.js"))
68+ .expect("should eval change-list.js OK");
69+ });
70+
71+ let bump = Bump::new();
72+ let js = js::ChangeList::new(container);
73+ ChangeList {
74+ bump,
75+ js,
76+ events_trampoline: None,
77+ }
78+ }
79+
80+ pub(crate) fn apply_changes(&mut self) {
81+ let js = &self.js;
82+ unsafe {
83+ self.bump.each_allocated_chunk(|ch| {
84+ js.add_change_list_range(ch.as_ptr() as usize, ch.len());
85+ });
86+ }
87+ js.apply_changes(wasm_bindgen::memory());
88+ self.bump.reset();
89+ }
90+
91+ pub(crate) fn init_events_trampoline(
92+ &mut self,
93+ trampoline: Closure<Fn(web_sys::Event, u32, u32)>,
94+ ) {
95+ debug_assert!(self.events_trampoline.is_none());
96+ self.js.init_events_trampoline(&trampoline);
97+ self.events_trampoline = Some(trampoline);
98+ }
99+}
100+
101+#[repr(u32)]
102+#[derive(Clone, Copy, Debug)]
103+enum ChangeDiscriminant {
104+ /// Immediates: `(pointer, length)`
105+ ///
106+ /// Stack: `[... TextNode] -> [... TextNode]`
107+ ///
108+ /// ```text
109+ /// stack.top().textContent = String(pointer, length)
110+ /// ```
111+ SetText = 0,
112+
113+ /// Immediates: `()`
114+ ///
115+ /// Stack: `[... Node] -> [...]`
116+ ///
117+ /// ```text
118+ /// node = stack.pop()
119+ /// while (node.nextSibling) {
120+ /// node.nextSibling.remove();
121+ /// }
122+ /// node.remove()
123+ /// ```
124+ RemoveSelfAndNextSiblings = 1,
125+
126+ /// Immediates: `()`
127+ ///
128+ /// Stack: `[... Node Node] -> [... Node]`
129+ ///
130+ /// ```text
131+ /// new = stack.pop()
132+ /// old = stack.pop()
133+ /// old.replaceWith(new)
134+ /// stack.push(new)
135+ /// ```
136+ ReplaceWith = 2,
137+
138+ /// Immediates: `(pointer1, length1, pointer2, length2)`
139+ ///
140+ /// Stack: `[... Node] -> [... Node]`
141+ ///
142+ /// ```text
143+ /// stack.top().setAttribute(String(pointer1, length1), String(pointer2, length2))
144+ /// ```
145+ SetAttribute = 3,
146+
147+ /// Immediates: (pointer, length)
148+ ///
149+ /// Stack: `[... Node] -> [... Node]`
150+ ///
151+ /// ```text
152+ /// stack.top().removeAttribute(String(pointer, length))
153+ /// ```
154+ RemoveAttribute = 4,
155+
156+ /// Immediates: `()`
157+ ///
158+ /// Stack: `[... Node] -> [... Node node]`
159+ ///
160+ /// ```text
161+ /// stack.push(stack.top().firstChild)
162+ /// ```
163+ PushFirstChild = 5,
164+
165+ /// Immediates: `()`
166+ ///
167+ /// Stack: `[... Node] -> [... Node]`
168+ ///
169+ /// ```text
170+ /// stack.push(stack.pop().nextSibling)
171+ /// ```
172+ PopPushNextSibling = 6,
173+
174+ /// Immediates: `()`
175+ ///
176+ /// Stack: `[... T] -> [...]`
177+ ///
178+ /// ```text
179+ /// stack.pop()
180+ /// ```
181+ Pop = 7,
182+
183+ /// Immediates: `()`
184+ ///
185+ /// Stack: `[... Node Node] -> [... Node]`
186+ ///
187+ /// ```text
188+ /// child = stack.pop()
189+ /// stack.top().appendChild(child)
190+ /// ```
191+ AppendChild = 8,
192+
193+ /// Immediates: `(pointer, length)`
194+ ///
195+ /// Stack: `[...] -> [... Node]`
196+ ///
197+ /// ```text
198+ /// stack.push(createTextNode(String(pointer, length)))
199+ /// ```
200+ CreateTextNode = 9,
201+
202+ /// Immediates: `(pointer, length)`
203+ ///
204+ /// Stack: `[...] -> [... Node]`
205+ ///
206+ /// ```text
207+ /// stack.push(createElement(String(pointer, length))
208+ /// ```
209+ CreateElement = 10,
210+
211+ /// Immediate: `(pointer, length, A, B)`
212+ ///
213+ /// Stack: `[... Node] -> [... Node]`
214+ ///
215+ /// ```text
216+ /// event = String(pointer, length)
217+ /// callback = ProxyToRustCallback(A, B);
218+ /// stack.top().addEventListener(event, callback);
219+ /// ```
220+ NewEventListener = 11,
221+
222+ /// Immediate: `(pointer, length, A, B)`
223+ ///
224+ /// Stack: `[... Node] -> [... Node]`
225+ ///
226+ /// ```text
227+ /// event = String(pointer, length)
228+ /// new_callback = ProxyToRustCallback(A, B);
229+ /// stack.top().updateEventlistener(new_callback)
230+ /// ```
231+ UpdateEventListener = 12,
232+
233+ /// Immediates: `(pointer, length)`
234+ ///
235+ /// Stack: `[... Node] -> [... Node]`
236+ ///
237+ /// ```text
238+ /// stack.top().removeEventListener(String(pointer, length));
239+ /// ```
240+ RemoveEventListener = 13,
241+}
242+
243+// Allocation utilities to ensure that we only allocation sequences of `u32`s
244+// into the change list's bump arena without any padding. This helps maintain
245+// the invariants required for `Bump::each_allocated_chunk`'s safety.
246+impl ChangeList {
247+ // Allocate an opcode with zero immediates.
248+ fn op0(&self, discriminant: ChangeDiscriminant) {
249+ self.bump.alloc(discriminant as u32);
250+ }
251+
252+ // Note: no 1-immediate opcodes at this time.
253+
254+ // Allocate an opcode with two immediates.
255+ fn op2(&self, discriminant: ChangeDiscriminant, a: u32, b: u32) {
256+ self.bump.alloc([discriminant as u32, a, b]);
257+ }
258+
259+ // Note: no 3-immediate opcodes at this time.
260+
261+ // Allocate an opcode with four immediates.
262+ fn op4(&self, discriminant: ChangeDiscriminant, a: u32, b: u32, c: u32, d: u32) {
263+ self.bump.alloc([discriminant as u32, a, b, c, d]);
264+ }
265+}
266+
267+impl ChangeList {
268+ pub(crate) fn emit_set_text(&self, text: &str) {
269+ debug!("emit_set_text({:?})", text);
270+ self.op2(
271+ ChangeDiscriminant::SetText,
272+ text.as_ptr() as u32,
273+ text.len() as u32,
274+ );
275+ }
276+
277+ pub(crate) fn emit_remove_self_and_next_siblings(&self) {
278+ debug!("emit_remove_self_and_next_siblings()");
279+ self.op0(ChangeDiscriminant::RemoveSelfAndNextSiblings);
280+ }
281+
282+ pub(crate) fn emit_replace_with(&self) {
283+ debug!("emit_replace_with()");
284+ self.op0(ChangeDiscriminant::ReplaceWith);
285+ }
286+
287+ pub(crate) fn emit_set_attribute(&self, name: &str, value: &str) {
288+ debug!("emit_set_attribute({:?}, {:?})", name, value);
289+ self.op4(
290+ ChangeDiscriminant::SetAttribute,
291+ name.as_ptr() as u32,
292+ name.len() as u32,
293+ value.as_ptr() as u32,
294+ value.len() as u32,
295+ );
296+ }
297+
298+ pub(crate) fn emit_remove_attribute(&self, name: &str) {
299+ debug!("emit_remove_attribute({:?})", name);
300+ self.op2(
301+ ChangeDiscriminant::RemoveAttribute,
302+ name.as_ptr() as u32,
303+ name.len() as u32,
304+ );
305+ }
306+
307+ pub(crate) fn emit_push_first_child(&self) {
308+ debug!("emit_push_first_child()");
309+ self.op0(ChangeDiscriminant::PushFirstChild);
310+ }
311+
312+ pub(crate) fn emit_pop_push_next_sibling(&self) {
313+ debug!("emit_pop_push_next_sibling()");
314+ self.op0(ChangeDiscriminant::PopPushNextSibling);
315+ }
316+
317+ pub(crate) fn emit_pop(&self) {
318+ debug!("emit_pop()");
319+ self.op0(ChangeDiscriminant::Pop);
320+ }
321+
322+ pub(crate) fn emit_append_child(&self) {
323+ debug!("emit_append_child()");
324+ self.op0(ChangeDiscriminant::AppendChild);
325+ }
326+
327+ pub(crate) fn emit_create_text_node(&self, text: &str) {
328+ debug!("emit_create_text_node({:?})", text);
329+ self.op2(
330+ ChangeDiscriminant::CreateTextNode,
331+ text.as_ptr() as u32,
332+ text.len() as u32,
333+ );
334+ }
335+
336+ pub(crate) fn emit_create_element(&self, tag_name: &str) {
337+ debug!("emit_create_element({:?})", tag_name);
338+ self.op2(
339+ ChangeDiscriminant::CreateElement,
340+ tag_name.as_ptr() as u32,
341+ tag_name.len() as u32,
342+ );
343+ }
344+
345+ pub(crate) fn emit_new_event_listener(&self, listener: &Listener) {
346+ debug!("emit_new_event_listener({:?})", listener);
347+ let (a, b) = listener.get_callback_parts();
348+ debug_assert!(a != 0);
349+ self.op4(
350+ ChangeDiscriminant::NewEventListener,
351+ listener.event.as_ptr() as u32,
352+ listener.event.len() as u32,
353+ a,
354+ b,
355+ );
356+ }
357+
358+ pub(crate) fn emit_update_event_listener(&self, listener: &Listener) {
359+ debug!("emit_update_event_listener({:?})", listener);
360+ let (a, b) = listener.get_callback_parts();
361+ debug_assert!(a != 0);
362+ self.op4(
363+ ChangeDiscriminant::UpdateEventListener,
364+ listener.event.as_ptr() as u32,
365+ listener.event.len() as u32,
366+ a,
367+ b,
368+ );
369+ }
370+
371+ pub(crate) fn emit_remove_event_listener(&self, event: &str) {
372+ debug!("emit_remove_event_listener({:?})", event);
373+ self.op2(
374+ ChangeDiscriminant::RemoveEventListener,
375+ event.as_ptr() as u32,
376+ event.len() as u32,
377+ );
378+ }
379+}
src/change_list/emitter.rsdeleted−292
@@ -1,292 +0,0 @@
1−//! The `InstructionEmitter` is responsible for encoding change list
2−//! instructions and ensuring that each instruction has the correct number of
3−//! immediates. It ensures that the resulting change list instruction stream is
4−//! *syntactically* correct (opcodes have the correct arity of immediate
5−//! arguments, etc), while `ChangeListBuilder` wraps an emitter and additionally
6−//! ensures that the resulting change list program is *semantically* correct
7−//! (doesn't reference cached strings before they've been added to the cache,
8−//! etc).
9−//!
10−//! We encode the instructions directly into a dedicated bump arena. We
11−//! eventually pass the bump arena's chunks to the interpreter in JS, so it is
12−//! critical that nothing other than change list instructions are allocated
13−//! inside this bump, and that the instructions themselves do not contain any
14−//! padding or uninitialized memory. See the documentation for the the
15−//! `Bump::each_allocated_chunk` method for details.
16−
17−use bumpalo::Bump;
18−
19−#[derive(Debug)]
20−pub(crate) struct InstructionEmitter {
21− bump: Bump,
22−}
23−
24−impl InstructionEmitter {
25− /// Construct a new `InstructionEmitter` with its own bump arena.
26− pub fn new() -> InstructionEmitter {
27− let bump = Bump::new();
28− InstructionEmitter { bump }
29− }
30−
31− /// Invoke the given function with each of the allocated instruction
32− /// sequences that this emitter has built up.
33− #[cfg_attr(feature = "xxx-unstable-internal-use-only", allow(dead_code))]
34− pub fn each_instruction_sequence<F>(&mut self, f: F)
35− where
36− F: FnMut(&[u8]),
37− {
38− // Note: the safety invariants required for `each_allocated_chunk` are
39− // maintained by the fact that everything we encode as allocations in
40− // the bump arena are in the form of `u32`s, and therefore we don't have
41− // any uninitialized memory padding in the arena.
42− unsafe {
43− self.bump.each_allocated_chunk(f);
44− }
45− }
46−
47− /// Reset to an empty sequence of instructions.
48− pub fn reset(&mut self) {
49− self.bump.reset();
50− }
51−}
52−
53−macro_rules! define_change_list_instructions {
54− ( $(
55− $( #[$attr:meta] )*
56− $name:ident (
57− $($immediate:ident),*
58− ) = $discriminant:expr,
59− )* ) => {
60− impl InstructionEmitter {
61− $(
62− $( #[$attr] )*
63− #[inline]
64− pub fn $name(&self $(, $immediate: u32)*) {
65− self.bump.alloc_with(|| [$discriminant $(, $immediate )* ]);
66− }
67− )*
68− }
69− }
70−}
71−
72−define_change_list_instructions! {
73− /// Stack: `[... TextNode] -> [... TextNode]`
74− ///
75− /// ```text
76− /// stack.top().textContent = readString(pointer, length)
77− /// ```
78− set_text(pointer, length) = 0,
79−
80− /// Stack: `[... Node] -> [...]`
81− ///
82− /// ```text
83− /// node = stack.pop()
84− /// while (node.nextSibling) {
85− /// node.nextSibling.remove();
86− /// }
87− /// node.remove()
88− /// ```
89− remove_self_and_next_siblings() = 1,
90−
91− /// Stack: `[... Node Node] -> [... Node]`
92− ///
93− /// ```text
94− /// new = stack.pop()
95− /// old = stack.pop()
96− /// old.replaceWith(new)
97− /// stack.push(new)
98− /// ```
99− replace_with() = 2,
100−
101− /// Stack: `[... Node] -> [... Node]`
102− ///
103− /// ```text
104− /// stack.top().setAttribute(getCachedString(attribute_key), getCachedString(value_key))
105− /// ```
106− set_attribute(attribute_key, value_key) = 3,
107−
108− /// Stack: `[... Node] -> [... Node]`
109− ///
110− /// ```text
111− /// stack.top().removeAttribute(getCachedString(attribute_key))
112− /// ```
113− remove_attribute(attribute_key) = 4,
114−
115− /// Stack: `[... Node] -> [... Node Node]`
116− ///
117− /// ```text
118− /// parent = stack.top()
119− /// child = parent.childNodes[parent.childNodes.length - n - 1]
120− /// stack.push(child)
121− /// ```
122− push_reverse_child(n) = 5,
123−
124− /// Stack: `[... Node Node] -> [... Node Node]`
125− ///
126− /// ```text
127− /// stack.pop();
128− /// parent = stack.top();
129− /// child = parent.childNodes[n]
130− /// stack.push(child)
131− /// ```
132− pop_push_child(n) = 6,
133−
134− /// Stack: `[... T] -> [...]`
135− ///
136− /// ```text
137− /// stack.pop()
138− /// ```
139− pop() = 7,
140−
141− /// Stack: `[... Node Node] -> [... Node]`
142− ///
143− /// ```text
144− /// child = stack.pop()
145− /// stack.top().appendChild(child)
146− /// ```
147− append_child() = 8,
148−
149− /// Stack: `[...] -> [... Node]`
150− ///
151− /// ```text
152− /// stack.push(document.createTextNode(readString(pointer, length)))
153− /// ```
154− create_text_node(pointer, length) = 9,
155−
156− /// Stack: `[...] -> [... Node]`
157− ///
158− /// ```text
159− /// stack.push(document.createElement(getCachedString(tag_name_key))
160− /// ```
161− create_element(tag_name_key) = 10,
162−
163− /// Stack: `[... Node] -> [... Node]`
164− ///
165− /// ```text
166− /// event = getCachedString(event_key)
167− /// callback = createProxyToRustCallback(a, b)
168− /// stack.top().addEventListener(event, callback)
169− /// ```
170− new_event_listener(event_key, a, b) = 11,
171−
172− /// Stack: `[... Node] -> [... Node]`
173− ///
174− /// ```text
175− /// event = getCachedString(event_key)
176− /// new_callback = createProxyToRustCallback(a, b);
177− /// stack.top().updateEventlistener(new_callback)
178− /// ```
179− update_event_listener(event_key, a, b) = 12,
180−
181− /// Stack: `[... Node] -> [... Node]`
182− ///
183− /// ```text
184− /// stack.top().removeEventListener(getCachedString(event_key));
185− /// ```
186− remove_event_listener(event_key) = 13,
187−
188− /// Stack: `[...] -> [...]`
189− ///
190− /// ```text
191− /// addCachedString(readString(pointer, length), key);
192− /// ```
193− add_cached_string(pointer, length, key) = 14,
194−
195− /// Stack: `[...] -> [...]`
196− ///
197− /// ```text
198− /// dropCachedString(key);
199− /// ```
200− drop_cached_string(key) = 15,
201−
202− /// Stack: `[...] -> [... Node]`
203− ///
204− /// ```text
205− /// tag_name = getCachedString(tag_name_key)
206− /// namespace = getCachedString(tag_name_key)
207− /// stack.push(document.createElementNS(tag_name, namespace))
208− /// ```
209− create_element_ns(tag_name_key, namespace_key) = 16,
210−
211− /// Stack: `[...] -> [...]`
212− ///
213− /// ```text
214− /// parent = stack.top()
215− /// children = parent.childNodes
216− /// temp = temp_base
217− /// for i in start .. end:
218− /// temporaries[temp] = children[i]
219− /// temp += 1
220− /// ```
221− save_children_to_temporaries(temp_base, start, end) = 17,
222−
223− /// Stack: `[... Node] -> [... Node Node]`
224− ///
225− /// ```text
226− /// parent = stack.top()
227− /// child = parent.childNodes[n]
228− /// stack.push(child)
229− /// ```
230− push_child(n) = 18,
231−
232− /// Stack: `[...] -> [... Node]`
233− ///
234− /// ```text
235− /// stack.push(temporaries[temp])
236− /// ```
237− push_temporary(temp) = 19,
238−
239− /// Stack: `[... Node Node] -> [... Node]`
240− ///
241− /// ```text
242− /// before = stack.pop()
243− /// after = stack.pop()
244− /// after.insertBefore(before)
245− /// stack.push(before)
246− /// ```
247− insert_before() = 20,
248−
249− /// Stack: `[... Node Node] -> [... Node Node]`
250− ///
251− /// ```text
252− /// stack.pop()
253− /// parent = stack.top()
254− /// child = parent.childNodes[parent.childNodes.length - n - 1]
255− /// stack.push(child)
256− /// ```
257− pop_push_reverse_child(n) = 21,
258−
259− /// Stack: `[... Node] -> [... Node]`
260− ///
261− /// ```text
262− /// parent = stack.top()
263− /// child = parent.childNodes[n]
264− /// child.remove()
265− /// ```
266− remove_child(n) = 22,
267−
268− /// Stack: `[... Node] -> [... Node]`
269− ///
270− /// ```text
271− /// class = getCachedString(class)
272− /// node = stack.top()
273− /// node.className = class
274− /// ```
275− set_class(class) = 23,
276−
277− /// Stack: `[... Node] -> [... Node]`
278− ///
279− /// ```text
280− /// template = stack.top()
281− /// saveTemplate(id, template)
282− /// ```
283− save_template(id) = 24,
284−
285− /// Stack: `[...] -> [... Node]`
286− ///
287− /// ```text
288− /// template = getTemplate(id)
289− /// stack.push(template.cloneNode(true))
290− /// ```
291− push_template(id) = 25,
292−}
src/change_list/js.rsdeleted−40
@@ -1,40 +0,0 @@
1−cfg_if::cfg_if! {
2− if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] {
3− #[derive(Clone, Debug)]
4− pub struct ChangeListInterpreter {}
5− impl ChangeListInterpreter {
6− pub fn new(_container: &crate::Element) -> ChangeListInterpreter {
7− ChangeListInterpreter {}
8− }
9− pub fn unmount(&self) {}
10− pub fn add_change_list_range(&self, _start: usize, _len: usize) {}
11− pub fn init_events_trampoline(&self, _trampoline: &crate::EventsTrampoline) {}
12− }
13− } else {
14− use wasm_bindgen::prelude::*;
15−
16− #[wasm_bindgen(module = "/js/change-list-interpreter.js")]
17− extern "C" {
18− #[derive(Clone, Debug)]
19− pub type ChangeListInterpreter;
20−
21− #[wasm_bindgen(constructor)]
22− pub fn new(container: &web_sys::Element) -> ChangeListInterpreter;
23−
24− #[wasm_bindgen(structural, method)]
25− pub fn unmount(this: &ChangeListInterpreter);
26−
27− #[wasm_bindgen(structural, method, js_name = addChangeListRange)]
28− pub fn add_change_list_range(this: &ChangeListInterpreter, start: usize, len: usize);
29−
30− #[wasm_bindgen(structural, method, js_name = applyChanges)]
31− pub fn apply_changes(this: &ChangeListInterpreter, memory: JsValue);
32−
33− #[wasm_bindgen(structural, method, js_name = initEventsTrampoline)]
34− pub fn init_events_trampoline(
35− this: &ChangeListInterpreter,
36− trampoline: &crate::EventsTrampoline,
37− );
38− }
39− }
40−}
src/change_list/mod.rsdeleted−353
@@ -1,353 +0,0 @@
1−pub(crate) mod emitter;
2−pub(crate) mod strings;
3−pub(crate) mod traversal;
4−
5−// Note: has to be `pub` because of `wasm-bindgen` visibility restrictions.
6−pub mod js;
7−
8−use self::emitter::InstructionEmitter;
9−use self::strings::{StringKey, StringsCache};
10−use self::traversal::{MoveTo, Traversal};
11−use crate::{cached_set::CacheId, Listener};
12−use fxhash::FxHashSet;
13−
14−#[derive(Debug)]
15−pub(crate) struct ChangeListPersistentState {
16− strings: StringsCache,
17− emitter: InstructionEmitter,
18− traversal: Traversal,
19− interpreter: js::ChangeListInterpreter,
20− templates: FxHashSet<CacheId>,
21−}
22−
23−pub(crate) struct ChangeListBuilder<'a> {
24− state: &'a mut ChangeListPersistentState,
25− next_temporary: u32,
26− forcing_new_listeners: bool,
27−}
28−
29−impl Drop for ChangeListPersistentState {
30− fn drop(&mut self) {
31− self.interpreter.unmount();
32− }
33−}
34−
35−impl ChangeListPersistentState {
36− pub(crate) fn new(container: &crate::Element) -> ChangeListPersistentState {
37− let strings = StringsCache::new();
38− let emitter = InstructionEmitter::new();
39− let traversal = Traversal::new();
40− let interpreter = js::ChangeListInterpreter::new(container);
41− let templates = Default::default();
42− ChangeListPersistentState {
43− strings,
44− emitter,
45− traversal,
46− interpreter,
47− templates,
48− }
49− }
50−
51− pub(crate) fn init_events_trampoline(&mut self, trampoline: &crate::EventsTrampoline) {
52− self.interpreter.init_events_trampoline(trampoline);
53− }
54−
55− pub(crate) fn builder<'a>(&'a mut self) -> ChangeListBuilder<'a> {
56− ChangeListBuilder {
57− state: self,
58− next_temporary: 0,
59− forcing_new_listeners: false,
60− }
61− }
62−}
63−
64−cfg_if::cfg_if! {
65− if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] {
66− impl ChangeListBuilder<'_> {
67− pub(crate) fn finish(self) {
68− self.state.strings.drop_unused_strings(&self.state.emitter);
69−
70− // Nothing to actually apply the changes to.
71−
72− self.state.emitter.reset();
73− self.state.traversal.reset();
74− }
75− }
76− } else {
77− impl ChangeListBuilder<'_> {
78− pub(crate) fn finish(self) {
79− self.state.strings.drop_unused_strings(&self.state.emitter);
80−
81− // Apply the changes.
82− let interpreter = &self.state.interpreter;
83− self.state.emitter.each_instruction_sequence(|seq| {
84− interpreter.add_change_list_range(seq.as_ptr() as usize, seq.len());
85− });
86− interpreter.apply_changes(wasm_bindgen::memory());
87−
88− self.state.emitter.reset();
89− self.state.traversal.reset();
90− }
91− }
92− }
93−}
94−
95−/// Traversal methods.
96−impl ChangeListBuilder<'_> {
97− pub fn go_down(&mut self) {
98− self.state.traversal.down();
99− }
100−
101− pub fn go_down_to_child(&mut self, index: usize) {
102− self.state.traversal.down();
103− self.state.traversal.sibling(index);
104− }
105−
106− pub fn go_down_to_reverse_child(&mut self, index: usize) {
107− self.state.traversal.down();
108− self.state.traversal.reverse_sibling(index);
109− }
110−
111− pub fn go_up(&mut self) {
112− self.state.traversal.up();
113− }
114−
115− pub fn go_to_sibling(&mut self, index: usize) {
116− self.state.traversal.sibling(index);
117− }
118−
119− pub fn go_to_temp_sibling(&mut self, temp: u32) {
120− self.state.traversal.up();
121− self.state.traversal.down_to_temp(temp);
122− }
123−
124− pub fn go_down_to_temp_child(&mut self, temp: u32) {
125− self.state.traversal.down_to_temp(temp);
126− }
127−
128− pub fn commit_traversal(&mut self) {
129− if self.state.traversal.is_committed() {
130− return;
131− }
132−
133− for mv in self.state.traversal.commit() {
134− match mv {
135− MoveTo::Parent => {
136− debug!("emit: pop");
137− self.state.emitter.pop();
138− }
139− MoveTo::Child(n) => {
140− debug!("emit: push_child({})", n);
141− self.state.emitter.push_child(n);
142− }
143− MoveTo::ReverseChild(n) => {
144− debug!("emit: push_reverse_child({})", n);
145− self.state.emitter.push_reverse_child(n);
146− }
147− MoveTo::Sibling(n) => {
148− debug!("emit: pop_push_child({})", n);
149− self.state.emitter.pop_push_child(n);
150− }
151− MoveTo::ReverseSibling(n) => {
152− debug!("emit: pop_push_reverse_child({})", n);
153− self.state.emitter.pop_push_reverse_child(n);
154− }
155− MoveTo::TempChild(temp) => {
156− debug!("emit: push_temporary({})", temp);
157− self.state.emitter.push_temporary(temp);
158− }
159− }
160− }
161− }
162−
163− pub fn traversal_is_committed(&self) -> bool {
164− self.state.traversal.is_committed()
165− }
166−}
167−
168−impl ChangeListBuilder<'_> {
169− pub fn next_temporary(&self) -> u32 {
170− self.next_temporary
171− }
172−
173− pub fn set_next_temporary(&mut self, next_temporary: u32) {
174− self.next_temporary = next_temporary;
175− }
176−
177− pub fn save_children_to_temporaries(&mut self, start: usize, end: usize) -> u32 {
178− debug_assert!(self.traversal_is_committed());
179− debug_assert!(start < end);
180− let temp_base = self.next_temporary;
181− debug!(
182− "emit: save_children_to_temporaries({}, {}, {})",
183− temp_base, start, end
184− );
185− self.next_temporary = temp_base + (end - start) as u32;
186− self.state
187− .emitter
188− .save_children_to_temporaries(temp_base, start as u32, end as u32);
189− temp_base
190− }
191−
192− pub fn push_temporary(&self, temp: u32) {
193− debug_assert!(self.traversal_is_committed());
194− debug!("emit: push_temporary({})", temp);
195− self.state.emitter.push_temporary(temp);
196− }
197−
198− pub fn remove_child(&self, child: usize) {
199− debug_assert!(self.traversal_is_committed());
200− debug!("emit: remove_child({})", child);
201− self.state.emitter.remove_child(child as u32);
202− }
203−
204− pub fn insert_before(&self) {
205− debug_assert!(self.traversal_is_committed());
206− debug!("emit: insert_before()");
207− self.state.emitter.insert_before();
208− }
209−
210− pub fn ensure_string(&mut self, string: &str) -> StringKey {
211− self.state
212− .strings
213− .ensure_string(string, &self.state.emitter)
214− }
215−
216− pub fn set_text(&self, text: &str) {
217− debug_assert!(self.traversal_is_committed());
218− debug!("emit: set_text({:?})", text);
219− self.state
220− .emitter
221− .set_text(text.as_ptr() as u32, text.len() as u32);
222− }
223−
224− pub fn remove_self_and_next_siblings(&self) {
225− debug_assert!(self.traversal_is_committed());
226− debug!("emit: remove_self_and_next_siblings()");
227− self.state.emitter.remove_self_and_next_siblings();
228− }
229−
230− pub fn replace_with(&self) {
231− debug_assert!(self.traversal_is_committed());
232− debug!("emit: replace_with()");
233− self.state.emitter.replace_with();
234− }
235−
236− pub fn set_attribute(&mut self, name: &str, value: &str, is_namespaced: bool) {
237− debug_assert!(self.traversal_is_committed());
238− if name == "class" && !is_namespaced {
239− let class_id = self.ensure_string(value);
240− debug!("emit: set_class({:?})", value);
241− self.state.emitter.set_class(class_id.into());
242− } else {
243− let name_id = self.ensure_string(name);
244− let value_id = self.ensure_string(value);
245− debug!("emit: set_attribute({:?}, {:?})", name, value);
246− self.state
247− .emitter
248− .set_attribute(name_id.into(), value_id.into());
249− }
250− }
251−
252− pub fn remove_attribute(&mut self, name: &str) {
253− debug_assert!(self.traversal_is_committed());
254− debug!("emit: remove_attribute({:?})", name);
255− let name_id = self.ensure_string(name);
256− self.state.emitter.remove_attribute(name_id.into());
257− }
258−
259− pub fn append_child(&self) {
260− debug_assert!(self.traversal_is_committed());
261− debug!("emit: append_child()");
262− self.state.emitter.append_child();
263− }
264−
265− pub fn create_text_node(&self, text: &str) {
266− debug_assert!(self.traversal_is_committed());
267− debug!("emit: create_text_node({:?})", text);
268− self.state
269− .emitter
270− .create_text_node(text.as_ptr() as u32, text.len() as u32);
271− }
272−
273− pub fn create_element(&mut self, tag_name: &str) {
274− debug_assert!(self.traversal_is_committed());
275− debug!("emit: create_element({:?})", tag_name);
276− let tag_name_id = self.ensure_string(tag_name);
277− self.state.emitter.create_element(tag_name_id.into());
278− }
279−
280− pub fn create_element_ns(&mut self, tag_name: &str, ns: &str) {
281− debug_assert!(self.traversal_is_committed());
282− debug!("emit: create_element_ns({:?}, {:?})", tag_name, ns);
283− let tag_name_id = self.ensure_string(tag_name);
284− let ns_id = self.ensure_string(ns);
285− self.state
286− .emitter
287− .create_element_ns(tag_name_id.into(), ns_id.into());
288− }
289−
290− pub fn push_force_new_listeners(&mut self) -> bool {
291− let old = self.forcing_new_listeners;
292− self.forcing_new_listeners = true;
293− old
294− }
295−
296− pub fn pop_force_new_listeners(&mut self, previous: bool) {
297− debug_assert!(self.forcing_new_listeners);
298− self.forcing_new_listeners = previous;
299− }
300−
301− pub fn new_event_listener(&mut self, listener: &Listener) {
302− debug_assert!(self.traversal_is_committed());
303− debug!("emit: new_event_listener({:?})", listener);
304− let (a, b) = listener.get_callback_parts();
305− debug_assert!(a != 0);
306− let event_id = self.ensure_string(listener.event);
307− self.state.emitter.new_event_listener(event_id.into(), a, b);
308− }
309−
310− pub fn update_event_listener(&mut self, listener: &Listener) {
311− debug_assert!(self.traversal_is_committed());
312−
313− if self.forcing_new_listeners {
314− self.new_event_listener(listener);
315− return;
316− }
317−
318− debug!("emit: update_event_listener({:?})", listener);
319− let (a, b) = listener.get_callback_parts();
320− debug_assert!(a != 0);
321− let event_id = self.ensure_string(listener.event);
322− self.state
323− .emitter
324− .update_event_listener(event_id.into(), a, b);
325− }
326−
327− pub fn remove_event_listener(&mut self, event: &str) {
328− debug_assert!(self.traversal_is_committed());
329− debug!("emit: remove_event_listener({:?})", event);
330− let event_id = self.ensure_string(event);
331− self.state.emitter.remove_event_listener(event_id.into());
332− }
333−
334− #[inline]
335− pub fn has_template(&mut self, id: CacheId) -> bool {
336− self.state.templates.contains(&id)
337− }
338−
339− pub fn save_template(&mut self, id: CacheId) {
340− debug_assert!(self.traversal_is_committed());
341− debug_assert!(!self.has_template(id));
342− debug!("emit: save_template({:?})", id);
343− self.state.templates.insert(id);
344− self.state.emitter.save_template(id.into());
345− }
346−
347− pub fn push_template(&mut self, id: CacheId) {
348− debug_assert!(self.traversal_is_committed());
349− debug_assert!(self.has_template(id));
350− debug!("emit: push_template({:?})", id);
351− self.state.emitter.push_template(id.into());
352− }
353−}
src/change_list/strings.rsdeleted−66
@@ -1,66 +0,0 @@
1−use crate::change_list::emitter::InstructionEmitter;
2−use fxhash::FxHashMap;
3−
4−#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5−pub struct StringKey(u32);
6−
7−impl From<StringKey> for u32 {
8− #[inline]
9− fn from(key: StringKey) -> u32 {
10− key.0
11− }
12−}
13−
14−#[derive(Debug)]
15−struct StringsCacheEntry {
16− key: StringKey,
17− used: bool,
18−}
19−
20−#[derive(Debug, Default)]
21−pub(crate) struct StringsCache {
22− entries: FxHashMap<String, StringsCacheEntry>,
23− next_string_key: u32,
24−}
25−
26−impl StringsCache {
27− /// Create a new, empty strings cache.
28− pub fn new() -> StringsCache {
29− Default::default()
30− }
31−
32− /// Ensure that the given string is cached, and get its key.
33− pub fn ensure_string(&mut self, string: &str, emitter: &InstructionEmitter) -> StringKey {
34− if let Some(entry) = self.entries.get_mut(string) {
35− entry.used = true;
36− entry.key
37− } else {
38− let key = StringKey(self.next_string_key);
39− self.next_string_key += 1;
40− let entry = StringsCacheEntry { key, used: true };
41− self.entries.insert(string.to_string(), entry);
42− emitter.add_cached_string(string.as_ptr() as u32, string.len() as u32, key.into());
43− key
44− }
45− }
46−
47− pub fn drop_unused_strings(&mut self, emitter: &InstructionEmitter) {
48− self.entries.retain(|string, entry| {
49− if entry.used {
50− // Since this entry was used during while rendering this frame,
51− // it is likely going to be used for the next frame as well. So
52− // we keep it in the cache so that we don't have to repopulate
53− // and resync the cache next frame (assuming it is reused), but
54− // we set the `used` flag to false so that if it is not used
55− // next frame, it will be cleaned up.
56− entry.used = false;
57− true
58− } else {
59− let key = entry.key.into();
60− debug!("emit: drop_cached_string({}) = {:?}", key, string);
61− emitter.drop_cached_string(key);
62− false
63− }
64− });
65− }
66−}
src/change_list/traversal.rsdeleted−344
@@ -1,344 +0,0 @@
1−//! Keeps track of where we are moving in a DOM tree, and shortens traversal
2−//! paths between mutations to their minimal number of operations.
3−
4−#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5−pub enum MoveTo {
6− /// Move from the current node up to its parent.
7− Parent,
8−
9− /// Move to the current node's n^th child.
10− Child(u32),
11−
12− /// Move to the current node's n^th from last child.
13− ReverseChild(u32),
14−
15− /// Move to the n^th sibling. Not relative from the current
16− /// location. Absolute indexed within all of the current siblings.
17− Sibling(u32),
18−
19− /// Move to the n^th from last sibling. Not relative from the current
20− /// location. Absolute indexed within all of the current siblings.
21− ReverseSibling(u32),
22−
23− /// Move down to the given saved temporary child.
24− TempChild(u32),
25−}
26−
27−#[derive(Debug)]
28−pub struct Traversal {
29− uncommitted: Vec<MoveTo>,
30−}
31−
32−impl Traversal {
33− /// Construct a new `Traversal` with its internal storage backed by the
34− /// given bump arena.
35− pub fn new() -> Traversal {
36− Traversal {
37− uncommitted: Vec::with_capacity(32),
38− }
39− }
40−
41− /// Move the traversal up in the tree.
42− pub fn up(&mut self) {
43− match self.uncommitted.last() {
44− Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => {
45− self.uncommitted.pop();
46− self.uncommitted.push(MoveTo::Parent);
47− }
48− Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) | Some(MoveTo::ReverseChild(_)) => {
49− self.uncommitted.pop();
50− // And we're back at the parent.
51− }
52− _ => {
53− self.uncommitted.push(MoveTo::Parent);
54− }
55− }
56− }
57−
58− /// Move the traversal down in the tree to the first child of the current
59− /// node.
60− pub fn down(&mut self) {
61− if let Some(&MoveTo::Parent) = self.uncommitted.last() {
62− self.uncommitted.pop();
63− self.sibling(0);
64− } else {
65− self.uncommitted.push(MoveTo::Child(0));
66− }
67− }
68−
69− /// Move the traversal to the n^th sibling.
70− pub fn sibling(&mut self, index: usize) {
71− let index = index as u32;
72− match self.uncommitted.last_mut() {
73− Some(MoveTo::Sibling(ref mut n)) | Some(MoveTo::Child(ref mut n)) => {
74− *n = index;
75− }
76− Some(MoveTo::ReverseSibling(_)) => {
77− self.uncommitted.pop();
78− self.uncommitted.push(MoveTo::Sibling(index));
79− }
80− Some(MoveTo::TempChild(_)) | Some(MoveTo::ReverseChild(_)) => {
81− self.uncommitted.pop();
82− self.uncommitted.push(MoveTo::Child(index))
83− }
84− _ => {
85− self.uncommitted.push(MoveTo::Sibling(index));
86− }
87− }
88− }
89−
90− /// Move the the n^th from last sibling.
91− pub fn reverse_sibling(&mut self, index: usize) {
92− let index = index as u32;
93− match self.uncommitted.last_mut() {
94− Some(MoveTo::ReverseSibling(ref mut n)) | Some(MoveTo::ReverseChild(ref mut n)) => {
95− *n = index;
96− }
97− Some(MoveTo::Sibling(_)) => {
98− self.uncommitted.pop();
99− self.uncommitted.push(MoveTo::ReverseSibling(index));
100− }
101− Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) => {
102− self.uncommitted.pop();
103− self.uncommitted.push(MoveTo::ReverseChild(index))
104− }
105− _ => {
106− self.uncommitted.push(MoveTo::ReverseSibling(index));
107− }
108− }
109− }
110−
111− /// Go to the given saved temporary.
112− pub fn down_to_temp(&mut self, temp: u32) {
113− match self.uncommitted.last() {
114− Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => {
115− self.uncommitted.pop();
116− }
117− Some(MoveTo::Parent)
118− | Some(MoveTo::TempChild(_))
119− | Some(MoveTo::Child(_))
120− | Some(MoveTo::ReverseChild(_))
121− | None => {
122− // Can't remove moves to parents since we rely on their stack
123− // pops.
124− }
125− }
126− self.uncommitted.push(MoveTo::TempChild(temp));
127− }
128−
129− /// Are all the traversal's moves committed? That is, are there no moves
130− /// that have *not* been committed yet?
131− #[inline]
132− pub fn is_committed(&self) -> bool {
133− self.uncommitted.is_empty()
134− }
135−
136− /// Commit this traversals moves and return the optimized path from the last
137− /// commit.
138− #[inline]
139− pub fn commit(&mut self) -> Moves {
140− Moves {
141− inner: self.uncommitted.drain(..),
142− }
143− }
144−
145− #[inline]
146− pub fn reset(&mut self) {
147− self.uncommitted.clear();
148− }
149−}
150−
151−pub struct Moves<'a> {
152− inner: std::vec::Drain<'a, MoveTo>,
153−}
154−
155−impl Iterator for Moves<'_> {
156− type Item = MoveTo;
157−
158− #[inline]
159− fn next(&mut self) -> Option<MoveTo> {
160− self.inner.next()
161− }
162−}
163−
164−#[cfg(test)]
165−mod tests {
166− use super::*;
167−
168− #[test]
169− fn test_traversal() {
170− fn t<F>(f: F) -> Box<dyn FnMut(&mut Traversal)>
171− where
172− F: 'static + FnMut(&mut Traversal),
173− {
174− Box::new(f) as _
175− }
176−
177− for (mut traverse, expected_moves) in vec![
178− (
179− t(|t| {
180− t.down();
181− }),
182− vec![MoveTo::Child(0)],
183− ),
184− (
185− t(|t| {
186− t.up();
187− }),
188− vec![MoveTo::Parent],
189− ),
190− (
191− t(|t| {
192− t.sibling(42);
193− }),
194− vec![MoveTo::Sibling(42)],
195− ),
196− (
197− t(|t| {
198− t.down();
199− t.up();
200− }),
201− vec![],
202− ),
203− (
204− t(|t| {
205− t.down();
206− t.sibling(2);
207− t.up();
208− }),
209− vec![],
210− ),
211− (
212− t(|t| {
213− t.down();
214− t.sibling(3);
215− }),
216− vec![MoveTo::Child(3)],
217− ),
218− (
219− t(|t| {
220− t.down();
221− t.sibling(4);
222− t.sibling(8);
223− }),
224− vec![MoveTo::Child(8)],
225− ),
226− (
227− t(|t| {
228− t.sibling(1);
229− t.sibling(1);
230− }),
231− vec![MoveTo::Sibling(1)],
232− ),
233− (
234− t(|t| {
235− t.reverse_sibling(3);
236− }),
237− vec![MoveTo::ReverseSibling(3)],
238− ),
239− (
240− t(|t| {
241− t.down();
242− t.reverse_sibling(3);
243− }),
244− vec![MoveTo::ReverseChild(3)],
245− ),
246− (
247− t(|t| {
248− t.down();
249− t.reverse_sibling(3);
250− t.up();
251− }),
252− vec![],
253− ),
254− (
255− t(|t| {
256− t.down();
257− t.reverse_sibling(3);
258− t.reverse_sibling(6);
259− }),
260− vec![MoveTo::ReverseChild(6)],
261− ),
262− (
263− t(|t| {
264− t.up();
265− t.reverse_sibling(3);
266− t.reverse_sibling(6);
267− }),
268− vec![MoveTo::Parent, MoveTo::ReverseSibling(6)],
269− ),
270− (
271− t(|t| {
272− t.up();
273− t.sibling(3);
274− t.sibling(6);
275− }),
276− vec![MoveTo::Parent, MoveTo::Sibling(6)],
277− ),
278− (
279− t(|t| {
280− t.sibling(3);
281− t.sibling(6);
282− t.up();
283− }),
284− vec![MoveTo::Parent],
285− ),
286− (
287− t(|t| {
288− t.reverse_sibling(3);
289− t.reverse_sibling(6);
290− t.up();
291− }),
292− vec![MoveTo::Parent],
293− ),
294− (
295− t(|t| {
296− t.down();
297− t.down_to_temp(3);
298− }),
299− vec![MoveTo::Child(0), MoveTo::TempChild(3)],
300− ),
301− (
302− t(|t| {
303− t.down_to_temp(3);
304− t.sibling(5);
305− }),
306− vec![MoveTo::Child(5)],
307− ),
308− (
309− t(|t| {
310− t.down_to_temp(3);
311− t.reverse_sibling(5);
312− }),
313− vec![MoveTo::ReverseChild(5)],
314− ),
315− (
316− t(|t| {
317− t.down_to_temp(3);
318− t.up();
319− }),
320− vec![],
321− ),
322− (
323− t(|t| {
324− t.sibling(2);
325− t.up();
326− t.down_to_temp(3);
327− }),
328− vec![MoveTo::Parent, MoveTo::TempChild(3)],
329− ),
330− (
331− t(|t| {
332− t.up();
333− t.down_to_temp(3);
334− }),
335− vec![MoveTo::Parent, MoveTo::TempChild(3)],
336− ),
337− ] {
338− let mut traversal = Traversal::new();
339− traverse(&mut traversal);
340− let actual_moves: Vec<_> = traversal.commit().collect();
341− assert_eq!(actual_moves, expected_moves);
342− }
343− }
344−}
src/diff.rsdeleted−1125
@@ -1,1125 +0,0 @@
1−use crate::{
2− cached_set::{CacheId, CachedSet},
3− change_list::ChangeListBuilder,
4− events::EventsRegistry,
5− node::{Attribute, ElementNode, Listener, Node, NodeKind, TextNode},
6−};
7−use fxhash::{FxHashMap, FxHashSet};
8−use std::cmp::Ordering;
9−use std::u32;
10−use wasm_bindgen::UnwrapThrowExt;
11−
12−// Diff the `old` node with the `new` node. Emits instructions to modify a
13−// physical DOM node that reflects `old` into something that reflects `new`.
14−//
15−// Upon entry to this function, the physical DOM node must be on the top of the
16−// change list stack:
17−//
18−// [... node]
19−//
20−// The change list stack is in the same state when this function exits.
21−pub(crate) fn diff(
22− cached_set: &CachedSet,
23− change_list: &mut ChangeListBuilder,
24− registry: &mut EventsRegistry,
25− old: &Node,
26− new: &Node,
27− cached_roots: &mut FxHashSet<CacheId>,
28−) {
29− match (&new.kind, &old.kind) {
30− (
31− &NodeKind::Text(TextNode { text: new_text }),
32− &NodeKind::Text(TextNode { text: old_text }),
33− ) => {
34− if new_text != old_text {
35− change_list.commit_traversal();
36− change_list.set_text(new_text);
37− }
38− }
39−
40− (&NodeKind::Text(_), &NodeKind::Element(_)) => {
41− change_list.commit_traversal();
42− create(cached_set, change_list, registry, new, cached_roots);
43− registry.remove_subtree(&old);
44− change_list.replace_with();
45− }
46−
47− (&NodeKind::Element(_), &NodeKind::Text(_)) => {
48− change_list.commit_traversal();
49− create(cached_set, change_list, registry, new, cached_roots);
50− // Note: text nodes cannot have event listeners, so we don't need to
51− // remove the old node's listeners from our registry her.
52− change_list.replace_with();
53− }
54−
55− (
56− &NodeKind::Element(ElementNode {
57− key: _,
58− tag_name: new_tag_name,
59− listeners: new_listeners,
60− attributes: new_attributes,
61− children: new_children,
62− namespace: new_namespace,
63− }),
64− &NodeKind::Element(ElementNode {
65− key: _,
66− tag_name: old_tag_name,
67− listeners: old_listeners,
68− attributes: old_attributes,
69− children: old_children,
70− namespace: old_namespace,
71− }),
72− ) => {
73− if new_tag_name != old_tag_name || new_namespace != old_namespace {
74− change_list.commit_traversal();
75− create(cached_set, change_list, registry, new, cached_roots);
76− registry.remove_subtree(&old);
77− change_list.replace_with();
78− return;
79− }
80− diff_listeners(change_list, registry, old_listeners, new_listeners);
81− diff_attributes(change_list, old_attributes, new_attributes, new_namespace.is_some());
82− diff_children(
83− cached_set,
84− change_list,
85− registry,
86− old_children,
87− new_children,
88− cached_roots,
89− );
90− }
91−
92− // Both the new and old nodes are cached.
93− (&NodeKind::Cached(ref new), &NodeKind::Cached(ref old)) => {
94− cached_roots.insert(new.id);
95−
96− if new.id == old.id {
97− // This is the same cached node, so nothing has changed!
98− return;
99− }
100−
101− let (new, new_template) = cached_set.get(new.id);
102− let (old, old_template) = cached_set.get(old.id);
103− if new_template == old_template {
104− // If they are both using the same template, then just diff the
105− // subtrees.
106− diff(cached_set, change_list, registry, old, new, cached_roots);
107− } else {
108− // Otherwise, they are probably different enough that
109− // re-constructing the subtree from scratch should be faster.
110− // This doubly holds true if we have a new template.
111− change_list.commit_traversal();
112− create_and_replace(
113− cached_set,
114− change_list,
115− registry,
116− new_template,
117− old,
118− new,
119− cached_roots,
120− );
121− }
122− }
123−
124− // New cached node when the old node was not cached. In this scenario,
125− // we assume that they are pretty different, and it isn't worth diffing
126− // the subtrees, so we just create the new cached node afresh.
127− (&NodeKind::Cached(ref c), _) => {
128− change_list.commit_traversal();
129− cached_roots.insert(c.id);
130− let (new, new_template) = cached_set.get(c.id);
131− create_and_replace(
132− cached_set,
133− change_list,
134− registry,
135− new_template,
136− old,
137− new,
138− cached_roots,
139− );
140− }
141−
142− // Old cached node and new non-cached node. Again, assume that they are
143− // probably pretty different and create the new non-cached node afresh.
144− (_, &NodeKind::Cached(_)) => {
145− change_list.commit_traversal();
146− create(cached_set, change_list, registry, new, cached_roots);
147− registry.remove_subtree(&old);
148− change_list.replace_with();
149− }
150− }
151−}
152−
153−// Diff event listeners between `old` and `new`.
154−//
155−// The listeners' node must be on top of the change list stack:
156−//
157−// [... node]
158−//
159−// The change list stack is left unchanged.
160−fn diff_listeners(
161− change_list: &mut ChangeListBuilder,
162− registry: &mut EventsRegistry,
163− old: &[Listener],
164− new: &[Listener],
165−) {
166− if !old.is_empty() || !new.is_empty() {
167− change_list.commit_traversal();
168− }
169−
170− 'outer1: for new_l in new {
171− unsafe {
172− // Safety relies on removing `new_l` from the registry manually at
173− // the end of its lifetime. This happens below in the `'outer2`
174− // loop, and elsewhere in diffing when removing old dom trees.
175− registry.add(new_l);
176− }
177−
178− for old_l in old {
179− if new_l.event == old_l.event {
180− change_list.update_event_listener(new_l);
181− continue 'outer1;
182− }
183− }
184−
185− change_list.new_event_listener(new_l);
186− }
187−
188− 'outer2: for old_l in old {
189− registry.remove(old_l);
190−
191− for new_l in new {
192− if new_l.event == old_l.event {
193− continue 'outer2;
194− }
195− }
196− change_list.remove_event_listener(old_l.event);
197− }
198−}
199−
200−// Diff a node's attributes.
201−//
202−// The attributes' node must be on top of the change list stack:
203−//
204−// [... node]
205−//
206−// The change list stack is left unchanged.
207−fn diff_attributes(change_list: &mut ChangeListBuilder, old: &[Attribute], new: &[Attribute], is_namespaced: bool) {
208− // Do O(n^2) passes to add/update and remove attributes, since
209− // there are almost always very few attributes.
210− 'outer: for new_attr in new {
211− if new_attr.is_volatile() {
212− change_list.commit_traversal();
213− change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
214− } else {
215− for old_attr in old {
216− if old_attr.name == new_attr.name {
217− if old_attr.value != new_attr.value {
218− change_list.commit_traversal();
219− change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
220− }
221− continue 'outer;
222− }
223− }
224−
225− change_list.commit_traversal();
226− change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
227− }
228− }
229−
230− 'outer2: for old_attr in old {
231− for new_attr in new {
232− if old_attr.name == new_attr.name {
233− continue 'outer2;
234− }
235− }
236−
237− change_list.commit_traversal();
238− change_list.remove_attribute(old_attr.name);
239− }
240−}
241−
242−// Diff the given set of old and new children.
243−//
244−// The parent must be on top of the change list stack when this function is
245−// entered:
246−//
247−// [... parent]
248−//
249−// the change list stack is in the same state when this function returns.
250−fn diff_children(
251− cached_set: &CachedSet,
252− change_list: &mut ChangeListBuilder,
253− registry: &mut EventsRegistry,
254− old: &[Node],
255− new: &[Node],
256− cached_roots: &mut FxHashSet<CacheId>,
257−) {
258− if new.is_empty() {
259− if !old.is_empty() {
260− change_list.commit_traversal();
261− remove_all_children(change_list, registry, old);
262− }
263− return;
264− }
265−
266− if new.len() == 1 {
267− match (old.first(), &new[0]) {
268− (
269− Some(&Node {
270− kind: NodeKind::Text(TextNode { text: old_text }),
271− }),
272− &Node {
273− kind: NodeKind::Text(TextNode { text: new_text }),
274− },
275− ) if old_text == new_text => {
276− // Don't take this fast path...
277− }
278− (
279− _,
280− &Node {
281− kind: NodeKind::Text(TextNode { text }),
282− },
283− ) => {
284− change_list.commit_traversal();
285− change_list.set_text(text);
286− for o in old {
287− registry.remove_subtree(o);
288− }
289− return;
290− }
291− (_, _) => {}
292− }
293− }
294−
295− if old.is_empty() {
296− if !new.is_empty() {
297− change_list.commit_traversal();
298− create_and_append_children(cached_set, change_list, registry, new, cached_roots);
299− }
300− return;
301− }
302−
303− let new_is_keyed = new[0].key().is_some();
304− let old_is_keyed = old[0].key().is_some();
305−
306− debug_assert!(
307− new.iter().all(|n| n.key().is_some() == new_is_keyed),
308− "all siblings must be keyed or all siblings must be non-keyed"
309− );
310− debug_assert!(
311− old.iter().all(|o| o.key().is_some() == old_is_keyed),
312− "all siblings must be keyed or all siblings must be non-keyed"
313− );
314−
315− if new_is_keyed && old_is_keyed {
316− let t = change_list.next_temporary();
317− diff_keyed_children(cached_set, change_list, registry, old, new, cached_roots);
318− change_list.set_next_temporary(t);
319− } else {
320− diff_non_keyed_children(cached_set, change_list, registry, old, new, cached_roots);
321− }
322−}
323−
324−// Diffing "keyed" children.
325−//
326−// With keyed children, we care about whether we delete, move, or create nodes
327−// versus mutate existing nodes in place. Presumably there is some sort of CSS
328−// transition animation that makes the virtual DOM diffing algorithm
329−// observable. By specifying keys for nodes, we know which virtual DOM nodes
330−// must reuse (or not reuse) the same physical DOM nodes.
331−//
332−// This is loosely based on Inferno's keyed patching implementation. However, we
333−// have to modify the algorithm since we are compiling the diff down into change
334−// list instructions that will be executed later, rather than applying the
335−// changes to the DOM directly as we compare virtual DOMs.
336−//
337−// https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
338−//
339−// When entering this function, the parent must be on top of the change list
340−// stack:
341−//
342−// [... parent]
343−//
344−// Upon exiting, the change list stack is in the same state.
345−fn diff_keyed_children(
346− cached_set: &CachedSet,
347− change_list: &mut ChangeListBuilder,
348− registry: &mut EventsRegistry,
349− old: &[Node],
350− new: &[Node],
351− cached_roots: &mut FxHashSet<CacheId>,
352−) {
353− if cfg!(debug_assertions) {
354− let mut keys = FxHashSet::default();
355− let mut assert_unique_keys = |children: &[Node]| {
356− keys.clear();
357− for child in children {
358− let key = child.key();
359− debug_assert!(
360− key.is_some(),
361− "if any sibling is keyed, all siblings must be keyed"
362− );
363− keys.insert(key);
364− }
365− debug_assert_eq!(
366− children.len(),
367− keys.len(),
368− "keyed siblings must each have a unique key"
369− );
370− };
371− assert_unique_keys(old);
372− assert_unique_keys(new);
373− }
374−
375− // First up, we diff all the nodes with the same key at the beginning of the
376− // children.
377− //
378− // `shared_prefix_count` is the count of how many nodes at the start of
379− // `new` and `old` share the same keys.
380− let shared_prefix_count =
381− match diff_keyed_prefix(cached_set, change_list, registry, old, new, cached_roots) {
382− KeyedPrefixResult::Finished => return,
383− KeyedPrefixResult::MoreWorkToDo(count) => count,
384− };
385−
386− // Next, we find out how many of the nodes at the end of the children have
387− // the same key. We do _not_ diff them yet, since we want to emit the change
388− // list instructions such that they can be applied in a single pass over the
389− // DOM. Instead, we just save this information for later.
390− //
391− // `shared_suffix_count` is the count of how many nodes at the end of `new`
392− // and `old` share the same keys.
393− let shared_suffix_count = old[shared_prefix_count..]
394− .iter()
395− .rev()
396− .zip(new[shared_prefix_count..].iter().rev())
397− .take_while(|&(old, new)| old.key() == new.key())
398− .count();
399−
400− let old_shared_suffix_start = old.len() - shared_suffix_count;
401− let new_shared_suffix_start = new.len() - shared_suffix_count;
402−
403− // Ok, we now hopefully have a smaller range of children in the middle
404− // within which to re-order nodes with the same keys, remove old nodes with
405− // now-unused keys, and create new nodes with fresh keys.
406− diff_keyed_middle(
407− cached_set,
408− change_list,
409− registry,
410− &old[shared_prefix_count..old_shared_suffix_start],
411− &new[shared_prefix_count..new_shared_suffix_start],
412− cached_roots,
413− shared_prefix_count,
414− shared_suffix_count,
415− old_shared_suffix_start,
416− );
417−
418− // Finally, diff the nodes at the end of `old` and `new` that share keys.
419− let old_suffix = &old[old_shared_suffix_start..];
420− let new_suffix = &new[new_shared_suffix_start..];
421− debug_assert_eq!(old_suffix.len(), new_suffix.len());
422− if !old_suffix.is_empty() {
423− diff_keyed_suffix(
424− cached_set,
425− change_list,
426− registry,
427− old_suffix,
428− new_suffix,
429− cached_roots,
430− new_shared_suffix_start,
431− );
432− }
433−}
434−
435−enum KeyedPrefixResult {
436− // Fast path: we finished diffing all the children just by looking at the
437− // prefix of shared keys!
438− Finished,
439− // There is more diffing work to do. Here is a count of how many children at
440− // the beginning of `new` and `old` we already processed.
441− MoreWorkToDo(usize),
442−}
443−
444−// Diff the prefix of children in `new` and `old` that share the same keys in
445−// the same order.
446−//
447−// Upon entry of this function, the change list stack must be:
448−//
449−// [... parent]
450−//
451−// Upon exit, the change list stack is the same.
452−fn diff_keyed_prefix(
453− cached_set: &CachedSet,
454− change_list: &mut ChangeListBuilder,
455− registry: &mut EventsRegistry,
456− old: &[Node],
457− new: &[Node],
458− cached_roots: &mut FxHashSet<CacheId>,
459−) -> KeyedPrefixResult {
460− change_list.go_down();
461− let mut shared_prefix_count = 0;
462−
463− for (i, (old, new)) in old.iter().zip(new.iter()).enumerate() {
464− if old.key() != new.key() {
465− break;
466− }
467−
468− change_list.go_to_sibling(i);
469− diff(cached_set, change_list, registry, old, new, cached_roots);
470− shared_prefix_count += 1;
471− }
472−
473− // If that was all of the old children, then create and append the remaining
474− // new children and we're finished.
475− if shared_prefix_count == old.len() {
476− change_list.go_up();
477− change_list.commit_traversal();
478− create_and_append_children(
479− cached_set,
480− change_list,
481− registry,
482− &new[shared_prefix_count..],
483− cached_roots,
484− );
485− return KeyedPrefixResult::Finished;
486− }
487−
488− // And if that was all of the new children, then remove all of the remaining
489− // old children and we're finished.
490− if shared_prefix_count == new.len() {
491− change_list.go_to_sibling(shared_prefix_count);
492− change_list.commit_traversal();
493− remove_self_and_next_siblings(change_list, registry, &old[shared_prefix_count..]);
494− return KeyedPrefixResult::Finished;
495− }
496−
497− change_list.go_up();
498− KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
499−}
500−
501−// The most-general, expensive code path for keyed children diffing.
502−//
503−// We find the longest subsequence within `old` of children that are relatively
504−// ordered the same way in `new` (via finding a longest-increasing-subsequence
505−// of the old child's index within `new`). The children that are elements of
506−// this subsequence will remain in place, minimizing the number of DOM moves we
507−// will have to do.
508−//
509−// Upon entry to this function, the change list stack must be:
510−//
511−// [... parent]
512−//
513−// Upon exit from this function, it will be restored to that same state.
514−fn diff_keyed_middle(
515− cached_set: &CachedSet,
516− change_list: &mut ChangeListBuilder,
517− registry: &mut EventsRegistry,
518− old: &[Node],
519− mut new: &[Node],
520− cached_roots: &mut FxHashSet<CacheId>,
521− shared_prefix_count: usize,
522− shared_suffix_count: usize,
523− old_shared_suffix_start: usize,
524−) {
525− // Should have already diffed the shared-key prefixes and suffixes.
526− debug_assert_ne!(new.first().map(|n| n.key()), old.first().map(|o| o.key()));
527− debug_assert_ne!(new.last().map(|n| n.key()), old.last().map(|o| o.key()));
528−
529− // The algorithm below relies upon using `u32::MAX` as a sentinel
530− // value, so if we have that many new nodes, it won't work. This
531− // check is a bit academic (hence only enabled in debug), since
532− // wasm32 doesn't have enough address space to hold that many nodes
533− // in memory.
534− debug_assert!(new.len() < u32::MAX as usize);
535−
536− // Map from each `old` node's key to its index within `old`.
537− let mut old_key_to_old_index = FxHashMap::default();
538− old_key_to_old_index.reserve(old.len());
539− old_key_to_old_index.extend(old.iter().enumerate().map(|(i, o)| (o.key(), i)));
540−
541− // The set of shared keys between `new` and `old`.
542− let mut shared_keys = FxHashSet::default();
543− // Map from each index in `new` to the index of the node in `old` that
544− // has the same key.
545− let mut new_index_to_old_index = Vec::with_capacity(new.len());
546− new_index_to_old_index.extend(new.iter().map(|n| {
547− let key = n.key();
548− if let Some(&i) = old_key_to_old_index.get(&key) {
549− shared_keys.insert(key);
550− i
551− } else {
552− u32::MAX as usize
553− }
554− }));
555−
556− // If none of the old keys are reused by the new children, then we
557− // remove all the remaining old children and create the new children
558− // afresh.
559− if shared_suffix_count == 0 && shared_keys.is_empty() {
560− if shared_prefix_count == 0 {
561− change_list.commit_traversal();
562− remove_all_children(change_list, registry, old);
563− } else {
564− change_list.go_down_to_child(shared_prefix_count);
565− change_list.commit_traversal();
566− remove_self_and_next_siblings(change_list, registry, &old[shared_prefix_count..]);
567− }
568− create_and_append_children(cached_set, change_list, registry, new, cached_roots);
569− return;
570− }
571−
572− // Save each of the old children whose keys are reused in the new
573− // children.
574− let mut old_index_to_temp = vec![u32::MAX; old.len()];
575− let mut start = 0;
576− loop {
577− let end = (start..old.len())
578− .find(|&i| {
579− let key = old[i].key();
580− !shared_keys.contains(&key)
581− })
582− .unwrap_or(old.len());
583−
584− if end - start > 0 {
585− change_list.commit_traversal();
586− let mut t = change_list.save_children_to_temporaries(
587− shared_prefix_count + start,
588− shared_prefix_count + end,
589− );
590− for i in start..end {
591− old_index_to_temp[i] = t;
592− t += 1;
593− }
594− }
595−
596− debug_assert!(end <= old.len());
597− if end == old.len() {
598− break;
599− } else {
600− start = end + 1;
601− }
602− }
603−
604− // Remove any old children whose keys were not reused in the new
605− // children. Remove from the end first so that we don't mess up indices.
606− let mut removed_count = 0;
607− for (i, old_child) in old.iter().enumerate().rev() {
608− if !shared_keys.contains(&old_child.key()) {
609− registry.remove_subtree(old_child);
610− change_list.commit_traversal();
611− change_list.remove_child(i + shared_prefix_count);
612− removed_count += 1;
613− }
614− }
615−
616− // If there aren't any more new children, then we are done!
617− if new.is_empty() {
618− return;
619− }
620−
621− // The longest increasing subsequence within `new_index_to_old_index`. This
622− // is the longest sequence on DOM nodes in `old` that are relatively ordered
623− // correctly within `new`. We will leave these nodes in place in the DOM,
624− // and only move nodes that are not part of the LIS. This results in the
625− // maximum number of DOM nodes left in place, AKA the minimum number of DOM
626− // nodes moved.
627− let mut new_index_is_in_lis = FxHashSet::default();
628− new_index_is_in_lis.reserve(new_index_to_old_index.len());
629− let mut predecessors = vec![0; new_index_to_old_index.len()];
630− let mut starts = vec![0; new_index_to_old_index.len()];
631− longest_increasing_subsequence::lis_with(
632− &new_index_to_old_index,
633− &mut new_index_is_in_lis,
634− |a, b| a < b,
635− &mut predecessors,
636− &mut starts,
637− );
638−
639− // Now we will iterate from the end of the new children back to the
640− // beginning, diffing old children we are reusing and if they aren't in the
641− // LIS moving them to their new destination, or creating new children. Note
642− // that iterating in reverse order lets us use `Node.prototype.insertBefore`
643− // to move/insert children.
644− //
645− // But first, we ensure that we have a child on the change list stack that
646− // we can `insertBefore`. We handle this once before looping over `new`
647− // children, so that we don't have to keep checking on every loop iteration.
648− if shared_suffix_count > 0 {
649− // There is a shared suffix after these middle children. We will be
650− // inserting before that shared suffix, so add the first child of that
651− // shared suffix to the change list stack.
652− //
653− // [... parent]
654− change_list.go_down_to_child(old_shared_suffix_start - removed_count);
655− // [... parent first_child_of_shared_suffix]
656− } else {
657− // There is no shared suffix coming after these middle children.
658− // Therefore we have to process the last child in `new` and move it to
659− // the end of the parent's children if it isn't already there.
660− let last_index = new.len() - 1;
661− let last = new.last().unwrap_throw();
662− new = &new[..new.len() - 1];
663− if shared_keys.contains(&last.key()) {
664− let old_index = new_index_to_old_index[last_index];
665− let temp = old_index_to_temp[old_index];
666− // [... parent]
667− change_list.go_down_to_temp_child(temp);
668− // [... parent last]
669− diff(
670− cached_set,
671− change_list,
672− registry,
673− &old[old_index],
674− last,
675− cached_roots,
676− );
677− if new_index_is_in_lis.contains(&last_index) {
678− // Don't move it, since it is already where it needs to be.
679− } else {
680− change_list.commit_traversal();
681− // [... parent last]
682− change_list.append_child();
683− // [... parent]
684− change_list.go_down_to_temp_child(temp);
685− // [... parent last]
686− }
687− } else {
688− change_list.commit_traversal();
689− // [... parent]
690− create(cached_set, change_list, registry, last, cached_roots);
691− // [... parent last]
692− change_list.append_child();
693− // [... parent]
694− change_list.go_down_to_reverse_child(0);
695− // [... parent last]
696− }
697− }
698−
699− for (new_index, new_child) in new.iter().enumerate().rev() {
700− let old_index = new_index_to_old_index[new_index];
701− if old_index == u32::MAX as usize {
702− debug_assert!(!shared_keys.contains(&new_child.key()));
703− change_list.commit_traversal();
704− // [... parent successor]
705− create(cached_set, change_list, registry, new_child, cached_roots);
706− // [... parent successor new_child]
707− change_list.insert_before();
708− // [... parent new_child]
709− } else {
710− debug_assert!(shared_keys.contains(&new_child.key()));
711− let temp = old_index_to_temp[old_index];
712− debug_assert_ne!(temp, u32::MAX);
713−
714− if new_index_is_in_lis.contains(&new_index) {
715− // [... parent successor]
716− change_list.go_to_temp_sibling(temp);
717− // [... parent new_child]
718− } else {
719− change_list.commit_traversal();
720− // [... parent successor]
721− change_list.push_temporary(temp);
722− // [... parent successor new_child]
723− change_list.insert_before();
724− // [... parent new_child]
725− }
726−
727− diff(
728− cached_set,
729− change_list,
730− registry,
731− &old[old_index],
732− new_child,
733− cached_roots,
734− );
735− }
736− }
737−
738− // [... parent child]
739− change_list.go_up();
740− // [... parent]
741−}
742−
743−// Diff the suffix of keyed children that share the same keys in the same order.
744−//
745−// The parent must be on the change list stack when we enter this function:
746−//
747−// [... parent]
748−//
749−// When this function exits, the change list stack remains the same.
750−fn diff_keyed_suffix(
751− cached_set: &CachedSet,
752− change_list: &mut ChangeListBuilder,
753− registry: &mut EventsRegistry,
754− old: &[Node],
755− new: &[Node],
756− cached_roots: &mut FxHashSet<CacheId>,
757− new_shared_suffix_start: usize,
758−) {
759− debug_assert_eq!(old.len(), new.len());
760− debug_assert!(!old.is_empty());
761−
762− // [... parent]
763− change_list.go_down();
764− // [... parent new_child]
765−
766− for (i, (old_child, new_child)) in old.iter().zip(new.iter()).enumerate() {
767− change_list.go_to_sibling(new_shared_suffix_start + i);
768− diff(
769− cached_set,
770− change_list,
771− registry,
772− old_child,
773− new_child,
774− cached_roots,
775− );
776− }
777−
778− // [... parent]
779− change_list.go_up();
780−}
781−
782−// Diff children that are not keyed.
783−//
784−// The parent must be on the top of the change list stack when entering this
785−// function:
786−//
787−// [... parent]
788−//
789−// the change list stack is in the same state when this function returns.
790−fn diff_non_keyed_children(
791− cached_set: &CachedSet,
792− change_list: &mut ChangeListBuilder,
793− registry: &mut EventsRegistry,
794− old: &[Node],
795− new: &[Node],
796− cached_roots: &mut FxHashSet<CacheId>,
797−) {
798− // Handled these cases in `diff_children` before calling this function.
799− debug_assert!(!new.is_empty());
800− debug_assert!(!old.is_empty());
801−
802− // [... parent]
803− change_list.go_down();
804− // [... parent child]
805−
806− for (i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() {
807− // [... parent prev_child]
808− change_list.go_to_sibling(i);
809− // [... parent this_child]
810− diff(
811− cached_set,
812− change_list,
813− registry,
814− old_child,
815− new_child,
816− cached_roots,
817− );
818− }
819−
820− match old.len().cmp(&new.len()) {
821− Ordering::Greater => {
822− // [... parent prev_child]
823− change_list.go_to_sibling(new.len());
824− // [... parent first_child_to_remove]
825− change_list.commit_traversal();
826− remove_self_and_next_siblings(change_list, registry, &old[new.len()..]);
827− // [... parent]
828− }
829− Ordering::Less => {
830− // [... parent last_child]
831− change_list.go_up();
832− // [... parent]
833− change_list.commit_traversal();
834− create_and_append_children(
835− cached_set,
836− change_list,
837− registry,
838− &new[old.len()..],
839− cached_roots,
840− );
841− }
842− Ordering::Equal => {
843− // [... parent child]
844− change_list.go_up();
845− // [... parent]
846− }
847− }
848−}
849−
850−// Create the given children and append them to the parent node.
851−//
852−// The parent node must currently be on top of the change list stack:
853−//
854−// [... parent]
855−//
856−// When this function returns, the change list stack is in the same state.
857−fn create_and_append_children(
858− cached_set: &CachedSet,
859− change_list: &mut ChangeListBuilder,
860− registry: &mut EventsRegistry,
861− new: &[Node],
862− cached_roots: &mut FxHashSet<CacheId>,
863−) {
864− debug_assert!(change_list.traversal_is_committed());
865− for child in new {
866− create(cached_set, change_list, registry, child, cached_roots);
867− change_list.append_child();
868− }
869−}
870−
871−// Remove all of a node's children.
872−//
873−// The change list stack must have this shape upon entry to this function:
874−//
875−// [... parent]
876−//
877−// When this function returns, the change list stack is in the same state.
878−fn remove_all_children(
879− change_list: &mut ChangeListBuilder,
880− registry: &mut EventsRegistry,
881− old: &[Node],
882−) {
883− debug_assert!(change_list.traversal_is_committed());
884− for child in old {
885− registry.remove_subtree(child);
886− }
887− // Fast way to remove all children: set the node's textContent to an empty
888− // string.
889− change_list.set_text("");
890−}
891−
892−// Remove the current child and all of its following siblings.
893−//
894−// The change list stack must have this shape upon entry to this function:
895−//
896−// [... parent child]
897−//
898−// After the function returns, the child is no longer on the change list stack:
899−//
900−// [... parent]
901−fn remove_self_and_next_siblings(
902− change_list: &mut ChangeListBuilder,
903− registry: &mut EventsRegistry,
904− old: &[Node],
905−) {
906− debug_assert!(change_list.traversal_is_committed());
907− for child in old {
908− registry.remove_subtree(child);
909− }
910− change_list.remove_self_and_next_siblings();
911−}
912−
913−// Emit instructions to create the given virtual node.
914−//
915−// The change list stack may have any shape upon entering this function:
916−//
917−// [...]
918−//
919−// When this function returns, the new node is on top of the change list stack:
920−//
921−// [... node]
922−fn create(
923− cached_set: &CachedSet,
924− change_list: &mut ChangeListBuilder,
925− registry: &mut EventsRegistry,
926− node: &Node,
927− cached_roots: &mut FxHashSet<CacheId>,
928−) {
929− debug_assert!(change_list.traversal_is_committed());
930− match node.kind {
931− NodeKind::Text(TextNode { text }) => {
932− change_list.create_text_node(text);
933− }
934− NodeKind::Element(&ElementNode {
935− key: _,
936− tag_name,
937− listeners,
938− attributes,
939− children,
940− namespace,
941− }) => {
942− if let Some(namespace) = namespace {
943− change_list.create_element_ns(tag_name, namespace);
944− } else {
945− change_list.create_element(tag_name);
946− }
947−
948− for l in listeners {
949− unsafe {
950− registry.add(l);
951− }
952− change_list.new_event_listener(l);
953− }
954−
955− for attr in attributes {
956− change_list.set_attribute(&attr.name, &attr.value, namespace.is_some());
957− }
958−
959− // Fast path: if there is a single text child, it is faster to
960− // create-and-append the text node all at once via setting the
961− // parent's `textContent` in a single change list instruction than
962− // to emit three instructions to (1) create a text node, (2) set its
963− // text content, and finally (3) append the text node to this
964− // parent.
965− if children.len() == 1 {
966− if let Node {
967− kind: NodeKind::Text(TextNode { text }),
968− } = children[0]
969− {
970− change_list.set_text(text);
971− return;
972− }
973− }
974−
975− for child in children {
976− create(cached_set, change_list, registry, child, cached_roots);
977− change_list.append_child();
978− }
979− }
980− NodeKind::Cached(ref c) => {
981− cached_roots.insert(c.id);
982− let (node, template) = cached_set.get(c.id);
983− if let Some(template) = template {
984− create_with_template(
985− cached_set,
986− change_list,
987− registry,
988− template,
989− node,
990− cached_roots,
991− );
992− } else {
993− create(cached_set, change_list, registry, node, cached_roots);
994− }
995− }
996− }
997−}
998−
999−// Get or create the template.
1000−//
1001−// Upon entering this function the change list stack may be in any shape:
1002−//
1003−// [...]
1004−//
1005−// When this function returns, it leaves a freshly cloned copy of the template
1006−// on the top of the change list stack:
1007−//
1008−// [... template]
1009−#[inline]
1010−fn get_or_create_template<'a>(
1011− cached_set: &'a CachedSet,
1012− change_list: &mut ChangeListBuilder,
1013− registry: &mut EventsRegistry,
1014− cached_roots: &mut FxHashSet<CacheId>,
1015− template_id: CacheId,
1016−) -> (&'a Node<'a>, bool) {
1017− let (template, template_template) = cached_set.get(template_id);
1018− debug_assert!(
1019− template_template.is_none(),
1020− "templates should not be templated themselves"
1021− );
1022−
1023− // If we haven't already created and saved the physical DOM subtree for this
1024− // template, do that now.
1025− if change_list.has_template(template_id) {
1026− // Clone the template and push it onto the stack.
1027− //
1028− // [...]
1029− change_list.push_template(template_id);
1030− // [... template]
1031−
1032− (template, true)
1033− } else {
1034− // [...]
1035− create(cached_set, change_list, registry, template, cached_roots);
1036− // [... template]
1037− change_list.save_template(template_id);
1038− // [... template]
1039−
1040− (template, false)
1041− }
1042−}
1043−
1044−fn create_and_replace(
1045− cached_set: &CachedSet,
1046− change_list: &mut ChangeListBuilder,
1047− registry: &mut EventsRegistry,
1048− new_template: Option<CacheId>,
1049− old: &Node,
1050− new: &Node,
1051− cached_roots: &mut FxHashSet<CacheId>,
1052−) {
1053− debug_assert!(change_list.traversal_is_committed());
1054−
1055− if let Some(template_id) = new_template {
1056− let (template, needs_listeners) =
1057− get_or_create_template(cached_set, change_list, registry, cached_roots, template_id);
1058− change_list.replace_with();
1059−
1060− let mut old_forcing = None;
1061− if needs_listeners {
1062− old_forcing = Some(change_list.push_force_new_listeners());
1063− }
1064−
1065− diff(
1066− cached_set,
1067− change_list,
1068− registry,
1069− template,
1070− new,
1071− cached_roots,
1072− );
1073−
1074− if let Some(old) = old_forcing {
1075− change_list.pop_force_new_listeners(old);
1076− }
1077−
1078− change_list.commit_traversal();
1079− } else {
1080− create(cached_set, change_list, registry, new, cached_roots);
1081− change_list.replace_with();
1082− }
1083− registry.remove_subtree(old);
1084−}
1085−
1086−fn create_with_template(
1087− cached_set: &CachedSet,
1088− change_list: &mut ChangeListBuilder,
1089− registry: &mut EventsRegistry,
1090− template_id: CacheId,
1091− node: &Node,
1092− cached_roots: &mut FxHashSet<CacheId>,
1093−) {
1094− debug_assert!(change_list.traversal_is_committed());
1095−
1096− // [...]
1097− let (template, needs_listeners) =
1098− get_or_create_template(cached_set, change_list, registry, cached_roots, template_id);
1099− // [... template]
1100−
1101− // Now diff the node with its template.
1102− //
1103− // We must force adding new listeners instead of updating existing ones,
1104− // since listeners don't get cloned in `cloneNode`.
1105− let mut old_forcing = None;
1106− if needs_listeners {
1107− old_forcing = Some(change_list.push_force_new_listeners());
1108− }
1109−
1110− diff(
1111− cached_set,
1112− change_list,
1113− registry,
1114− template,
1115− node,
1116− cached_roots,
1117− );
1118−
1119− if let Some(old) = old_forcing {
1120− change_list.pop_force_new_listeners(old);
1121− }
1122−
1123− // Make sure that we come back up to the level we were at originally.
1124− change_list.commit_traversal();
1125−}
src/events.rs+83−123
@@ -1,138 +1,98 @@
1−use crate::{node::Node, vdom::VdomInner, Listener};
1+use crate::vdom::{VdomInner, VdomWeak};
2+use crate::{Listener, ListenerCallback};
2use std::cell::RefCell;
3use std::cell::RefCell;
4+use std::collections::HashMap;
5+use std::fmt;
6+use std::mem;
3use std::rc::{Rc, Weak};
7use std::rc::{Rc, Weak};
8+use wasm_bindgen::closure::Closure;
9+use wasm_bindgen::prelude::*;
4
10
5−cfg_if::cfg_if! {
6− if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] {
7− #[derive(Debug)]
8− pub(crate) struct EventsRegistry {}
9− impl EventsRegistry {
10− pub(crate) fn new(_vdom: Weak<VdomInner>) -> (
11− Rc<RefCell<EventsRegistry>>,
12− crate::EventsTrampoline,
13− ) {
14− (Rc::new(RefCell::new(EventsRegistry {})), ())
15− }
16− pub(crate) fn remove(&mut self, _listener: &Listener) {}
17− pub(crate) fn remove_subtree(&mut self, _node: &Node) {}
18− pub(crate) unsafe fn add<'a>(&mut self, _listener: &'a Listener<'a>) {}
19− pub(crate) fn clear_active_listeners(&mut self) {}
20− }
21− } else {
22− use crate::{
23− node::{ElementNode, ListenerCallback, NodeKind},
24− vdom::VdomWeak,
25− };
26− use fxhash::FxHashMap;
27− use std::fmt;
28− use std::mem;
29− use wasm_bindgen::closure::Closure;
30− use wasm_bindgen::prelude::*;
31−
32− /// The events registry manages event listeners for a virtual DOM.
11+/// The events registry manages event listeners for a virtual DOM.
33− ///
12+///
34− /// The events registry is persistent across virtual DOM rendering and double
13+/// The events registry is persistent across virtual DOM rendering and double
35− /// buffering.
14+/// buffering.
36− pub(crate) struct EventsRegistry {
15+pub(crate) struct EventsRegistry {
37− vdom: Weak<VdomInner>,
16+ vdom: Weak<VdomInner>,
38− active: FxHashMap<(u32, u32), ListenerCallback<'static>>,
17+ active: HashMap<(u32, u32), ListenerCallback<'static>>,
39− }
18+}
40−
41− impl fmt::Debug for EventsRegistry {
42− fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43− f.debug_struct("EventsRegistry")
44− .field("active", &self.active.keys().collect::<Vec<_>>())
45− .finish()
46− }
47− }
48−
49− impl EventsRegistry {
50− /// Construct a new events registry and JS function trampoline that weakly
51− /// holds the new registry and can be used by JS to invoke listeners on the
52− /// Rust side.
53− pub(crate) fn new(
54− vdom: Weak<VdomInner>,
55− ) -> (
56− Rc<RefCell<EventsRegistry>>,
57− crate::EventsTrampoline,
58− ) {
59− let registry = Rc::new(RefCell::new(EventsRegistry {
60− vdom,
61− active: FxHashMap::default(),
62− }));
63−
64− let weak_registry = Rc::downgrade(&registry);
65− let closure = Closure::wrap(Box::new(move |event, a, b| {
66− debug_assert!(a != 0);
67
19
68− // if the VdomInnerExclusive is keeping this closure alive, then the
20+impl fmt::Debug for EventsRegistry {
69− // VdomInnerExclusive should also be keeping the registry alive
21+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
70− let registry = weak_registry.upgrade().unwrap_throw();
22+ f.debug_struct("EventsRegistry")
71− let registry = registry.borrow();
23+ .field("active", &self.active.keys().collect::<Vec<_>>())
24+ .finish()
25+ }
26+}
72
27
73− match registry.active.get(&(a, b)) {
28+impl EventsRegistry {
74− None => warn!(
29+ /// Construct a new events registry and JS function trampoline that weakly
75− "EventsRegistry closure invoked with unknown listener parts: \
30+ /// holds the new registry and can be used by JS to invoke listeners on the
76− (0x{:x}, 0x{:x})",
31+ /// Rust side.
77− a, b
32+ pub(crate) fn new(
78− ),
33+ vdom: Weak<VdomInner>,
79− Some(callback) => {
34+ ) -> (
80− let vdom = registry.vdom.upgrade().expect_throw(
35+ Rc<RefCell<EventsRegistry>>,
81− "if the registry is still around, then the vdom should still be around",
82− );
83− let vdom_weak = VdomWeak::new(&vdom);
84− let mut vdom = vdom.exclusive.borrow_mut();
85− let component = vdom.component_raw_mut();
86− callback(component, vdom_weak, event);
87− }
88− }
89− }) as Box<dyn Fn(web_sys::Event, u32, u32)>);
36+ Closure<Fn(web_sys::Event, u32, u32)>,
37+ ) {
38+ let registry = Rc::new(RefCell::new(EventsRegistry {
39+ vdom,
40+ active: HashMap::new(),
41+ }));
90
42
91− (registry, closure)
43+ let weak_registry = Rc::downgrade(&registry);
92− }
44+ let closure = Closure::wrap(Box::new(move |event, a, b| {
45+ debug!("Events trampoline invoked with (0x{:x}, 0x{:x})", a, b);
46+ debug_assert!(a != 0);
93
47
94− pub(crate) fn remove(&mut self, listener: &Listener) {
48+ // if the VdomInnerExclusive is keeping this closure alive, then the
95− let id = listener.get_callback_parts();
49+ // VdomInnerExclusive should also be keeping the registry alive
96− debug_assert!(id.0 != 0);
50+ let registry = weak_registry.upgrade().unwrap_throw();
97− self.active.remove(&id);
51+ let registry = registry.borrow();
98− }
99
52
100− pub(crate) fn remove_subtree(&mut self, node: &Node) {
53+ match registry.active.get(&(a, b)) {
101− match node.kind {
54+ None => warn!(
102− NodeKind::Cached(_) | NodeKind::Text(_) => {},
55+ "EventsRegistry closure invoked with unknown listener parts: \
103− NodeKind::Element(&ElementNode {listeners, children, ..}) => {
56+ (0x{:x}, 0x{:x})",
104− for l in listeners {
57+ a, b
105− self.remove(l);
58+ ),
106− }
59+ Some(callback) => {
107− for child in children {
60+ let vdom = registry.vdom.upgrade().expect_throw(
108− self.remove_subtree(child)
61+ "if the registry is still around, then the vdom should still be around",
109− }
62+ );
110− }
63+ let vdom_weak = VdomWeak::new(&vdom);
64+ let mut vdom = vdom.exclusive.borrow_mut();
65+ let component = vdom.component_raw_mut();
66+ callback(component, vdom_weak, event);
111 }
67 }
112 }
68 }
69+ }) as Box<Fn(web_sys::Event, u32, u32)>);
70+
71+ (registry, closure)
72+ }
113
73
114− /// Add an event listener to the registry, exposing to JS.
74+ /// Add an event listener to the registry, exposing to JS.
115− ///
75+ ///
116− /// # Unsafety
76+ /// # Unsafety
117− ///
77+ ///
118− /// The listener's lifetime is extended to `'static` and it is the
78+ /// The listener's lifetime is extended to `'static` and it is the caller's
119− /// caller's responsibility to ensure that the listener is not kept
79+ /// responsibility to ensure that the listener is not kept in the registry
120− /// in the registry after it is dropped. This is maintained during
80+ /// after it is dropped. This is typically done via clearing the whole
121− /// diffing.
81+ /// registry on each render and repopulating it during diffing.
122− pub(crate) unsafe fn add<'a>(&mut self, listener: &'a Listener<'a>) {
82+ pub(crate) unsafe fn add<'a>(&mut self, listener: &'a Listener<'a>) {
123− let id = listener.get_callback_parts();
83+ let id = listener.get_callback_parts();
84+ debug!("EventsRegistry::add(0x{:x}, 0x{:x})", id.0, id.1);
124− debug_assert!(id.0 != 0);
85+ debug_assert!(id.0 != 0);
125
86
126− let callback =
87+ let callback =
127− mem::transmute::<ListenerCallback<'a>, ListenerCallback<'static>>(listener.callback);
88+ mem::transmute::<ListenerCallback<'a>, ListenerCallback<'static>>(listener.callback);
128− let old = self.active.insert(id, callback);
89+ let old = self.active.insert(id, callback);
129− debug_assert!(old.is_none());
90+ debug_assert!(old.is_none());
130− }
91+ }
131
92
132− /// Clear all event listeners from the registry.
93+ /// Clear all event listeners from the registry.
133− pub(crate) fn clear_active_listeners(&mut self) {
94+ pub(crate) fn clear_active_listeners(&mut self) {
95+ debug!("EventsRegistry::clear_active_listeners()");
134− self.active.clear();
96+ self.active.clear();
135− }
136− }
137 }
97 }
138}
98}
src/lib.rs+11−64
@@ -3,7 +3,7 @@
3//! ## Example
3//! ## Example
4//!
4//!
5//! ```no_run
5//! ```no_run
6−//! use dodrio::{bumpalo, Attribute, Node, Render, RenderContext};
6+//! use dodrio::{bumpalo::Bump, Attribute, Node, Render};
7//! use wasm_bindgen::UnwrapThrowExt;
7//! use wasm_bindgen::UnwrapThrowExt;
8//!
8//!
9//! /// A component that greets someone.
9//! /// A component that greets someone.
@@ -18,14 +18,16 @@
18//! }
18//! }
19//! }
19//! }
20//!
20//!
21−//! impl<'a, 'who> Render<'a> for Hello<'who> {
21+//! impl<'who> Render for Hello<'who> {
22−//! fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
22+//! fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
23+//! where
24+//! 'a: 'bump,
25+//! {
23//! use dodrio::builder::*;
26//! use dodrio::builder::*;
24//!
27//!
25−//! let id = bumpalo::format!(in cx.bump, "hello-{}", self.who);
28+//! let id = bumpalo::format!(in bump, "hello-{}", self.who);
26−//! let who = bumpalo::collections::String::from_str_in(self.who, cx.bump).into_bump_str();
27//!
29//!
28−//! div(&cx)
30+//! div(bump)
29//! .attr("id", id.into_bump_str())
31//! .attr("id", id.into_bump_str())
30//! .on("click", |root, _vdom, _event| {
32//! .on("click", |root, _vdom, _event| {
31//! let hello = root.unwrap_mut::<Hello>();
33//! let hello = root.unwrap_mut::<Hello>();
@@ -35,9 +37,9 @@
35//! })
37//! })
36//! .children([
38//! .children([
37//! text("Hello, "),
39//! text("Hello, "),
38−//! strong(&cx)
40+//! strong(bump)
39//! .children([
41//! .children([
40−//! text(who),
42+//! text(self.who),
41//! text("!"),
43//! text("!"),
42//! ])
44//! ])
43//! .finish(),
45//! .finish(),
@@ -61,75 +63,20 @@ cfg_if::cfg_if! {
61 }
63 }
62}
64}
63
65
64−// A macro to expose items unstably, only for internal/testing usage.
65−cfg_if::cfg_if! {
66− if #[cfg(feature = "xxx-unstable-internal-use-only")] {
67− macro_rules! pub_unstable_internal {
68− ( $(#[$attr:meta])* pub(crate) $( $thing:tt )* ) => {
69− #[doc(hidden)]
70− $( #[$attr] )*
71− pub $( $thing )*
72− }
73− }
74− } else {
75− macro_rules! pub_unstable_internal {
76− ( $(#[$attr:meta])* pub(crate) $( $thing:tt )* ) => {
77− $( #[$attr] )*
78− pub(crate) $( $thing )*
79− }
80− }
81− }
82−}
83−
84// Only `pub` so that the wasm-bindgen bindings work.
66// Only `pub` so that the wasm-bindgen bindings work.
85#[doc(hidden)]
67#[doc(hidden)]
86pub mod change_list;
68pub mod change_list;
87
69
88mod cached;
70mod cached;
89−mod cached_set;
90−mod diff;
91mod events;
71mod events;
92mod node;
72mod node;
93mod render;
73mod render;
94−mod render_context;
95−mod strace;
96mod vdom;
74mod vdom;
97
75
98pub mod builder;
76pub mod builder;
99
77
100// Re-export items at the top level.
78// Re-export items at the top level.
101pub use self::cached::Cached;
79pub use self::cached::Cached;
102−pub use self::node::{Attribute, Listener, Node, NodeKey};
80+pub use self::node::{Attribute, ElementNode, Listener, ListenerCallback, Node, TextNode};
103pub use self::render::{Render, RootRender};
81pub use self::render::{Render, RootRender};
104−pub use self::render_context::RenderContext;
105pub use self::vdom::{Vdom, VdomWeak};
82pub use self::vdom::{Vdom, VdomWeak};
106−
107−cfg_if::cfg_if! {
108− if #[cfg(all(target_arch = "wasm32", not(feature = "xxx-unstable-internal-use-only")))] {
109− use wasm_bindgen::__rt::WasmRefCell as RefCell;
110− } else {
111− use std::cell::RefCell;
112− }
113−}
114−
115−// Polyfill some Web stuff for benchmarking...
116−cfg_if::cfg_if! {
117− if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] {
118− /// An element node in the physical DOM.
119− pub type Element = ();
120−
121− pub(crate) type EventsTrampoline = ();
122− } else {
123− /// An element node in the physical DOM.
124− pub type Element = web_sys::Element;
125−
126− pub(crate) type EventsTrampoline = wasm_bindgen::closure::Closure<dyn Fn(web_sys::Event, u32, u32)>;
127− }
128−}
129−
130−cfg_if::cfg_if! {
131− if #[cfg(feature = "xxx-unstable-internal-use-only")] {
132− pub use self::cached_set::{CachedSet};
133− pub use self::node::{ElementNode, NodeKind, TextNode};
134− }
135−}
src/node.rs+77−150
@@ -1,111 +1,33 @@
1−use crate::{cached_set::CacheId, RootRender, VdomWeak};
1+use crate::{RootRender, VdomWeak};
2use bumpalo::Bump;
2use bumpalo::Bump;
3use std::fmt;
3use std::fmt;
4−use std::iter;
5use std::mem;
4use std::mem;
6−use std::u32;
7
5
8−/// A virtual DOM node.
6+/// A node is either a text node or an element.
9#[derive(Debug, Clone)]
7#[derive(Debug, Clone)]
10−pub struct Node<'a> {
8+pub enum Node<'a> {
11− #[cfg(feature = "xxx-unstable-internal-use-only")]
9+ /// A text node.
12− #[doc(hidden)]
10+ Text(TextNode<'a>),
13− pub kind: NodeKind<'a>,
14
11
15− #[cfg(not(feature = "xxx-unstable-internal-use-only"))]
12+ /// An element potentially with attributes and children.
16− pub(crate) kind: NodeKind<'a>,
13+ Element(ElementNode<'a>),
17}
14}
18
15
19−pub_unstable_internal! {
20− /// A node is either a text node or an element.
21− #[derive(Debug, Clone)]
22− pub(crate) enum NodeKind<'a> {
23− /// A text node.
24− Text(TextNode<'a>),
25−
26− /// An element potentially with attributes and children.
27− Element(&'a ElementNode<'a>),
28−
29− /// A node in the vdom's `CachedSet`. This allows us to avoid
30− /// re-rendering and re-diffing subtrees.
31− Cached(CachedNode),
32− }
33−}
34−
35−pub_unstable_internal! {
36− /// Text nodes are just a string of text. They cannot have attributes or
16+/// Text nodes are just a string of text. They cannot have attributes or
37− /// children.
17+/// children.
38− #[derive(Debug, Clone)]
18+#[derive(Debug, Clone)]
39− pub(crate) struct TextNode<'a> {
19+pub struct TextNode<'a> {
40− pub text: &'a str,
20+ pub(crate) text: &'a str,
41− }
42−}
43−
44−pub_unstable_internal! {
45− /// Elements have a tag name, zero or more attributes, and zero or more
46− /// children.
47− #[derive(Debug, Clone)]
48− pub(crate) struct ElementNode<'a> {
49− pub key: NodeKey,
50− pub tag_name: &'a str,
51− pub listeners: &'a [Listener<'a>],
52− pub attributes: &'a [Attribute<'a>],
53− pub children: &'a [Node<'a>],
54− pub namespace: Option<&'a str>,
55− }
56−}
57−
58−pub_unstable_internal! {
59− /// A cached node exists in an arena that is internal to the `Vdom`. It
60− /// allows us to avoid both re-rendering a sub-tree and re-diffing
61− /// it. `CachedNode`s cannot contain cycles, since they can only contain
62− /// other `CachedNode`s that already exist when they are being constructed.
63− #[derive(Debug, Clone, Copy)]
64− pub(crate) struct CachedNode {
65− pub id: CacheId,
66− // Always a copy of the inner cached node's key.
67− pub key: NodeKey,
68− }
69−}
70−
71−/// The key for keyed children.
72−///
73−/// Keys must be unique among siblings.
74−///
75−/// If any sibling is keyed, then they all must be keyed.
76−#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
77−pub struct NodeKey(pub(crate) u32);
78−
79−impl Default for NodeKey {
80− fn default() -> NodeKey {
81− NodeKey::NONE
82− }
83}
21}
84
22
85−impl NodeKey {
23+/// Elements have a tag name, zero or more attributes, and zero or more
86− /// The default, lack of a key.
24+/// children.
87− pub const NONE: NodeKey = NodeKey(u32::MAX);
25+#[derive(Debug, Clone)]
88−
26+pub struct ElementNode<'a> {
89− /// Is this key `NodeKey::NONE`?
27+ pub(crate) tag_name: &'a str,
90− #[inline]
28+ pub(crate) listeners: &'a [Listener<'a>],
91− pub fn is_none(&self) -> bool {
29+ pub(crate) attributes: &'a [Attribute<'a>],
92− *self == Self::NONE
30+ pub(crate) children: &'a [Node<'a>],
93− }
94−
95− /// Is this key not `NodeKey::NONE`?
96− #[inline]
97− pub fn is_some(&self) -> bool {
98− !self.is_none()
99− }
100−
101− /// Create a new `NodeKey`.
102− ///
103− /// `key` must not be `u32::MAX`.
104− #[inline]
105− pub fn new(key: u32) -> Self {
106− debug_assert_ne!(key, u32::MAX);
107− NodeKey(key)
108− }
109}
31}
110
32
111/// An event listener callback function.
33/// An event listener callback function.
@@ -114,8 +36,8 @@ impl NodeKey {
114///
36///
115/// 1. The virtual DOM's root rendering component.
37/// 1. The virtual DOM's root rendering component.
116/// 2. A capability to scheduler virtual DOM re-rendering.
38/// 2. A capability to scheduler virtual DOM re-rendering.
117−/// 3. The event that occurred.
39+/// 3. The event that ocurred.
118−pub(crate) type ListenerCallback<'a> =
40+pub type ListenerCallback<'a> =
119 &'a (dyn Fn(&mut dyn RootRender, VdomWeak, web_sys::Event) + 'static);
41 &'a (dyn Fn(&mut dyn RootRender, VdomWeak, web_sys::Event) + 'static);
120
42
121/// An event listener.
43/// An event listener.
@@ -134,15 +56,6 @@ pub struct Attribute<'a> {
134 pub(crate) value: &'a str,
56 pub(crate) value: &'a str,
135}
57}
136
58
137−impl<'a> From<CachedNode> for Node<'a> {
138− #[inline]
139− fn from(c: CachedNode) -> Self {
140− Node {
141− kind: NodeKind::Cached(c),
142− }
143− }
144−}
145−
146impl fmt::Debug for Listener<'_> {
59impl fmt::Debug for Listener<'_> {
147 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
148 let (a, b) = self.get_callback_parts();
61 let (a, b) = self.get_callback_parts();
@@ -182,67 +95,81 @@ impl<'a> Attribute<'a> {
182}
95}
183
96
184impl<'a> Node<'a> {
97impl<'a> Node<'a> {
185− /// Low-level constructor for making a new `Node` of type element with given
98+ /// Construct a new element node with the given tag name and children.
186− /// parts.
187− ///
188− /// This is primarily intended for JSX and templating proc-macros to compile
189− /// down into. If you are building nodes by-hand, prefer using the
190− /// `dodrio::builder::*` APIs.
191 #[inline]
99 #[inline]
192− pub fn element(
100+ pub(crate) fn element<Listeners, Attributes, Children>(
193 bump: &'a Bump,
101 bump: &'a Bump,
194− key: NodeKey,
195 tag_name: &'a str,
102 tag_name: &'a str,
196− listeners: &'a [Listener<'a>],
103+ listeners: Listeners,
197− attributes: &'a [Attribute<'a>],
104+ attributes: Attributes,
198− children: &'a [Node<'a>],
105+ children: Children,
199− namespace: Option<&'a str>,
200− ) -> Node<'a> {
106+ ) -> Node<'a>
201− let element = bump.alloc_with(|| ElementNode {
107+ where
202− key,
108+ Listeners: 'a + AsRef<[Listener<'a>]>,
109+ Attributes: 'a + AsRef<[Attribute<'a>]>,
110+ Children: 'a + AsRef<[Node<'a>]>,
111+ {
112+ let children: &'a Children = bump.alloc(children);
113+ let children: &'a [Node<'a>] = children.as_ref();
114+
115+ let listeners: &'a Listeners = bump.alloc(listeners);
116+ let listeners: &'a [Listener<'a>] = listeners.as_ref();
117+
118+ let attributes: &'a Attributes = bump.alloc(attributes);
119+ let attributes: &'a [Attribute<'a>] = attributes.as_ref();
120+
121+ Node::Element(ElementNode {
203 tag_name,
122 tag_name,
204 listeners,
123 listeners,
205 attributes,
124 attributes,
206 children,
125 children,
207− namespace,
126+ })
127+ }
128+
129+ /// Is this node a text node?
130+ pub fn is_text(&self) -> bool {
131+ match self {
132+ Node::Text(_) => true,
133+ _ => false,
208− });
134+ }
135+ }
209
136
137+ /// Is this node an element?
138+ pub fn is_element(&self) -> bool {
139+ match self {
210− Node {
140+ Node::Element { .. } => true,
211− kind: NodeKind::Element(element),
141+ _ => false,
212 }
142 }
213 }
143 }
214
144
215 /// Construct a new text node with the given text.
145 /// Construct a new text node with the given text.
216 #[inline]
146 #[inline]
217 pub(crate) fn text(text: &'a str) -> Node<'a> {
147 pub(crate) fn text(text: &'a str) -> Node<'a> {
218− Node {
219− kind: NodeKind::Text(TextNode { text }),
148+ Node::Text(TextNode { text })
220− }
221 }
149 }
150+}
222
151
223− #[inline]
152+impl<'a> TextNode<'a> {
153+ /// Get this text node's text content.
224− pub(crate) fn key(&self) -> NodeKey {
154+ pub fn text(&self) -> &'a str {
225− match &self.kind {
155+ self.text
226− NodeKind::Text(_) => NodeKey::NONE,
227− NodeKind::Element(e) => e.key,
228− NodeKind::Cached(c) => c.key,
229− }
230 }
156 }
231}
157}
232
158
233−/// A node can become an iterator that yields the node itself once.
159+impl<'a> ElementNode<'a> {
234−///
160+ /// Get this element's tag name.
235−/// This implementation of `IntoIterator` mostly exists to improve the
161+ pub fn tag_name(&self) -> &'a str {
236−/// `typed-html` ergonomics, where the macro invokes `.into_iter()` on the child
162+ self.tag_name
237−/// contents of a tag. By implementing `IntoIterator` here, we avoid having to
163+ }
238−/// do nasty shenanigans like `<p>vec![$contents]</p>` instead of plain old
164+
239−/// `<p>$contents</p>`.
165+ /// Get this element's attributes.
240−impl<'a> IntoIterator for Node<'a> {
166+ pub fn attributes(&self) -> &'a [Attribute<'a>] {
241− type Item = Node<'a>;
167+ self.attributes
242− type IntoIter = iter::Once<Self::Item>;
168+ }
243
169
244− fn into_iter(self) -> Self::IntoIter {
170+ /// Get this element's attributes.
245− iter::once(self)
171+ pub fn children(&self) -> &'a [Node<'a>] {
172+ self.children
246 }
173 }
247}
174}
248
175
src/render.rs+50−65
@@ -1,4 +1,5 @@
1−use crate::{Node, RenderContext};
1+use crate::Node;
2+use bumpalo::Bump;
2use std::any::Any;
3use std::any::Any;
3use std::rc::Rc;
4use std::rc::Rc;
4use wasm_bindgen::UnwrapThrowExt;
5use wasm_bindgen::UnwrapThrowExt;
@@ -10,52 +11,72 @@ use wasm_bindgen::UnwrapThrowExt;
10///
11///
11/// ## `Bump` Allocation
12/// ## `Bump` Allocation
12///
13///
13−/// `Render` implementations can use the `Bump` inside the provided
14+/// `Render` implementations can use the provided `Bump` for very fast
14−/// `RenderContext` for very fast allocation for anything that needs to be
15+/// allocation for anything that needs to be allocated during rendering.
15−/// temporarily allocated during rendering.
16+///
17+/// ## The `'a: 'bump` Lifetime Bound
18+///
19+/// The `'a: 'bump` bounds enforce that `self` outlives the given bump
20+/// allocator. This means that if `self` contains a string, the string does not
21+/// need to be copied into the output `Node` and can be used by reference
22+/// instead (i.e. it prevents accidentally using the string after its been
23+/// freed). The `'a: 'bump` bound also enables abstractions like
24+/// `dodrio::Cached` that can re-use cached `Node`s across `render`s without
25+/// copying them.
16///
26///
17/// ## Example
27/// ## Example
18///
28///
19/// ```no_run
29/// ```no_run
20−/// use dodrio::{Node, Render, RenderContext};
30+/// use dodrio::{bumpalo::Bump, Node, Render};
21///
31///
22/// pub struct MyComponent;
32/// pub struct MyComponent;
23///
33///
24−/// impl<'a> Render<'a> for MyComponent {
34+/// impl Render for MyComponent {
25−/// fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
35+/// fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
36+/// where
37+/// 'a: 'bump
38+/// {
26/// use dodrio::builder::*;
39/// use dodrio::builder::*;
27///
40///
28−/// p(&cx)
41+/// p(bump)
29/// .children([
42/// .children([
30/// text("This is "),
43/// text("This is "),
31−/// strong(&cx).children([text("my component")]).finish(),
44+/// strong(bump).children([text("my component")]).finish(),
32/// text(" rendered!"),
45/// text(" rendered!"),
33/// ])
46/// ])
34/// .finish()
47/// .finish()
35/// }
48/// }
36/// }
49/// }
37/// ```
50/// ```
38−pub trait Render<'a> {
51+pub trait Render {
39− /// Render `self` as a virtual DOM. Use the given context's `Bump` for
52+ /// Render `self` as a virtual DOM. Use the given `Bump` for temporary
40− /// temporary allocations.
53+ /// allocations.
41− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a>;
54+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
55+ where
56+ 'a: 'bump;
42}
57}
43
58
44−impl<'a, 'r, R> Render<'a> for &'r R
59+impl<'r, R> Render for &'r R
45where
60where
46− R: Render<'a>,
61+ R: Render,
47{
62{
48− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
63+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
64+ where
65+ 'a: 'bump,
66+ {
49− (**self).render(cx)
67+ (**self).render(bump)
50 }
68 }
51}
69}
52
70
53−impl<'a, R> Render<'a> for Rc<R>
71+impl<R> Render for Rc<R>
54where
72where
55− R: Render<'a>,
73+ R: Render,
56{
74{
57− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
75+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
76+ where
77+ 'a: 'bump,
78+ {
58− (**self).render(cx)
79+ (**self).render(bump)
59 }
80 }
60}
81}
61
82
@@ -69,24 +90,21 @@ where
69/// You do not need to implement this trait by hand: there is a blanket
90/// You do not need to implement this trait by hand: there is a blanket
70/// implementation for all `Render` types that fulfill the `RootRender`
91/// implementation for all `Render` types that fulfill the `RootRender`
71/// requirements.
92/// requirements.
72−pub trait RootRender: Any + for<'a> Render<'a> {
93+pub trait RootRender: Any + Render {
73 /// Get this `&RootRender` trait object as an `&Any` trait object reference.
94 /// Get this `&RootRender` trait object as an `&Any` trait object reference.
74− fn as_any(&self) -> &dyn Any;
95+ fn as_any(&self) -> &Any;
75
96
76 /// Get this `&mut RootRender` trait object as an `&mut Any` trait object
97 /// Get this `&mut RootRender` trait object as an `&mut Any` trait object
77 /// reference.
98 /// reference.
78− fn as_any_mut(&mut self) -> &mut dyn Any;
99+ fn as_any_mut(&mut self) -> &mut Any;
79}
100}
80
101
81−impl<T> RootRender for T
82−where
83− T: Any + for<'a> Render<'a>,
102+impl<T: Any + Render> RootRender for T {
84−{
85− fn as_any(&self) -> &dyn Any {
103+ fn as_any(&self) -> &Any {
86 self
104 self
87 }
105 }
88
106
89− fn as_any_mut(&mut self) -> &mut dyn Any {
107+ fn as_any_mut(&mut self) -> &mut Any {
90 self
108 self
91 }
109 }
92}
110}
@@ -99,10 +117,7 @@ impl dyn RootRender {
99 ///
117 ///
100 /// Panics if this virtual DOM's root rendering component is not an `R`
118 /// Panics if this virtual DOM's root rendering component is not an `R`
101 /// instance.
119 /// instance.
102− pub fn unwrap_ref<R>(&self) -> &R
120+ pub fn unwrap_ref<R: RootRender>(&self) -> &R {
103− where
104− R: RootRender,
105− {
106 self.as_any()
121 self.as_any()
107 .downcast_ref::<R>()
122 .downcast_ref::<R>()
108 .expect_throw("bad `RootRender::unwrap_ref` call")
123 .expect_throw("bad `RootRender::unwrap_ref` call")
@@ -115,10 +130,7 @@ impl dyn RootRender {
115 ///
130 ///
116 /// Panics if this virtual DOM's root rendering component is not an `R`
131 /// Panics if this virtual DOM's root rendering component is not an `R`
117 /// instance.
132 /// instance.
118− pub fn unwrap_mut<R>(&mut self) -> &mut R
133+ pub fn unwrap_mut<R: RootRender>(&mut self) -> &mut R {
119− where
120− R: RootRender,
121− {
122 self.as_any_mut()
134 self.as_any_mut()
123 .downcast_mut::<R>()
135 .downcast_mut::<R>()
124 .expect_throw("bad `RootRender::unwrap_ref` call")
136 .expect_throw("bad `RootRender::unwrap_ref` call")
@@ -138,31 +150,4 @@ mod tests {
138 #[allow(dead_code)]
150 #[allow(dead_code)]
139 fn takes_dyn_render(_: &dyn super::RootRender) {}
151 fn takes_dyn_render(_: &dyn super::RootRender) {}
140 }
152 }
141−
142− #[test]
143− fn render_bump_scoped_child() {
144− use crate::{builder::*, bumpalo::collections::String, Node, Render, RenderContext};
145−
146− struct Child<'a> {
147− name: &'a str,
148− }
149−
150− impl<'a> Render<'a> for Child<'a> {
151− fn render(&self, _cx: &mut RenderContext<'a>) -> Node<'a> {
152− text(self.name)
153− }
154− }
155−
156− struct Parent;
157−
158− impl<'a> Render<'a> for Parent {
159− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
160− let child_name = String::from_str_in("child", cx.bump).into_bump_str();
161−
162− div(&cx)
163− .children([Child { name: child_name }.render(cx)])
164− .finish()
165− }
166− }
167− }
168}
153}
src/render_context.rsdeleted−104
@@ -1,104 +0,0 @@
1−use crate::{
2− cached::{Cached, TemplateId},
3− cached_set::{CacheId, CachedSet},
4− Node, Render,
5−};
6−use bumpalo::Bump;
7−use fxhash::FxHashMap;
8−use std::fmt;
9−
10−/// Common context available to all `Render` implementations.
11−///
12−/// Notably, the `RenderContext` gives access to the bump arena that the virtual
13−/// DOM should be allocated within. This is available via the `bump` field.
14−pub struct RenderContext<'a> {
15− /// The underlying bump arena that virtual DOMs are rendered into.
16− ///
17− /// ## Example
18− ///
19− /// ```
20− /// use dodrio::RenderContext;
21− ///
22− /// // Given a rendering context, allocate an i32 inside its bump arena.
23− /// fn foo<'a>(cx: &mut RenderContext<'a>) -> &'a mut i32 {
24− /// cx.bump.alloc(42)
25− /// }
26− /// ```
27− pub bump: &'a Bump,
28−
29− pub(crate) cached_set: &'a crate::RefCell<CachedSet>,
30−
31− pub(crate) templates: &'a mut FxHashMap<TemplateId, Option<CacheId>>,
32−
33− // Prevent exhaustive matching on the rendering context, so we can always
34− // add more members in a semver-compatible way.
35− _non_exhaustive: (),
36−}
37−
38−impl fmt::Debug for RenderContext<'_> {
39− fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
40− f.debug_struct("RenderContext")
41− .field("bump", &self.bump)
42− .finish()
43− }
44−}
45−
46−impl<'a> RenderContext<'a> {
47− pub_unstable_internal! {
48− pub(crate) fn new(
49− bump: &'a Bump,
50− cached_set: &'a crate::RefCell<CachedSet>,
51− templates: &'a mut FxHashMap<TemplateId, Option<CacheId>>
52− ) -> Self {
53− RenderContext {
54− bump,
55− cached_set,
56− templates,
57− _non_exhaustive: (),
58− }
59− }
60− }
61−
62− pub(crate) fn cache<F>(&mut self, pinned: bool, template: Option<CacheId>, f: F) -> CacheId
63− where
64− F: for<'b> FnOnce(&mut RenderContext<'b>) -> Node<'b>,
65− {
66− CachedSet::insert(self, pinned, template, f)
67− }
68−
69− /// Get or create the cached template for `Cached<R>`.
70− pub(crate) fn template<R>(&mut self) -> Option<CacheId>
71− where
72− R: 'static + Default + for<'b> Render<'b>,
73− {
74− let template_id = Cached::<R>::template_id();
75− if let Some(cache_id) = self.templates.get(&template_id).cloned() {
76− return cache_id;
77− }
78−
79− // Prevent re-entrancy from infinite looping. Any attempts to get `R`'s
80− // template while constructing the template will simply fail to use the
81− // templated fast path.
82− self.templates.insert(template_id, None);
83−
84− // Render the default `R` and save that as the template for all
85− // `Cached<R>`s.
86− let cache_id = self.cache(true, None, |nested_cx| R::default().render(nested_cx));
87− self.templates.insert(template_id, Some(cache_id));
88− Some(cache_id)
89− }
90−}
91−
92−impl<'a, 'b> From<&'b RenderContext<'a>> for &'a Bump {
93− #[inline]
94− fn from(cx: &'b RenderContext<'a>) -> &'a Bump {
95− cx.bump
96− }
97−}
98−
99−impl<'a, 'b, 'c> From<&'c &'b mut RenderContext<'a>> for &'a Bump {
100− #[inline]
101− fn from(cx: &'c &'b mut RenderContext<'a>) -> &'a Bump {
102− cx.bump
103− }
104−}
src/strace.rsdeleted−22
@@ -1,22 +0,0 @@
1−cfg_if::cfg_if! {
2− if #[cfg(feature = "xxx-unstable-strace")] {
3− use wasm_bindgen::prelude::*;
4−
5− #[wasm_bindgen(module = "/js/strace.js")]
6− extern "C" {
7− #[wasm_bindgen(js_name = initStrace)]
8− fn really_init_strace();
9− }
10−
11− pub fn init_strace() {
12− use std::sync::Once;
13− static STRACE: Once = Once::new();
14− STRACE.call_once(|| {
15− really_init_strace();
16− });
17− }
18− } else {
19− #[inline]
20− pub fn init_strace() {}
21− }
22−}
src/vdom.rs+335−182
@@ -1,16 +1,13 @@
1−use super::change_list::ChangeListPersistentState;
1+use super::change_list::ChangeList;
2+use super::node::{Attribute, ElementNode, Listener, Node, TextNode};
2use super::RootRender;
3use super::RootRender;
3−use crate::cached::TemplateId;
4−use crate::cached_set::{CacheId, CachedSet};
5use crate::events::EventsRegistry;
4use crate::events::EventsRegistry;
6−use crate::node::{Node, NodeKey};
7−use crate::RenderContext;
8use bumpalo::Bump;
5use bumpalo::Bump;
9−use fxhash::FxHashMap;
6+use futures::future::Future;
10use std::cell::Cell;
7use std::cell::Cell;
11use std::cell::RefCell;
8use std::cell::RefCell;
9+use std::cmp;
12use std::fmt;
10use std::fmt;
13−use std::future::Future;
14use std::mem;
11use std::mem;
15use std::mem::ManuallyDrop;
12use std::mem::ManuallyDrop;
16use std::rc::{Rc, Weak};
13use std::rc::{Rc, Weak};
@@ -55,15 +52,12 @@ pub(crate) struct VdomInnerExclusive {
55 // Always `Some` except just before we drop. Just an option so that
52 // Always `Some` except just before we drop. Just an option so that
56 // `unmount` can take the component out but we can still have a Drop
53 // `unmount` can take the component out but we can still have a Drop
57 // implementation.
54 // implementation.
58− component: Option<Box<dyn RootRender>>,
55+ component: Option<Box<RootRender>>,
59
56
60− dom_buffers: Option<[Bump; 2]>,
57+ dom_buffers: [Bump; 2],
61− change_list: ManuallyDrop<ChangeListPersistentState>,
58+ change_list: ManuallyDrop<ChangeList>,
62− container: crate::Element,
59+ container: web_sys::Element,
63 events_registry: Option<Rc<RefCell<EventsRegistry>>>,
60 events_registry: Option<Rc<RefCell<EventsRegistry>>>,
64− events_trampoline: Option<crate::EventsTrampoline>,
65− cached_set: crate::RefCell<CachedSet>,
66− templates: FxHashMap<TemplateId, Option<CacheId>>,
67
61
68 // Actually a reference into `self.dom_buffers[0]` or if `self.component` is
62 // Actually a reference into `self.dom_buffers[0]` or if `self.component` is
69 // caching renders, into `self.component`'s bump.
63 // caching renders, into `self.component`'s bump.
@@ -96,7 +90,6 @@ impl fmt::Debug for VdomInnerExclusive {
96 .field("change_list", &self.change_list)
90 .field("change_list", &self.change_list)
97 .field("container", &self.container)
91 .field("container", &self.container)
98 .field("events_registry", &self.events_registry)
92 .field("events_registry", &self.events_registry)
99− .field("events_trampoline", &"..")
100 .field("current_root", &self.current_root)
93 .field("current_root", &self.current_root)
101 .finish()
94 .finish()
102 }
95 }
@@ -116,36 +109,7 @@ impl Drop for VdomInnerExclusive {
116 let mut registry = registry.borrow_mut();
109 let mut registry = registry.borrow_mut();
117 registry.clear_active_listeners();
110 registry.clear_active_listeners();
118
111
119− empty_container(&self.container);
120− }
121−}
122−
123−cfg_if::cfg_if! {
124− if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] {
125− fn empty_container(_container: &crate::Element) {}
126− fn initialize_container(_container: &crate::Element) {}
127− } else {
128− fn empty_container(container: &crate::Element) {
129− container.set_inner_html("");
112+ self.container.set_inner_html("");
130− }
131−
132− fn initialize_container(container: &crate::Element) {
133− empty_container(container);
134−
135− // Create the dummy `<div/>` child in the container.
136− let window = web_sys::window().expect_throw("should have access to the Window");
137− let document = window
138− .document()
139− .expect("should have access to the Document");
140− container
141− .append_child(
142− document
143− .create_element("div")
144− .expect("should create element OK")
145− .as_ref(),
146− )
147− .expect("should append child OK");
148− }
149 }
113 }
150}
114}
151
115
@@ -154,29 +118,40 @@ impl Vdom {
154 /// rendering component.
118 /// rendering component.
155 ///
119 ///
156 /// This will box the given component into trait object.
120 /// This will box the given component into trait object.
157− pub fn new<R>(container: &crate::Element, component: R) -> Vdom
121+ pub fn new<R>(container: &web_sys::Element, component: R) -> Vdom
158 where
122 where
159 R: RootRender,
123 R: RootRender,
160 {
124 {
161− Self::with_boxed_root_render(container, Box::new(component) as Box<dyn RootRender>)
125+ Self::with_boxed_root_render(container, Box::new(component) as Box<RootRender>)
162 }
126 }
163
127
164 /// Construct a `Vdom` with the already-boxed-as-a-trait-object root
128 /// Construct a `Vdom` with the already-boxed-as-a-trait-object root
165 /// rendering component.
129 /// rendering component.
166 pub fn with_boxed_root_render(
130 pub fn with_boxed_root_render(
167− container: &crate::Element,
131+ container: &web_sys::Element,
168− component: Box<dyn RootRender>,
132+ component: Box<RootRender>,
169 ) -> Vdom {
133 ) -> Vdom {
170− crate::strace::init_strace();
171−
172 let dom_buffers = [Bump::new(), Bump::new()];
134 let dom_buffers = [Bump::new(), Bump::new()];
173− let change_list = ManuallyDrop::new(ChangeListPersistentState::new(container));
135+ let change_list = ManuallyDrop::new(ChangeList::new(container));
136+
137+ // Ensure that the container is empty.
138+ container.set_inner_html("");
174
139
175 // Create a dummy `<div/>` in our container.
140 // Create a dummy `<div/>` in our container.
176− initialize_container(container);
177− let current_root =
178− Node::element(&dom_buffers[0], NodeKey::NONE, "div", &[], &[], &[], None);
141+ let current_root = Node::element(&dom_buffers[0], "div", [], [], []);
179 let current_root = Some(unsafe { extend_node_lifetime(current_root) });
142 let current_root = Some(unsafe { extend_node_lifetime(current_root) });
143+ let window = web_sys::window().expect("should have acess to the Window");
144+ let document = window
145+ .document()
146+ .expect("should have access to the Document");
147+ container
148+ .append_child(
149+ document
150+ .create_element("div")
151+ .expect("should create element OK")
152+ .as_ref(),
153+ )
154+ .expect("should append child OK");
180
155
181 let container = container.clone();
156 let container = container.clone();
182 let inner = Rc::new(VdomInner {
157 let inner = Rc::new(VdomInner {
@@ -185,14 +160,11 @@ impl Vdom {
185 },
160 },
186 exclusive: RefCell::new(VdomInnerExclusive {
161 exclusive: RefCell::new(VdomInnerExclusive {
187 component: Some(component),
162 component: Some(component),
188− dom_buffers: Some(dom_buffers),
163+ dom_buffers,
189 change_list,
164 change_list,
190 container,
165 container,
191 current_root,
166 current_root,
192 events_registry: None,
167 events_registry: None,
193− events_trampoline: None,
194− cached_set: crate::RefCell::new(Default::default()),
195− templates: Default::default(),
196 }),
168 }),
197 });
169 });
198
170
@@ -201,9 +173,7 @@ impl Vdom {
201 {
173 {
202 let mut inner = inner.exclusive.borrow_mut();
174 let mut inner = inner.exclusive.borrow_mut();
203 inner.events_registry = Some(events_registry);
175 inner.events_registry = Some(events_registry);
204− inner.change_list.init_events_trampoline(&events_trampoline);
176+ inner.change_list.init_events_trampoline(events_trampoline);
205− debug_assert!(inner.events_trampoline.is_none());
206− inner.events_trampoline = Some(events_trampoline);
207
177
208 // Diff and apply the `contents` against our dummy `<div/>`.
178 // Diff and apply the `contents` against our dummy `<div/>`.
209 inner.render();
179 inner.render();
@@ -212,19 +182,6 @@ impl Vdom {
212 Vdom { inner }
182 Vdom { inner }
213 }
183 }
214
184
215− /// Immediately re-render and diff. Only for internal testing and
216− /// benchmarking purposes.
217− #[cfg(feature = "xxx-unstable-internal-use-only")]
218− pub fn immediately_render_and_diff<R>(&self, component: R)
219− where
220− R: RootRender,
221− {
222− let mut exclusive = self.inner.exclusive.borrow_mut();
223− let component = Box::new(component) as Box<dyn RootRender>;
224− exclusive.component = Some(component);
225− exclusive.render();
226− }
227−
228 /// Run this virtual DOM and its listeners forever and never unmount it.
185 /// Run this virtual DOM and its listeners forever and never unmount it.
229 #[inline]
186 #[inline]
230 pub fn forget(self) {
187 pub fn forget(self) {
@@ -240,7 +197,7 @@ impl Vdom {
240 /// Unmount this virtual DOM, unregister its event listeners, and return its
197 /// Unmount this virtual DOM, unregister its event listeners, and return its
241 /// root render component.
198 /// root render component.
242 #[inline]
199 #[inline]
243− pub fn unmount(self) -> Box<dyn RootRender> {
200+ pub fn unmount(self) -> Box<RootRender> {
244 Rc::try_unwrap(self.inner.clone())
201 Rc::try_unwrap(self.inner.clone())
245 .map_err(|_| ())
202 .map_err(|_| ())
246 .unwrap_throw()
203 .unwrap_throw()
@@ -264,69 +221,265 @@ impl VdomInnerExclusive {
264 pub(crate) fn render(&mut self) {
221 pub(crate) fn render(&mut self) {
265 unsafe {
222 unsafe {
266 let events_registry = self.events_registry.take().unwrap();
223 let events_registry = self.events_registry.take().unwrap();
224+
267 {
225 {
226+ // All the old listeners are no longer active. We will build a new
227+ // set of active listeners when diffing.
228+ //
229+ // NB: if we end up avoiding diffing cached renders (instead of just
230+ // avoiding re-rendering them) then we will need to maintain cached
231+ // active listeners, and can't just clear all active listeners and
232+ // rebuild them here.
268 let mut registry = events_registry.borrow_mut();
233 let mut registry = events_registry.borrow_mut();
234+ registry.clear_active_listeners();
269
235
270 // Reset the inactive bump arena's pointer.
236 // Reset the inactive bump arena's pointer.
271− let mut dom_buffers = self.dom_buffers.take().unwrap_throw();
272− dom_buffers[1].reset();
237+ self.dom_buffers[1].reset();
273
238
274 // Render the new current contents into the inactive bump arena.
239 // Render the new current contents into the inactive bump arena.
275− let mut cx =
276− RenderContext::new(&dom_buffers[1], &self.cached_set, &mut self.templates);
277− let new_contents = self.component.as_ref().unwrap_throw().render(&mut cx);
240+ let new_contents = self
241+ .component
242+ .as_ref()
243+ .unwrap_throw()
244+ .render(&self.dom_buffers[1]);
278 let new_contents = extend_node_lifetime(new_contents);
245 let new_contents = extend_node_lifetime(new_contents);
279
246
280 // Diff the old contents with the new contents.
247 // Diff the old contents with the new contents.
281 let old_contents = self.current_root.take().unwrap();
248 let old_contents = self.current_root.take().unwrap();
282− let mut cache_roots;
283− {
284− let cached_set = self.cached_set.borrow();
285− cache_roots = cached_set.new_roots_set();
286− let mut change_list = self.change_list.builder();
287− crate::diff::diff(
288− &cached_set,
289− &mut change_list,
290− &mut registry,
291− &old_contents,
249+ self.diff(&mut registry, old_contents, new_contents.clone());
292− &new_contents,
293− &mut cache_roots,
294− );
295−
296− // Tell JS to apply our diff-generated changes to the physical DOM!
297− change_list.finish();
298− }
299−
300− {
301− // Clean up unused cached renders.
302− let mut cached_set = self.cached_set.borrow_mut();
303− cached_set.gc(&mut registry, cache_roots);
304− }
305
250
306 // Swap the buffers to make the bump arena with the new contents the
251 // Swap the buffers to make the bump arena with the new contents the
307 // active arena, and the old one into the inactive arena.
252 // active arena, and the old one into the inactive arena.
308− self.swap_buffers(dom_buffers);
253+ self.swap_buffers();
309 self.set_current_root(new_contents);
254 self.set_current_root(new_contents);
310 }
255 }
311
256
312 self.events_registry = Some(events_registry);
257 self.events_registry = Some(events_registry);
258+
259+ // Tell JS to apply our diff-generated changes to the physical DOM!
260+ self.change_list.apply_changes();
313 }
261 }
314 }
262 }
315
263
316− fn swap_buffers(&mut self, mut dom_buffers: [Bump; 2]) {
264+ fn swap_buffers(&mut self) {
317− debug_assert!(self.dom_buffers.is_none());
318− let (first, second) = dom_buffers.as_mut().split_at_mut(1);
265+ let (first, second) = self.dom_buffers.as_mut().split_at_mut(1);
319 mem::swap(&mut first[0], &mut second[0]);
266 mem::swap(&mut first[0], &mut second[0]);
320− self.dom_buffers = Some(dom_buffers);
321 }
267 }
322
268
323 unsafe fn set_current_root(&mut self, current: Node<'static>) {
269 unsafe fn set_current_root(&mut self, current: Node<'static>) {
324 debug_assert!(self.current_root.is_none());
270 debug_assert!(self.current_root.is_none());
325 self.current_root = Some(current);
271 self.current_root = Some(current);
326 }
272 }
273+
274+ fn diff<'a>(&self, registry: &mut EventsRegistry, old: Node<'a>, new: Node<'a>) {
275+ // debug!("---------------------------------------------------------");
276+ // debug!("dodrio::Vdom::diff");
277+ // debug!(" old = {:#?}", old);
278+ // debug!(" new = {:#?}", new);
279+ match (&new, old) {
280+ (&Node::Text(TextNode { text: new_text }), Node::Text(TextNode { text: old_text })) => {
281+ debug!(" both are text nodes");
282+ if new_text != old_text {
283+ debug!(" text needs updating");
284+ self.change_list.emit_set_text(new_text);
285+ }
286+ }
287+ (&Node::Text(TextNode { .. }), Node::Element(ElementNode { .. })) => {
288+ debug!(" replacing a text node with an element");
289+ self.create(registry, new);
290+ self.change_list.emit_replace_with();
291+ }
292+ (&Node::Element(ElementNode { .. }), Node::Text(TextNode { .. })) => {
293+ debug!(" replacing an element with a text node");
294+ self.create(registry, new);
295+ self.change_list.emit_replace_with();
296+ }
297+ (
298+ &Node::Element(ElementNode {
299+ tag_name: new_tag_name,
300+ listeners: new_listeners,
301+ attributes: new_attributes,
302+ children: new_children,
303+ }),
304+ Node::Element(ElementNode {
305+ tag_name: old_tag_name,
306+ listeners: old_listeners,
307+ attributes: old_attributes,
308+ children: old_children,
309+ }),
310+ ) => {
311+ debug!(" updating an element");
312+ if new_tag_name != old_tag_name {
313+ debug!(" different tag names; creating new element and replacing old element");
314+ self.create(registry, new);
315+ self.change_list.emit_replace_with();
316+ return;
317+ }
318+ self.diff_listeners(registry, old_listeners, new_listeners);
319+ self.diff_attributes(old_attributes, new_attributes);
320+ self.diff_children(registry, old_children, new_children);
321+ }
322+ }
323+ }
324+
325+ fn diff_listeners<'a>(
326+ &self,
327+ registry: &mut EventsRegistry,
328+ old: &'a [Listener<'a>],
329+ new: &'a [Listener<'a>],
330+ ) {
331+ debug!(" updating event listeners");
332+
333+ 'outer1: for new_l in new {
334+ unsafe {
335+ // Safety relies on removing `new_l` from the registry manually
336+ // at the end of its lifetime. This happens when we invoke
337+ // `clear_active_listeners` at the start of a new rendering
338+ // phase.
339+ registry.add(new_l);
340+ }
341+ for old_l in old {
342+ if new_l.event == old_l.event {
343+ self.change_list.emit_update_event_listener(new_l);
344+ continue 'outer1;
345+ }
346+ }
347+ self.change_list.emit_new_event_listener(new_l);
348+ }
349+
350+ 'outer2: for old_l in old {
351+ for new_l in new {
352+ if new_l.event == old_l.event {
353+ continue 'outer2;
354+ }
355+ }
356+ self.change_list.emit_remove_event_listener(old_l.event);
357+ }
358+ }
359+
360+ fn diff_attributes(&self, old: &[Attribute], new: &[Attribute]) {
361+ debug!(" updating attributes");
362+
363+ // Do O(n^2) passes to add/update and remove attributes, since
364+ // there are almost always very few attributes.
365+ 'outer: for new_attr in new {
366+ if new_attr.is_volatile() {
367+ self.change_list
368+ .emit_set_attribute(new_attr.name, new_attr.value);
369+ } else {
370+ for old_attr in old {
371+ if old_attr.name == new_attr.name {
372+ if old_attr.value != new_attr.value {
373+ self.change_list
374+ .emit_set_attribute(new_attr.name, new_attr.value);
375+ }
376+ continue 'outer;
377+ }
378+ }
379+ self.change_list
380+ .emit_set_attribute(new_attr.name, new_attr.value);
381+ }
382+ }
383+
384+ 'outer2: for old_attr in old {
385+ for new_attr in new {
386+ if old_attr.name == new_attr.name {
387+ continue 'outer2;
388+ }
389+ }
390+ self.change_list.emit_remove_attribute(old_attr.name);
391+ }
392+ }
393+
394+ fn diff_children<'a>(
395+ &self,
396+ registry: &mut EventsRegistry,
397+ old: &'a [Node<'a>],
398+ new: &'a [Node<'a>],
399+ ) {
400+ debug!(" updating children shared by old and new");
401+
402+ let num_children_to_diff = cmp::min(new.len(), old.len());
403+ let mut new_children = new.iter();
404+ let mut old_children = old.iter();
405+ let mut pushed = false;
406+
407+ for (i, (new_child, old_child)) in new_children
408+ .by_ref()
409+ .zip(old_children.by_ref())
410+ .take(num_children_to_diff)
411+ .enumerate()
412+ {
413+ if i == 0 {
414+ self.change_list.emit_push_first_child();
415+ pushed = true;
416+ } else {
417+ debug_assert!(pushed);
418+ self.change_list.emit_pop_push_next_sibling();
419+ }
420+
421+ self.diff(registry, old_child.clone(), new_child.clone());
422+ }
423+
424+ if old_children.next().is_some() {
425+ debug!(" removing extra old children");
426+ debug_assert!(new_children.next().is_none());
427+ if !pushed {
428+ self.change_list.emit_push_first_child();
429+ } else {
430+ self.change_list.emit_pop_push_next_sibling();
431+ }
432+ self.change_list.emit_remove_self_and_next_siblings();
433+ pushed = false;
434+ } else {
435+ debug!(" creating new children");
436+ for (i, new_child) in new_children.enumerate() {
437+ if i == 0 && pushed {
438+ self.change_list.emit_pop();
439+ pushed = false;
440+ }
441+ self.create(registry, new_child.clone());
442+ self.change_list.emit_append_child();
443+ }
444+ }
445+
446+ debug!(" done updating children");
447+ if pushed {
448+ self.change_list.emit_pop();
449+ }
450+ }
451+
452+ fn create<'a>(&self, registry: &mut EventsRegistry, node: Node<'a>) {
453+ match node {
454+ Node::Text(TextNode { text }) => {
455+ self.change_list.emit_create_text_node(text);
456+ }
457+ Node::Element(ElementNode {
458+ tag_name,
459+ listeners,
460+ attributes,
461+ children,
462+ }) => {
463+ self.change_list.emit_create_element(tag_name);
464+ for l in listeners {
465+ unsafe {
466+ registry.add(l);
467+ }
468+ self.change_list.emit_new_event_listener(l);
469+ }
470+ for attr in attributes {
471+ self.change_list.emit_set_attribute(&attr.name, &attr.value);
472+ }
473+ for child in children {
474+ self.create(registry, child.clone());
475+ self.change_list.emit_append_child();
476+ }
477+ }
478+ }
479+ }
327}
480}
328
481
329−fn request_animation_frame(f: &Closure<dyn FnMut()>) {
482+fn request_animation_frame(f: &Closure<FnMut()>) {
330 web_sys::window()
483 web_sys::window()
331 .expect_throw("should have a window")
484 .expect_throw("should have a window")
332 .request_animation_frame(f.as_ref().unchecked_ref())
485 .request_animation_frame(f.as_ref().unchecked_ref())
@@ -343,7 +496,7 @@ where
343 let f = Closure::wrap(Box::new(move || {
496 let f = Closure::wrap(Box::new(move || {
344 *g.borrow_mut() = None;
497 *g.borrow_mut() = None;
345 f();
498 f();
346− }) as Box<dyn FnMut()>);
499+ }) as Box<FnMut()>);
347 request_animation_frame(&f);
500 request_animation_frame(&f);
348
501
349 *h.borrow_mut() = Some(f);
502 *h.borrow_mut() = Some(f);
@@ -374,23 +527,25 @@ impl VdomWeak {
374 /// Replace the root rendering component with the new `root`.
527 /// Replace the root rendering component with the new `root`.
375 ///
528 ///
376 /// Returns a future that resolves to the *old* root component.
529 /// Returns a future that resolves to the *old* root component.
377− pub async fn set_component(
530+ pub fn set_component(
378 self,
531 self,
379 root: Box<dyn RootRender>,
532 root: Box<dyn RootRender>,
380− ) -> Result<Box<dyn RootRender + 'static>, VdomDroppedError> {
533+ ) -> impl Future<Item = Box<dyn RootRender + 'static>, Error = VdomDroppedError> {
534+ futures::future::ok(self.inner.upgrade())
535+ .and_then(|inner| inner.ok_or(()))
381− let inner = self.inner.upgrade().ok_or(VdomDroppedError {})?;
536+ .map_err(|_| VdomDroppedError {})
382−
537+ .and_then(|inner| {
383− // Wait for a new tick of the micro-task queue
384− let _ = JsFuture::from(js_sys::Promise::resolve(&JsValue::null())).await;
538+ let promise = js_sys::Promise::resolve(&JsValue::null());
385−
539+ JsFuture::from(promise)
540+ .map_err(|_| VdomDroppedError {})
541+ .and_then(move |_| {
386− let old = {
542+ let old = {
387− let mut exclusive = inner.exclusive.borrow_mut();
543+ let mut exclusive = inner.exclusive.borrow_mut();
388− mem::replace(&mut *exclusive.component.as_mut().unwrap_throw(), root)
544+ mem::replace(&mut *exclusive.component.as_mut().unwrap_throw(), root)
389− };
545+ };
390−
391− VdomWeak::new(&inner).render().await?;
546+ VdomWeak::new(&inner).render().map(|_| old)
392−
547+ })
393− Ok(old)
548+ })
394 }
549 }
395
550
396 /// Execute `f` with a reference to this virtual DOM's root rendering
551 /// Execute `f` with a reference to this virtual DOM's root rendering
@@ -398,18 +553,24 @@ impl VdomWeak {
398 ///
553 ///
399 /// To ensure exclusive access to the root rendering component, the
554 /// To ensure exclusive access to the root rendering component, the
400 /// invocation takes place on a new tick of the micro-task queue.
555 /// invocation takes place on a new tick of the micro-task queue.
401− pub async fn with_component<F, T>(&self, f: F) -> Result<T, VdomDroppedError>
556+ pub fn with_component<F, T>(&self, f: F) -> impl Future<Item = T, Error = VdomDroppedError>
402 where
557 where
403 F: 'static + FnOnce(&mut dyn RootRender) -> T,
558 F: 'static + FnOnce(&mut dyn RootRender) -> T,
404 {
559 {
560+ futures::future::ok(self.inner.upgrade())
561+ .and_then(|inner| inner.ok_or(()))
405− let inner = self.inner.upgrade().ok_or(VdomDroppedError {})?;
562+ .map_err(|_| VdomDroppedError {})
406−
563+ .and_then(|inner| {
407− // Wait for a new tick of the micro-task queue
564+ let mut f = Some(f);
408− let _ = JsFuture::from(js_sys::Promise::resolve(&JsValue::null())).await;
565+ let promise = js_sys::Promise::resolve(&JsValue::null());
409−
566+ JsFuture::from(promise)
567+ .map_err(|_| VdomDroppedError {})
568+ .map(move |_| {
569+ let f = f.take().unwrap_throw();
410− let mut exclusive = inner.exclusive.borrow_mut();
570+ let mut exclusive = inner.exclusive.borrow_mut();
411−
412− Ok(f(exclusive.component_raw_mut()))
571+ f(exclusive.component_raw_mut())
572+ })
573+ })
413 }
574 }
414
575
415 /// Schedule a render to occur during the next animation frame.
576 /// Schedule a render to occur during the next animation frame.
@@ -418,12 +579,7 @@ impl VdomWeak {
418 /// `render` instead.
579 /// `render` instead.
419 pub fn schedule_render(&self) {
580 pub fn schedule_render(&self) {
420 debug!("VdomWeak::schedule_render");
581 debug!("VdomWeak::schedule_render");
421−
422− let future = self.render();
423−
424− wasm_bindgen_futures::spawn_local(async move {
582+ wasm_bindgen_futures::spawn_local(self.render().map_err(|_| ()));
425− let _ = future.await;
426− });
427 }
583 }
428
584
429 /// Schedule a render to occur during the next animation frame and return a
585 /// Schedule a render to occur during the next animation frame and return a
@@ -431,46 +587,43 @@ impl VdomWeak {
431 ///
587 ///
432 /// If you don't want to do more things after the render completes, then use
588 /// If you don't want to do more things after the render completes, then use
433 /// `schedule_render` instead of `render`.
589 /// `schedule_render` instead of `render`.
434− pub fn render(&self) -> impl Future<Output = Result<(), VdomDroppedError>> {
590+ pub fn render(&self) -> impl Future<Item = (), Error = VdomDroppedError> {
591+ debug!("VdomWeak::render: initiating render in new animation frame");
435− let inner = self.inner.upgrade();
592+ futures::future::ok(self.inner.upgrade())
436−
593+ .and_then(|inner| inner.ok_or(()))
437− async move {
438− let inner = inner.ok_or(VdomDroppedError {})?;
594+ .map_err(|_| VdomDroppedError {})
439−
595+ .and_then(|inner| {
440− let promise = inner.shared.render_scheduled.take().unwrap_or_else(|| {
596+ let promise = inner.shared.render_scheduled.take().unwrap_or_else(|| {
441− js_sys::Promise::new(&mut |resolve, reject| {
597+ js_sys::Promise::new(&mut |resolve, reject| {
442− let vdom = VdomWeak {
598+ let vdom = VdomWeak {
443− inner: Rc::downgrade(&inner),
599+ inner: Rc::downgrade(&inner),
444− };
600+ };
445−
446− with_animation_frame(move || match vdom.inner.upgrade() {
601+ with_animation_frame(move || match vdom.inner.upgrade() {
447− None => {
602+ None => {
448− warn!("VdomWeak::render: vdom unmounted before we could render");
603+ warn!("VdomWeak::render: vdom unmounted before we could render");
449− let r = reject.call0(&JsValue::null());
604+ let r = reject.call0(&JsValue::null());
450− debug_assert!(r.is_ok());
605+ debug_assert!(r.is_ok());
451− }
606+ }
452− Some(inner) => {
607+ Some(inner) => {
453− let mut exclusive = inner.exclusive.borrow_mut();
608+ let mut exclusive = inner.exclusive.borrow_mut();
454− exclusive.render();
609+ exclusive.render();
455−
610+
456− // We did the render, so take the promise away
611+ // We did the render, so take the promise away
457− // and let future `render` calls request new
612+ // and let future `render` calls request new
458− // animation frames.
613+ // animation frames.
459− let _ = inner.shared.render_scheduled.take();
614+ let _ = inner.shared.render_scheduled.take();
460−
615+
616+ debug!("VdomWeak::render: finished rendering");
461− let r = resolve.call0(&JsValue::null());
617+ let r = resolve.call0(&JsValue::null());
462− debug_assert!(r.is_ok());
618+ debug_assert!(r.is_ok());
463− }
619+ }
464− });
620+ });
465− })
621+ })
466− });
622+ });
467−
468− inner.shared.render_scheduled.set(Some(promise.clone()));
623+ inner.shared.render_scheduled.set(Some(promise.clone()));
469−
470− JsFuture::from(promise)
624+ JsFuture::from(promise)
471− .await
472− .map(drop)
625+ .map(|_| ())
473− .map_err(|_| VdomDroppedError {})
626+ .map_err(|_| VdomDroppedError {})
474− }
627+ })
475 }
628 }
476}
629}
tests/web/cached.jsdeleted−11
@@ -1,11 +0,0 @@
1−let cloneNodeCount = 0;
2−
3−const origCloneNode = Node.prototype.cloneNode;
4−Node.prototype.cloneNode = function (...args) {
5− cloneNodeCount += 1;
6− return origCloneNode.apply(this, args);
7−};
8−
9−export function getCloneNodeCount() {
10− return cloneNodeCount;
11−}
tests/web/cached.rs+51−239
@@ -1,11 +1,11 @@
1−use super::{assert_rendered, before_after, create_element, RenderFn};
1+use super::{assert_rendered, create_element, RenderFn};
2−use dodrio::{builder::*, bumpalo, Cached, Node, Render, RenderContext, RootRender, Vdom};
2+use dodrio::{builder::*, bumpalo::Bump, Cached, Node, Render, Vdom};
3+use futures::prelude::*;
3use std::cell::Cell;
4use std::cell::Cell;
4use std::rc::Rc;
5use std::rc::Rc;
5−use wasm_bindgen::prelude::*;
6+use wasm_bindgen::*;
6use wasm_bindgen_test::*;
7use wasm_bindgen_test::*;
7
8
8−#[derive(Default)]
9pub struct CountRenders {
9pub struct CountRenders {
10 render_count: Cell<usize>,
10 render_count: Cell<usize>,
11}
11}
@@ -18,18 +18,21 @@ impl CountRenders {
18 }
18 }
19}
19}
20
20
21−impl<'a> Render<'a> for CountRenders {
21+impl Render for CountRenders {
22− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
22+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
23+ where
24+ 'a: 'bump,
25+ {
23 let count = self.render_count.get() + 1;
26 let count = self.render_count.get() + 1;
24 self.render_count.set(count);
27 self.render_count.set(count);
25
28
26− let s = bumpalo::format!(in cx.bump, "{}", count);
29+ let s = bumpalo::format!(in bump, "{}", count);
27 text(s.into_bump_str())
30 text(s.into_bump_str())
28 }
31 }
29}
32}
30
33
31−#[wasm_bindgen_test]
34+#[wasm_bindgen_test(async)]
32−async fn uses_cached_render() {
35+fn uses_cached_render() -> impl Future<Item = (), Error = JsValue> {
33 use dodrio::builder::*;
36 use dodrio::builder::*;
34
37
35 let cached = Cached::new(CountRenders::new());
38 let cached = Cached::new(CountRenders::new());
@@ -49,241 +52,50 @@ async fn uses_cached_render() {
49 let vdom6 = vdom0.clone();
52 let vdom6 = vdom0.clone();
50 let vdom7 = vdom0.clone();
53 let vdom7 = vdom0.clone();
51
54
52− // We render, populate the cache, and get "1".
53− vdom0.weak().render().await.unwrap();
54 vdom0
55 vdom0
55 .weak()
56 .weak()
57+ // We render, populate the cache, and get "1".
58+ .render()
59+ .and_then(move |_| {
56− .with_component(move |comp| {
60+ vdom0.weak().with_component(move |comp| {
57− let comp = comp.unwrap_mut::<Cached<CountRenders>>();
61+ let comp = comp.unwrap_mut::<Cached<CountRenders>>();
58− assert_eq!(comp.render_count.get(), 1);
62+ assert_eq!(comp.render_count.get(), 1);
59− assert_rendered(&container1, &RenderFn(|_| text("1")));
63+ assert_rendered(&container1, &RenderFn(|_| text("1")));
64+ })
60 })
65 })
61− .await
62− .unwrap();
63−
64− // We re-render, re-use the cached node, and get "1" again.
66+ // We re-render, re-use the cached node, and get "1" again.
65− vdom1.weak().render().await.unwrap();
67+ .and_then(move |_| vdom1.weak().render())
66− vdom2
68+ .and_then(move |_| {
67− .weak()
68− .with_component(move |comp| {
69+ vdom2.weak().with_component(move |comp| {
69− let comp = comp.unwrap_mut::<Cached<CountRenders>>();
70+ let comp = comp.unwrap_mut::<Cached<CountRenders>>();
70− assert_eq!(comp.render_count.get(), 1);
71+ assert_eq!(comp.render_count.get(), 1);
71− assert_rendered(&container2, &RenderFn(|_| text("1")));
72+ assert_rendered(&container2, &RenderFn(|_| text("1")));
73+ })
72 })
74 })
73− .await
74− .unwrap();
75−
76− // We invalidate the cache, re-render, re-populate the cache, and should now have "2".
75+ // We invalidate the cache, re-render, re-populate the cache, and should
77− vdom3
76+ // now have "2".
78− .weak()
77+ .and_then(move |_| {
79− .with_component(move |comp| {
78+ vdom3.weak().with_component(move |comp| {
80− let comp = comp.unwrap_mut::<Cached<CountRenders>>();
79+ let comp = comp.unwrap_mut::<Cached<CountRenders>>();
81− Cached::invalidate(comp);
80+ Cached::invalidate(comp);
81+ })
82 })
82 })
83− .await
84− .unwrap();
85− vdom4.weak().render().await.unwrap();
83+ .and_then(move |_| vdom4.weak().render())
86− vdom5
84+ .and_then(move |_| {
87− .weak()
88− .with_component(move |comp| {
85+ vdom5.weak().with_component(move |comp| {
89− let comp = comp.unwrap_mut::<Cached<CountRenders>>();
86+ let comp = comp.unwrap_mut::<Cached<CountRenders>>();
90− assert_eq!(comp.render_count.get(), 2);
87+ assert_eq!(comp.render_count.get(), 2);
91− assert_rendered(&container3, &RenderFn(|_| text("2")));
88+ assert_rendered(&container3, &RenderFn(|_| text("2")));
89+ })
92 })
90 })
93− .await
94− .unwrap();
95−
96− // We re-render, re-use the cached node, and get "2" again.
91+ // We re-render, re-use the cached node, and get "2" again.
97− vdom6.weak().render().await.unwrap();
92+ .and_then(move |_| vdom6.weak().render())
98− vdom7
93+ .and_then(move |_| {
99− .weak()
100− .with_component(move |comp| {
94+ vdom7.weak().with_component(move |comp| {
101− let comp = comp.unwrap_mut::<Cached<CountRenders>>();
95+ let comp = comp.unwrap_mut::<Cached<CountRenders>>();
102− assert_eq!(comp.render_count.get(), 2);
96+ assert_eq!(comp.render_count.get(), 2);
103− assert_rendered(&container4, &RenderFn(|_| text("2")));
97+ assert_rendered(&container4, &RenderFn(|_| text("2")));
98+ })
104 })
99 })
105− .await
100+ .map_err(|e| JsValue::from(e.to_string()))
106− .unwrap();
107−}
108−
109−#[wasm_bindgen(module = "/tests/web/cached.js")]
110−extern "C" {
111− #[wasm_bindgen(js_name = getCloneNodeCount)]
112− fn get_clone_node_count() -> u32;
113−}
114−
115−#[wasm_bindgen_test]
116−async fn creating_cached_nodes_uses_clone_node() {
117− use dodrio::builder::*;
118−
119− let container = create_element("div");
120−
121− // The initial vdom render should populate the cache and save a template.
122− let vdom0 = Rc::new(Vdom::new(&container, Cached::new(CountRenders::new())));
123− let vdom1 = vdom0.clone();
124− let _vdom2 = vdom0.clone();
125−
126− let clone_node_count_before = get_clone_node_count();
127−
128− // Render something entirely different...
129− vdom0
130− .weak()
131− .set_component(Box::new(RenderFn(|_| text("hi"))) as Box<dyn RootRender>)
132− .await
133− .unwrap();
134−
135− // Re-render our cached node. Since we are creating it from scratch,
136− // and already have a template from the earlier render, we should
137− // call `cloneNode` here.
138− vdom1
139− .weak()
140− .set_component(Box::new(Cached::new(CountRenders::new())) as Box<dyn RootRender>)
141− .await
142− .unwrap();
143−
144− let clone_node_count_after = get_clone_node_count();
145− assert_eq!(clone_node_count_after, clone_node_count_before + 1);
146−}
147−
148−struct Id(&'static str);
149−
150−impl Default for Id {
151− fn default() -> Id {
152− Id("")
153− }
154−}
155−
156−impl<'a> Render<'a> for Id {
157− fn render(&self, _cx: &mut RenderContext<'a>) -> Node<'a> {
158− text(self.0)
159− }
160−}
161−
162−thread_local! {
163− static WARM_CHEESE: Rc<Cached<Id>> = Rc::new(Cached::new(Id("cheese")));
164− static WARM_CHEESIER: Rc<Cached<Id>> = Rc::new(Cached::new(Id("cheesier")));
165−}
166−
167−fn warm_cheese<'a>(cx: &mut RenderContext<'a>) -> Node<'a> {
168− WARM_CHEESE.with(|c| {
169− let _ = c.render(cx);
170− c.render(cx)
171− })
172−}
173−
174−fn warm_cheesier<'a>(cx: &mut RenderContext<'a>) -> Node<'a> {
175− WARM_CHEESIER.with(|c| {
176− let _ = c.render(cx);
177− c.render(cx)
178− })
179−}
180−
181−before_after! {
182− cold_cache_and_not_cached {
183− before(cx) {
184− Cached::new(Id("cheese")).render(cx)
185− }
186− after(cx) {
187− Id("ravioli").render(cx)
188− }
189− }
190−
191− not_cached_and_cold_cache {
192− before(cx) {
193− Id("ravioli").render(cx)
194− }
195− after(cx) {
196− Cached::new(Id("cheese")).render(cx)
197− }
198− }
199−
200− cold_cache_and_cold_cache_same {
201− before(cx) {
202− Cached::new(Id("cheese")).render(cx)
203− }
204− after(cx) {
205− Cached::new(Id("cheese")).render(cx)
206− }
207− }
208−
209− cold_cache_and_cold_cache_different {
210− before(cx) {
211− Cached::new(Id("cheese")).render(cx)
212− }
213− after(cx) {
214− Cached::new(Id("ravioli")).render(cx)
215− }
216− }
217−
218− warm_cache_and_not_cached {
219− before(cx) {
220− warm_cheese(cx)
221− }
222− after(cx) {
223− Id("ravioli").render(cx)
224− }
225− }
226−
227− not_cached_and_warm_cache {
228− before(cx) {
229− Id("ravioli").render(cx)
230− }
231− after(cx) {
232− warm_cheese(cx)
233− }
234− }
235−
236− warm_cache_and_warm_cache_same {
237− before(cx) {
238− warm_cheese(cx)
239− }
240− after(cx) {
241− warm_cheese(cx)
242− }
243− }
244−
245− warm_cache_and_warm_cache_different {
246− before(cx) {
247− warm_cheese(cx)
248− }
249− after(cx) {
250− warm_cheesier(cx)
251− }
252− }
253−
254− cold_cache_and_warm_cache_same {
255− before(cx) {
256− Cached::new(Id("cheese")).render(cx)
257− }
258− after(cx) {
259− warm_cheese(cx)
260− }
261− }
262−
263− cold_cache_and_warm_cache_different {
264− before(cx) {
265− Cached::new(Id("cheese")).render(cx)
266− }
267− after(cx) {
268− warm_cheesier(cx)
269− }
270− }
271−
272− warm_cache_and_cold_cache_same {
273− before(cx) {
274− warm_cheese(cx)
275− }
276− after(cx) {
277− Cached::new(Id("cheese")).render(cx)
278− }
279− }
280−
281− warm_cache_and_cold_cache_different {
282− before(cx) {
283− warm_cheesier(cx)
284− }
285− after(cx) {
286− Cached::new(Id("cheese")).render(cx)
287− }
288− }
289}
101}
tests/web/events.rs+156−58
@@ -1,6 +1,7 @@
1use super::create_element;
1use super::create_element;
2+use bumpalo::Bump;
2−use dodrio::{Node, Render, RenderContext, Vdom};
3+use dodrio::{Node, Render, Vdom};
3−use futures::future::{select, Either};
4+use futures::Future;
4use std::cell::{Cell, RefCell};
5use std::cell::{Cell, RefCell};
5use std::rc::Rc;
6use std::rc::Rc;
6use wasm_bindgen::prelude::*;
7use wasm_bindgen::prelude::*;
@@ -9,7 +10,7 @@ use wasm_bindgen_test::*;
9
10
10struct EventContainer {
11struct EventContainer {
11 event: &'static str,
12 event: &'static str,
12− on_event: Box<dyn FnMut()>,
13+ on_event: Box<FnMut()>,
13}
14}
14
15
15impl EventContainer {
16impl EventContainer {
@@ -24,10 +25,13 @@ impl EventContainer {
24 }
25 }
25}
26}
26
27
27−impl<'a> Render<'a> for EventContainer {
28+impl Render for EventContainer {
28− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
29+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
30+ where
31+ 'a: 'bump,
32+ {
29 use dodrio::builder::*;
33 use dodrio::builder::*;
30− div(&cx)
34+ div(bump)
31 .attr("id", "target")
35 .attr("id", "target")
32 .on(self.event, |root, _scheduler, _event| {
36 .on(self.event, |root, _scheduler, _event| {
33 (root.unwrap_mut::<EventContainer>().on_event)();
37 (root.unwrap_mut::<EventContainer>().on_event)();
@@ -44,14 +48,14 @@ fn target(container: &web_sys::Element) -> web_sys::HtmlElement {
44 .unchecked_into()
48 .unchecked_into()
45}
49}
46
50
47−#[wasm_bindgen_test]
51+#[wasm_bindgen_test(async)]
48−async fn click() {
52+fn click() -> impl Future<Item = (), Error = JsValue> {
49 let container = create_element("div");
53 let container = create_element("div");
50
54
51− let (sender, receiver) = futures::channel::oneshot::channel();
55+ let (sender, receiver) = futures::sync::oneshot::channel();
52 let mut sender = Some(sender);
56 let mut sender = Some(sender);
53
57
54− let _vdom = Vdom::new(
58+ let vdom = Vdom::new(
55 &container,
59 &container,
56 EventContainer::new("click", move || {
60 EventContainer::new("click", move || {
57 sender
61 sender
@@ -64,14 +68,18 @@ async fn click() {
64
68
65 target(&container).click();
69 target(&container).click();
66
70
67− receiver.await.unwrap();
71+ receiver
72+ .map(|_| {
73+ drop(vdom);
74+ })
75+ .map_err(|e| JsValue::from(e.to_string()))
68}
76}
69
77
70−#[wasm_bindgen_test]
78+#[wasm_bindgen_test(async)]
71−async fn updated_listener_is_called() {
79+fn updated_listener_is_called() -> impl Future<Item = (), Error = JsValue> {
72 let container = create_element("div");
80 let container = create_element("div");
73
81
74− let (first_sender, first_receiver) = futures::channel::oneshot::channel();
82+ let (first_sender, first_receiver) = futures::sync::oneshot::channel();
75 let mut first_sender = Some(first_sender);
83 let mut first_sender = Some(first_sender);
76
84
77 let vdom = Vdom::new(
85 let vdom = Vdom::new(
@@ -85,7 +93,7 @@ async fn updated_listener_is_called() {
85 }),
93 }),
86 );
94 );
87
95
88− let (second_sender, second_receiver) = futures::channel::oneshot::channel();
96+ let (second_sender, second_receiver) = futures::sync::oneshot::channel();
89 let mut second_sender = Some(second_sender);
97 let mut second_sender = Some(second_sender);
90
98
91 vdom.weak()
99 vdom.weak()
@@ -96,22 +104,25 @@ async fn updated_listener_is_called() {
96 .send("second")
104 .send("second")
97 .expect_throw("should not have dropped the receiver");
105 .expect_throw("should not have dropped the receiver");
98 })))
106 })))
99− .await
107+ .map_err(|e| JsValue::from(e.to_string()))
100− .unwrap();
108+ .and_then(move |_| {
101−
102− target(&container).click();
109+ target(&container).click();
103−
110+
111+ first_receiver
104− match select(first_receiver, second_receiver).await {
112+ .select(second_receiver)
105− Either::Left((Ok(_), _)) => panic!(),
113+ .map(|(which, _other)| which)
114+ .or_else(|(_error, other)| other)
115+ .map(|which| {
106− Either::Left((Err(_), second)) => assert_eq!(second.await, Ok("second")),
116+ assert_eq!(which, "second");
107− Either::Right((Ok(msg), _)) => assert_eq!(msg, "second"),
117+ drop(vdom);
108− Either::Right((Err(_), _)) => panic!(),
118+ })
119+ .map_err(|e| JsValue::from(e.to_string()))
109− }
120+ })
110}
121}
111
122
112struct ListensOnlyOnFirstRender {
123struct ListensOnlyOnFirstRender {
113 count: Cell<usize>,
124 count: Cell<usize>,
114− callback: Box<dyn FnMut()>,
125+ callback: Box<FnMut()>,
115}
126}
116
127
117impl ListensOnlyOnFirstRender {
128impl ListensOnlyOnFirstRender {
@@ -126,14 +137,17 @@ impl ListensOnlyOnFirstRender {
126 }
137 }
127}
138}
128
139
129−impl<'a> Render<'a> for ListensOnlyOnFirstRender {
140+impl Render for ListensOnlyOnFirstRender {
130− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
141+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
142+ where
143+ 'a: 'bump,
144+ {
131 use dodrio::builder::*;
145 use dodrio::builder::*;
132
146
133 let count = self.count.get();
147 let count = self.count.get();
134 self.count.set(count + 1);
148 self.count.set(count + 1);
135
149
136− let mut elem = div(&cx).attr("id", "target");
150+ let mut elem = div(bump).attr("id", "target");
137 if count == 0 {
151 if count == 0 {
138 elem = elem.on("click", |root, _scheduler, _event| {
152 elem = elem.on("click", |root, _scheduler, _event| {
139 (root.unwrap_mut::<ListensOnlyOnFirstRender>().callback)();
153 (root.unwrap_mut::<ListensOnlyOnFirstRender>().callback)();
@@ -143,11 +157,11 @@ impl<'a> Render<'a> for ListensOnlyOnFirstRender {
143 }
157 }
144}
158}
145
159
146−#[wasm_bindgen_test]
160+#[wasm_bindgen_test(async)]
147−async fn removed_listener_is_not_called() {
161+fn removed_listener_is_not_called() -> impl Future<Item = (), Error = JsValue> {
148 let container = create_element("div");
162 let container = create_element("div");
149
163
150− let (outer_sender, outer_receiver) = futures::channel::oneshot::channel();
164+ let (outer_sender, outer_receiver) = futures::sync::oneshot::channel();
151 let outer_sender = Rc::new(RefCell::new(Some(outer_sender)));
165 let outer_sender = Rc::new(RefCell::new(Some(outer_sender)));
152 let outer_listener = Closure::wrap(Box::new(move |_| {
166 let outer_listener = Closure::wrap(Box::new(move |_| {
153 outer_sender
167 outer_sender
@@ -156,13 +170,13 @@ async fn removed_listener_is_not_called() {
156 .expect_throw("should only invoke outer_listener once")
170 .expect_throw("should only invoke outer_listener once")
157 .send("outer")
171 .send("outer")
158 .expect_throw("should not have dropped receiver");
172 .expect_throw("should not have dropped receiver");
159− }) as Box<dyn FnMut(web_sys::Event)>);
173+ }) as Box<FnMut(web_sys::Event)>);
160
174
161 container
175 container
162 .add_event_listener_with_callback("click", outer_listener.as_ref().unchecked_ref())
176 .add_event_listener_with_callback("click", outer_listener.as_ref().unchecked_ref())
163 .unwrap();
177 .unwrap();
164
178
165− let (vdom_sender, vdom_receiver) = futures::channel::oneshot::channel();
179+ let (vdom_sender, vdom_receiver) = futures::sync::oneshot::channel();
166 let mut vdom_sender = Some(vdom_sender);
180 let mut vdom_sender = Some(vdom_sender);
167
181
168 // Render a vdom with a listener into container.
182 // Render a vdom with a listener into container.
@@ -177,26 +191,33 @@ async fn removed_listener_is_not_called() {
177 }),
191 }),
178 );
192 );
179
193
194+ vdom.weak()
180− // Re-render, so we aren't listening anymore.
195+ // Re-render, so we aren't listening anymore.
181− vdom.weak().render().await.unwrap();
196+ .render()
182−
197+ .map_err(|e| JsValue::from(e.to_string()))
198+ .and_then(move |_| {
183− target(&container).click();
199+ target(&container).click();
184−
200+
185− // We should get our container's event handler fired, and not the unmounted
201+ // We should get our container's event handler fired, and not the unmounted
186− // vdom's event handler.
202+ // vdom's event handler.
203+ outer_receiver
187− match select(outer_receiver, vdom_receiver).await {
204+ .select(vdom_receiver)
205+ .map(|(which, _other)| which)
206+ .or_else(|(_error, other)| other)
207+ .map(|which| {
188− Either::Left((Ok(msg), _)) => assert_eq!(msg, ("outer")),
208+ assert_eq!(which, "outer");
189− Either::Left((Err(_), _)) => panic!(),
209+ drop(outer_listener);
190− Either::Right((Ok(_), _)) => panic!(),
210+ drop(vdom)
191− Either::Right((Err(_), outer)) => assert_eq!(outer.await, Ok("outer")),
211+ })
212+ .map_err(|e| JsValue::from(e.to_string()))
192− }
213+ })
193}
214}
194
215
195−#[wasm_bindgen_test]
216+#[wasm_bindgen_test(async)]
196−async fn event_does_not_fire_after_unmounting() {
217+fn event_does_not_fire_after_unmounting() -> impl Future<Item = (), Error = JsValue> {
197 let container = create_element("div");
218 let container = create_element("div");
198
219
199− let (outer_sender, outer_receiver) = futures::channel::oneshot::channel();
220+ let (outer_sender, outer_receiver) = futures::sync::oneshot::channel();
200 let outer_sender = Rc::new(RefCell::new(Some(outer_sender)));
221 let outer_sender = Rc::new(RefCell::new(Some(outer_sender)));
201 let outer_listener = Closure::wrap(Box::new(move |_| {
222 let outer_listener = Closure::wrap(Box::new(move |_| {
202 outer_sender
223 outer_sender
@@ -205,13 +226,13 @@ async fn event_does_not_fire_after_unmounting() {
205 .expect_throw("should only invoke outer_listener once")
226 .expect_throw("should only invoke outer_listener once")
206 .send("outer")
227 .send("outer")
207 .expect_throw("should not have dropped receiver");
228 .expect_throw("should not have dropped receiver");
208− }) as Box<dyn FnMut(web_sys::Event)>);
229+ }) as Box<FnMut(web_sys::Event)>);
209
230
210 container
231 container
211 .add_event_listener_with_callback("click", outer_listener.as_ref().unchecked_ref())
232 .add_event_listener_with_callback("click", outer_listener.as_ref().unchecked_ref())
212 .unwrap();
233 .unwrap();
213
234
214− let (vdom_sender, vdom_receiver) = futures::channel::oneshot::channel();
235+ let (vdom_sender, vdom_receiver) = futures::sync::oneshot::channel();
215 let mut vdom_sender = Some(vdom_sender);
236 let mut vdom_sender = Some(vdom_sender);
216
237
217 // Render a vdom with a listener into container.
238 // Render a vdom with a listener into container.
@@ -241,10 +262,87 @@ async fn event_does_not_fire_after_unmounting() {
241
262
242 // We should get our container's event handler fired, and not the unmounted
263 // We should get our container's event handler fired, and not the unmounted
243 // vdom's event handler.
264 // vdom's event handler.
265+ outer_receiver
244− match select(outer_receiver, vdom_receiver).await {
266+ .select(vdom_receiver)
267+ .map(|(which, _other)| which)
268+ .or_else(|(_error, other)| other)
269+ .map(|which| {
245− Either::Left((Ok(msg), _)) => assert_eq!(msg, ("outer")),
270+ assert_eq!(which, "outer");
246− Either::Left((Err(_), _)) => panic!(),
271+ drop(outer_listener);
247− Either::Right((Ok(_), _)) => panic!(),
272+ })
248− Either::Right((Err(_), outer)) => assert_eq!(outer.await, Ok("outer")),
273+ .map_err(|e| JsValue::from(e.to_string()))
274+}
275+
276+struct CountRendersAndListen {
277+ count: Cell<usize>,
278+ callback: Box<FnMut(usize)>,
279+}
280+
281+impl CountRendersAndListen {
282+ fn new<F>(callback: F) -> CountRendersAndListen
283+ where
284+ F: 'static + FnMut(usize),
285+ {
286+ CountRendersAndListen {
287+ count: Cell::new(0),
288+ callback: Box::new(callback),
289+ }
290+ }
291+}
292+
293+impl Render for CountRendersAndListen {
294+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
295+ where
296+ 'a: 'bump,
297+ {
298+ use dodrio::builder::*;
299+
300+ let count = self.count.get() + 1;
301+ self.count.set(count);
302+
303+ button(bump)
304+ .attr("id", "target")
305+ .on("click", |comp, _vdom, _event| {
306+ let comp: &mut _ = comp.unwrap_mut::<dodrio::Cached<CountRendersAndListen>>();
307+ let comp: &mut CountRendersAndListen = comp;
308+ let n = comp.count.get();
309+ (comp.callback)(n);
310+ })
311+ .finish()
249 }
312 }
250}
313}
314+
315+#[wasm_bindgen_test(async)]
316+fn cached_listeners_work_correctly() -> impl Future<Item = (), Error = JsValue> {
317+ let container = create_element("div");
318+
319+ let (sender, receiver) = futures::sync::oneshot::channel();
320+ let mut sender = Some(sender);
321+ let vdom = Vdom::new(
322+ &container,
323+ dodrio::Cached::new(CountRendersAndListen::new(move |n| {
324+ sender.take().unwrap().send(n).unwrap();
325+ })),
326+ );
327+
328+ let weak0 = vdom.weak();
329+ let weak1 = vdom.weak();
330+ let weak2 = vdom.weak();
331+ vdom.weak()
332+ // Re-render a few times, so we are using the cached version.
333+ .render()
334+ .and_then(move |_| weak0.render())
335+ .and_then(move |_| weak1.render())
336+ .and_then(move |_| weak2.render())
337+ .map_err(|e| JsValue::from(e.to_string()))
338+ .and_then(move |_| {
339+ target(&container).click();
340+
341+ receiver
342+ .map(|n| {
343+ assert_eq!(n, 1);
344+ drop(vdom);
345+ })
346+ .map_err(|e| JsValue::from(e.to_string()))
347+ })
348+}
tests/web/js_api.rs+47−37
@@ -1,7 +1,8 @@
1use super::{assert_rendered, create_element, RenderFn};
1use super::{assert_rendered, create_element, RenderFn};
2−use dodrio::{builder::*, Node, Render, RenderContext, Vdom};
2+use dodrio::{builder::*, bumpalo::Bump, Node, Render, Vdom};
3use dodrio_js_api::JsRender;
3use dodrio_js_api::JsRender;
4−use futures::channel::oneshot;
4+use futures::prelude::*;
5+use futures::sync::oneshot;
5use js_sys::Object;
6use js_sys::Object;
6use std::rc::Rc;
7use std::rc::Rc;
7use wasm_bindgen::prelude::*;
8use wasm_bindgen::prelude::*;
@@ -13,7 +14,7 @@ pub struct WrapJs {
13}
14}
14
15
15impl WrapJs {
16impl WrapJs {
16− pub fn new() -> (WrapJs, oneshot::Receiver<()>, Closure<dyn FnMut()>) {
17+ pub fn new() -> (WrapJs, oneshot::Receiver<()>, Closure<FnMut()>) {
17 let (sender, receiver) = oneshot::channel();
18 let (sender, receiver) = oneshot::channel();
18
19
19 let on_after_render = Closure::wrap(Box::new({
20 let on_after_render = Closure::wrap(Box::new({
@@ -25,7 +26,7 @@ impl WrapJs {
25 .send(())
26 .send(())
26 .expect_throw("receiver should not have been dropped");
27 .expect_throw("receiver should not have been dropped");
27 }
28 }
28− }) as Box<dyn FnMut()>);
29+ }) as Box<FnMut()>);
29
30
30 let component = WrapJs {
31 let component = WrapJs {
31 inner: JsRender::new(JsComponent::new(&on_after_render)),
32 inner: JsRender::new(JsComponent::new(&on_after_render)),
@@ -35,11 +36,14 @@ impl WrapJs {
35 }
36 }
36}
37}
37
38
38−impl<'a> Render<'a> for WrapJs {
39+impl Render for WrapJs {
39− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
40+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
41+ where
42+ 'a: 'bump,
43+ {
40− div(&cx)
44+ div(bump)
41 .attr("class", "wrap-js")
45 .attr("class", "wrap-js")
42− .children([self.inner.render(cx)])
46+ .children([self.inner.render(bump)])
43 .finish()
47 .finish()
44 }
48 }
45}
49}
@@ -51,7 +55,7 @@ extern "C" {
51 type JsComponent;
55 type JsComponent;
52
56
53 #[wasm_bindgen(constructor)]
57 #[wasm_bindgen(constructor)]
54− fn new(on_after_render: &Closure<dyn FnMut()>) -> JsComponent;
58+ fn new(on_after_render: &Closure<FnMut()>) -> JsComponent;
55}
59}
56
60
57fn eval_js_rendering_component() {
61fn eval_js_rendering_component() {
@@ -95,24 +99,24 @@ fn eval_js_rendering_component() {
95 });
99 });
96}
100}
97
101
98−#[wasm_bindgen_test]
102+#[wasm_bindgen_test(async)]
99−async fn can_use_js_rendering_components() {
103+fn can_use_js_rendering_components() -> impl Future<Item = (), Error = JsValue> {
100 eval_js_rendering_component();
104 eval_js_rendering_component();
101
105
102 let container = create_element("div");
106 let container = create_element("div");
103− let (component, receiver, _closure) = WrapJs::new();
107+ let (component, receiver, closure) = WrapJs::new();
104− let _vdom = Rc::new(Vdom::new(&container, component));
105
108
109+ let vdom = Rc::new(Vdom::new(&container, component));
106 assert_rendered(
110 assert_rendered(
107 &container,
111 &container,
108− &RenderFn(|cx| {
112+ &RenderFn(|bump| {
109− div(&cx)
113+ div(bump)
110 .attr("class", "wrap-js")
114 .attr("class", "wrap-js")
111− .children([span(&cx)
115+ .children([span(bump)
112 .attr("class", "js-component")
116 .attr("class", "js-component")
113 .children([
117 .children([
114 text("Here is some plain text"),
118 text("Here is some plain text"),
115− b(&cx)
119+ b(bump)
116 .children([text("...and here is some bold text")])
120 .children([text("...and here is some bold text")])
117 .finish(),
121 .finish(),
118 text("0"),
122 text("0"),
@@ -130,24 +134,30 @@ async fn can_use_js_rendering_components() {
130 .expect_throw("should be an `HTMLElement` object")
134 .expect_throw("should be an `HTMLElement` object")
131 .click();
135 .click();
132
136
133− receiver.await.unwrap();
137+ receiver
134−
138+ .map(move |_| {
135− assert_rendered(
139+ assert_rendered(
136− &container,
140+ &container,
137− &RenderFn(|cx| {
141+ &RenderFn(|bump| {
138− div(&cx)
142+ div(bump)
139− .attr("class", "wrap-js")
143+ .attr("class", "wrap-js")
140− .children([span(&cx)
144+ .children([span(bump)
141− .attr("class", "js-component")
145+ .attr("class", "js-component")
142− .children([
146+ .children([
143− text("Here is some plain text"),
147+ text("Here is some plain text"),
144− b(&cx)
148+ b(bump)
145− .children([text("...and here is some bold text")])
149+ .children([text("...and here is some bold text")])
146− .finish(),
150+ .finish(),
147− text("1"),
151+ text("1"),
148− ])
152+ ])
149− .finish()])
153+ .finish()])
150− .finish()
154+ .finish()
151− }),
155+ }),
152− );
156+ );
157+
158+ drop(vdom);
159+ drop(container);
160+ drop(closure);
161+ })
162+ .map_err(|e| e.to_string().into())
153}
163}
tests/web/keyed.jsdeleted−25
@@ -1,25 +0,0 @@
1−export function saveKeyedElements(container) {
2− const saved = new Map;
3− const children = container.querySelectorAll(".keyed");
4− for (const child of children) {
5− saved.set(child.id, child);
6− }
7− return saved;
8−}
9−
10−function assertEq(a, b, msg) {
11− if (a !== b) {
12− throw new Error(`assertEq failed: ${a} !== ${b}; ${msg}`);
13− }
14−}
15−
16−export function checkKeyedElements(container, saved) {
17− const children = container.querySelectorAll(".keyed");
18− for (const child of children) {
19− console.log(`checking child=${child.outerHTML}`);
20− const original = saved.get(child.id);
21− if (original) {
22− assertEq(original, child, `did not preserve child with key=${child.id}`);
23− }
24− }
25−}
tests/web/keyed.rsdeleted−269
@@ -1,269 +0,0 @@
1−use crate::{assert_rendered, create_element};
2−use dodrio::{builder::*, bumpalo, Node, Render, RenderContext, Vdom};
3−use log::*;
4−use std::rc::Rc;
5−use wasm_bindgen::prelude::*;
6−use wasm_bindgen_test::*;
7−
8−struct Keyed(u16);
9−
10−impl<'a> Render<'a> for Keyed {
11− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
12− let key = bumpalo::format!(in cx.bump, "{}", self.0).into_bump_str();
13− div(&cx)
14− .attr("class", "keyed")
15− .attr("id", key)
16− .key(self.0 as u32)
17− .finish()
18− }
19−}
20−
21−fn keyed<'a, Keys>(cx: &mut RenderContext<'a>, keys: Keys) -> Node<'a>
22−where
23− Keys: AsRef<[u16]>,
24−{
25− let mut parent = div(&cx).attr("class", "parent");
26−
27− for &k in keys.as_ref() {
28− parent = parent.child(Keyed(k).render(cx));
29− }
30−
31− parent.finish()
32−}
33−
34−async fn assert_keyed<Before, After>(before: Before, after: After) -> Result<(), JsValue>
35−where
36− Before: 'static + for<'a> Render<'a>,
37− After: 'static + for<'a> Render<'a>,
38−{
39− #[wasm_bindgen(module = "/tests/web/keyed.js")]
40− extern "C" {
41− #[wasm_bindgen(js_name = saveKeyedElements)]
42− fn save_keyed_elements(container: &web_sys::Element) -> JsValue;
43−
44− #[wasm_bindgen(js_name = checkKeyedElements)]
45− fn check_keyed_elements(container: &web_sys::Element, saved: JsValue);
46− }
47−
48− let container = create_element("div");
49−
50− let before = Rc::new(before);
51− let after = Rc::new(after);
52−
53− debug!("====== Rendering the *before* DOM into the physical DOM ======");
54− let vdom1 = Rc::new(Vdom::new(&container, before.clone()));
55− let _vdom2 = vdom1.clone();
56− let saved = save_keyed_elements(&container);
57−
58− debug!("====== Checking the *before* DOM against the physical DOM ======");
59− assert_rendered(&container, &before);
60−
61− debug!("====== Rendering the *after* DOM into the physical DOM ======");
62− let weak = vdom1.weak();
63− weak.set_component(Box::new(after.clone()))
64− .await
65− .map_err(|e| e.to_string())?;
66−
67− debug!("====== Checking the *after* DOM against the physical DOM ======");
68− assert_rendered(&container, &after);
69− check_keyed_elements(&container, saved);
70−
71− Ok(())
72−}
73−
74−macro_rules! keyed_tests {
75− ( $(
76− $name:ident {
77− before($before_cx:ident) {
78− $( $before:tt )*
79− }
80− after($after_cx:ident) {
81− $( $after:tt )*
82− }
83− }
84− )* ) => {
85− $(
86− #[wasm_bindgen_test]
87− async fn $name() {
88− use crate::RenderFn;
89− log::debug!("############### {} ###############", stringify!($name));
90− assert_keyed(
91− RenderFn(|$before_cx| { $( $before )* }),
92− RenderFn(|$after_cx| { $( $after )* }),
93− )
94− .await
95− .unwrap()
96− }
97− )*
98− }
99−}
100−
101−keyed_tests! {
102− same_order {
103− before(cx) {
104− keyed(cx, [1, 2, 3])
105− }
106− after(cx) {
107− keyed(cx, [1, 2, 3])
108− }
109− }
110−
111− same_order_append {
112− before(cx) {
113− keyed(cx, [1])
114− }
115− after(cx) {
116− keyed(cx, [1, 2])
117− }
118− }
119−
120− same_order_delete {
121− before(cx) {
122− keyed(cx, [1, 2, 3])
123− }
124− after(cx) {
125− keyed(cx, [1, 2])
126− }
127− }
128−
129− same_suffix {
130− before(cx) {
131− keyed(cx, [1, 2, 3])
132− }
133− after(cx) {
134− keyed(cx, [4, 2, 3])
135− }
136− }
137−
138− same_prefix_and_suffix_reorder_middle {
139− before(cx) {
140− keyed(cx, [1, 2, 3, 4])
141− }
142− after(cx) {
143− keyed(cx, [1, 3, 2, 4])
144− }
145− }
146−
147− same_prefix_and_suffix_new_middle {
148− before(cx) {
149− keyed(cx, [2, 3, 4, 5])
150− }
151− after(cx) {
152− keyed(cx, [2, 7, 8, 5])
153− }
154− }
155−
156− reverse_order {
157− before(cx) {
158− keyed(cx, [1, 2, 3])
159− }
160− after(cx) {
161− keyed(cx, [3, 2, 1])
162− }
163− }
164−
165− no_shared_keys {
166− before(cx) {
167− keyed(cx, [1, 2])
168− }
169− after(cx) {
170− keyed(cx, [3, 4])
171− }
172− }
173−
174− new_keys_in_middle {
175− before(cx) {
176− keyed(cx, [1, 2])
177− }
178− after(cx) {
179− keyed(cx, [1, 3, 4, 2])
180− }
181− }
182−
183− new_keys_at_start_and_end {
184− before(cx) {
185− keyed(cx, [1, 2, 3])
186− }
187− after(cx) {
188− keyed(cx, [4, 1, 2, 3, 5])
189− }
190− }
191−
192− delete_prefix {
193− before(cx) {
194− keyed(cx, [1, 2, 3])
195− }
196− after(cx) {
197− keyed(cx, [2, 3])
198− }
199− }
200−
201− delete_suffix {
202− before(cx) {
203− keyed(cx, [1, 2, 3])
204− }
205− after(cx) {
206− keyed(cx, [1, 2])
207− }
208− }
209−
210− delete_middle {
211− before(cx) {
212− keyed(cx, [1, 2, 3])
213− }
214− after(cx) {
215− keyed(cx, [1, 3])
216− }
217− }
218−
219− nested_keyed_children {
220− before(cx) {
221− ul(&cx)
222− .children([
223− li(&cx)
224− .key(1)
225− .children([
226− keyed(cx, [2, 3, 4])
227− ])
228− .finish(),
229− li(&cx)
230− .key(5)
231− .children([
232− keyed(cx, [6, 7, 8])
233− ])
234− .finish(),
235− li(&cx)
236− .key(9)
237− .children([
238− keyed(cx, [10, 11, 12])
239− ])
240− .finish(),
241− ])
242− .finish()
243− }
244− after(cx) {
245− ul(&cx)
246− .children([
247− li(&cx)
248− .key(9)
249− .children([
250− keyed(cx, [12, 11, 10])
251− ])
252− .finish(),
253− li(&cx)
254− .key(5)
255− .children([
256− keyed(cx, [8, 7, 6])
257− ])
258− .finish(),
259− li(&cx)
260− .key(1)
261− .children([
262− keyed(cx, [4, 3, 2])
263− ])
264− .finish(),
265− ])
266− .finish()
267− }
268− }
269−}
tests/web/main.rs+37−60
@@ -1,14 +1,11 @@
1//! Test suite for the Web and headless browsers.
1//! Test suite for the Web and headless browsers.
2
2
3−#![cfg(all(feature = "xxx-unstable-internal-use-only", target_arch = "wasm32"))]
3+#![cfg(target_arch = "wasm32")]
4
4
5use bumpalo::Bump;
5use bumpalo::Bump;
6−use dodrio::{
7− Attribute, CachedSet, ElementNode, Node, NodeKind, Render, RenderContext, TextNode, Vdom,
6+use dodrio::{Attribute, Node, Render, Vdom};
8−};
7+use futures::prelude::*;
9−use fxhash::FxHashMap;
10use log::*;
8use log::*;
11−use std::cell::RefCell;
12use std::rc::Rc;
9use std::rc::Rc;
13use wasm_bindgen::prelude::*;
10use wasm_bindgen::prelude::*;
14use wasm_bindgen::JsCast;
11use wasm_bindgen::JsCast;
@@ -19,7 +16,6 @@ wasm_bindgen_test_configure!(run_in_browser);
19pub mod cached;
16pub mod cached;
20pub mod events;
17pub mod events;
21pub mod js_api;
18pub mod js_api;
22−pub mod keyed;
23pub mod render;
19pub mod render;
24
20
25pub fn window() -> web_sys::Window {
21pub fn window() -> web_sys::Window {
@@ -50,20 +46,15 @@ pub fn init_logging() {
50
46
51/// Assert that the `container` contains the physical DOM tree that matches
47/// Assert that the `container` contains the physical DOM tree that matches
52/// `r`'s rendered virtual DOM.
48/// `r`'s rendered virtual DOM.
53−pub fn assert_rendered<R: for<'a> Render<'a>>(container: &web_sys::Element, r: &R) {
49+pub fn assert_rendered<R: Render>(container: &web_sys::Element, r: &R) {
54 init_logging();
50 init_logging();
55
51
56− let cached_set = &RefCell::new(CachedSet::default());
57 let bump = &Bump::new();
52 let bump = &Bump::new();
58− let templates = &mut FxHashMap::default();
59− let cx = &mut RenderContext::new(bump, cached_set, templates);
60− let node = r.render(cx);
53+ let node = r.render(bump);
61 let child = container
54 let child = container
62 .first_child()
55 .first_child()
63 .expect("container does not have anything rendered into it?");
56 .expect("container does not have anything rendered into it?");
64−
65− let cached_set = cached_set.borrow();
66− check_node(&cached_set, &child, &node);
57+ check_node(&child, &node);
67
58
68 fn stringify_actual_node(n: &web_sys::Node) -> String {
59 fn stringify_actual_node(n: &web_sys::Node) -> String {
69 if let Some(el) = n.dyn_ref::<web_sys::Element>() {
60 if let Some(el) = n.dyn_ref::<web_sys::Element>() {
@@ -73,12 +64,12 @@ pub fn assert_rendered<R: for<'a> Render<'a>>(container: &web_sys::Element, r: &
73 }
64 }
74 }
65 }
75
66
76− fn check_node(cached_set: &CachedSet, actual: &web_sys::Node, expected: &Node) {
67+ fn check_node(actual: &web_sys::Node, expected: &Node) {
77 debug!("check_render:");
68 debug!("check_render:");
78 debug!(" actual = {}", stringify_actual_node(&actual));
69 debug!(" actual = {}", stringify_actual_node(&actual));
79 debug!(" expected = {:#?}", expected);
70 debug!(" expected = {:#?}", expected);
80− match expected.kind {
71+ match expected {
81− NodeKind::Text(TextNode { text }) => {
72+ Node::Text(text_node) => {
82 assert_eq!(
73 assert_eq!(
83 actual.node_name().to_uppercase(),
74 actual.node_name().to_uppercase(),
84 "#TEXT",
75 "#TEXT",
@@ -86,34 +77,21 @@ pub fn assert_rendered<R: for<'a> Render<'a>>(container: &web_sys::Element, r: &
86 );
77 );
87 assert_eq!(
78 assert_eq!(
88 actual.text_content().unwrap_or_default(),
79 actual.text_content().unwrap_or_default(),
89− text,
80+ text_node.text(),
90 "actual.text_content() == expected.text()"
81 "actual.text_content() == expected.text()"
91 );
82 );
92 }
83 }
93− NodeKind::Element(&ElementNode {
84+ Node::Element(elem) => {
94− tag_name,
95− attributes,
96− children,
97− namespace,
98− ..
99− }) => {
100 assert_eq!(
85 assert_eq!(
101 actual.node_name().to_uppercase(),
86 actual.node_name().to_uppercase(),
102− tag_name.to_uppercase(),
87+ elem.tag_name().to_uppercase(),
103 "actual.node_name() == expected.tag_name()"
88 "actual.node_name() == expected.tag_name()"
104 );
89 );
105 let actual = actual
90 let actual = actual
106 .dyn_ref::<web_sys::Element>()
91 .dyn_ref::<web_sys::Element>()
107 .expect("`actual` should be an `Element`");
92 .expect("`actual` should be an `Element`");
108− check_attributes(actual.attributes(), attributes);
93+ check_attributes(actual.attributes(), elem.attributes());
109− check_children(cached_set, actual.child_nodes(), children);
94+ check_children(actual.child_nodes(), elem.children());
110− if let Some(namespace) = namespace {
111− assert_eq!(actual.namespace_uri(), Some(namespace.into()))
112− }
113− }
114− NodeKind::Cached(ref c) => {
115− let (expected, _template) = cached_set.get(c.id);
116− check_node(cached_set, actual, &expected);
117 }
95 }
118 }
96 }
119 }
97 }
@@ -137,7 +115,7 @@ pub fn assert_rendered<R: for<'a> Render<'a>>(container: &web_sys::Element, r: &
137 }
115 }
138 }
116 }
139
117
140− fn check_children(cached_set: &CachedSet, actual: web_sys::NodeList, expected: &[Node]) {
118+ fn check_children(actual: web_sys::NodeList, expected: &[Node]) {
141 assert_eq!(
119 assert_eq!(
142 actual.length(),
120 actual.length(),
143 expected.len() as u32,
121 expected.len() as u32,
@@ -145,7 +123,7 @@ pub fn assert_rendered<R: for<'a> Render<'a>>(container: &web_sys::Element, r: &
145 );
123 );
146 for (i, child) in expected.iter().enumerate() {
124 for (i, child) in expected.iter().enumerate() {
147 let actual_child = actual.item(i as u32).unwrap();
125 let actual_child = actual.item(i as u32).unwrap();
148− check_node(cached_set, &actual_child, child);
126+ check_node(&actual_child, child);
149 }
127 }
150 }
128 }
151}
129}
@@ -153,24 +131,27 @@ pub fn assert_rendered<R: for<'a> Render<'a>>(container: &web_sys::Element, r: &
153/// Use the function `F` to render.
131/// Use the function `F` to render.
154pub struct RenderFn<F>(F)
132pub struct RenderFn<F>(F)
155where
133where
156− F: for<'a> Fn(&mut RenderContext<'a>) -> Node<'a>;
134+ F: for<'bump> Fn(&'bump Bump) -> Node<'bump>;
157
135
158−impl<'a, F> Render<'a> for RenderFn<F>
136+impl<F> Render for RenderFn<F>
159where
137where
160− F: for<'b> Fn(&mut RenderContext<'b>) -> Node<'b>,
138+ F: for<'bump> Fn(&'bump Bump) -> Node<'bump>,
161{
139{
162− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
140+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
141+ where
142+ 'a: 'bump,
143+ {
163− (self.0)(cx)
144+ (self.0)(bump)
164 }
145 }
165}
146}
166
147
167/// Assert that if we start by rendering the `before` virtual DOM tree into a
148/// Assert that if we start by rendering the `before` virtual DOM tree into a
168/// physical DOM tree, and then diff it with the `after` virtual DOM tree, then
149/// physical DOM tree, and then diff it with the `after` virtual DOM tree, then
169/// the physical DOM tree correctly matches `after`.
150/// the physical DOM tree correctly matches `after`.
170−pub async fn assert_before_after<R, S>(before: R, after: S) -> Result<(), JsValue>
151+pub fn assert_before_after<R, S>(before: R, after: S) -> impl Future<Item = (), Error = JsValue>
171where
152where
172− R: 'static + for<'a> Render<'a>,
153+ R: 'static + Render,
173− S: 'static + for<'a> Render<'a>,
154+ S: 'static + Render,
174{
155{
175 let container = create_element("div");
156 let container = create_element("div");
176
157
@@ -179,7 +160,7 @@ where
179
160
180 debug!("====== Rendering the *before* DOM into the physical DOM ======");
161 debug!("====== Rendering the *before* DOM into the physical DOM ======");
181 let vdom1 = Rc::new(Vdom::new(&container, before.clone()));
162 let vdom1 = Rc::new(Vdom::new(&container, before.clone()));
182− let _vdom2 = vdom1.clone();
163+ let vdom2 = vdom1.clone();
183
164
184 debug!("====== Checking the *before* DOM against the physical DOM ======");
165 debug!("====== Checking the *before* DOM against the physical DOM ======");
185 assert_rendered(&container, &before);
166 assert_rendered(&container, &before);
@@ -187,16 +168,15 @@ where
187 debug!("====== Rendering the *after* DOM into the physical DOM ======");
168 debug!("====== Rendering the *after* DOM into the physical DOM ======");
188 let weak = vdom1.weak();
169 let weak = vdom1.weak();
189 weak.set_component(Box::new(after.clone()))
170 weak.set_component(Box::new(after.clone()))
190− .await
171+ .map(move |_| {
191− .map_err(|e| e.to_string())?;
192−
193− debug!("====== Checking the *after* DOM against the physical DOM ======");
172+ debug!("====== Checking the *after* DOM against the physical DOM ======");
194− assert_rendered(&container, &after);
173+ assert_rendered(&container, &after);
195−
174+ drop(vdom2);
196− Ok(())
175+ })
176+ .map_err(|e| JsValue::from(e.to_string()))
197}
177}
198
178
199−/// A helper macro for declaring a bunch of `assert_before_after` tests.
179+/// A helper macro for decalaring a bunch of `assert_before_after` tests.
200#[macro_export]
180#[macro_export]
201macro_rules! before_after {
181macro_rules! before_after {
202 ( $(
182 ( $(
@@ -210,16 +190,13 @@ macro_rules! before_after {
210 }
190 }
211 )* ) => {
191 )* ) => {
212 $(
192 $(
213− #[wasm_bindgen_test]
193+ #[wasm_bindgen_test(async)]
214− async fn $name() {
194+ fn $name() -> impl Future<Item = (), Error = wasm_bindgen::JsValue> {
215 use crate::{assert_before_after, RenderFn};
195 use crate::{assert_before_after, RenderFn};
216− log::debug!("############### {} ###############", stringify!($name));
217 assert_before_after(
196 assert_before_after(
218 RenderFn(|$before_bump| { $( $before )* }),
197 RenderFn(|$before_bump| { $( $before )* }),
219 RenderFn(|$after_bump| { $( $after )* })
198 RenderFn(|$after_bump| { $( $after )* })
220 )
199 )
221− .await
222− .unwrap()
223 }
200 }
224 )*
201 )*
225 }
202 }
tests/web/render.rs+83−183
@@ -1,12 +1,12 @@
1use super::{assert_rendered, before_after, create_element, RenderFn};
1use super::{assert_rendered, before_after, create_element, RenderFn};
2−use dodrio::{builder::*, bumpalo::collections::String, Node, Render, RenderContext, Vdom};
2+use dodrio::{builder::*, Vdom};
3+use futures::prelude::*;
3use std::rc::Rc;
4use std::rc::Rc;
4−use wasm_bindgen::{ JsCast};
5use wasm_bindgen_test::*;
5use wasm_bindgen_test::*;
6
6
7#[wasm_bindgen_test]
7#[wasm_bindgen_test]
8fn render_initial_text() {
8fn render_initial_text() {
9− let hello = Rc::new(RenderFn(|_cx| text("hello")));
9+ let hello = Rc::new(RenderFn(|_bump| text("hello")));
10
10
11 let container = create_element("div");
11 let container = create_element("div");
12 let _vdom = Vdom::new(&container, hello.clone());
12 let _vdom = Vdom::new(&container, hello.clone());
@@ -15,10 +15,10 @@ fn render_initial_text() {
15
15
16#[wasm_bindgen_test]
16#[wasm_bindgen_test]
17fn render_initial_node() {
17fn render_initial_node() {
18− let hello = Rc::new(RenderFn(|cx| {
18+ let hello = Rc::new(RenderFn(|bump| {
19− div(&cx)
19+ div(bump)
20 .attr("id", "hello-world")
20 .attr("id", "hello-world")
21− .children([text("Hello "), span(&cx).child(text("World!")).finish()])
21+ .children([text("Hello "), span(bump).child(text("World!")).finish()])
22 .finish()
22 .finish()
23 }));
23 }));
24
24
@@ -30,183 +30,96 @@ fn render_initial_node() {
30#[wasm_bindgen_test]
30#[wasm_bindgen_test]
31fn container_is_emptied_upon_drop() {
31fn container_is_emptied_upon_drop() {
32 let container = create_element("div");
32 let container = create_element("div");
33− let vdom = Vdom::new(&container, RenderFn(|_cx| text("blah")));
33+ let vdom = Vdom::new(&container, RenderFn(|_bump| text("blah")));
34 drop(vdom);
34 drop(vdom);
35 assert!(container.first_child().is_none());
35 assert!(container.first_child().is_none());
36}
36}
37
37
38−/// Renders a child with a lifetime scoped to the RenderContext bump arena.
39−#[wasm_bindgen_test]
40−fn render_bump_scoped_node() {
41− struct Child<'a> {
42− name: &'a str,
43− }
44−
45− impl<'a> Render<'a> for Child<'a> {
46− fn render(&self, _cx: &mut RenderContext<'a>) -> Node<'a> {
47− text(self.name)
48− }
49− }
50−
51− struct Parent;
52−
53− impl<'a> Render<'a> for Parent {
54− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
55− let child_name = String::from_str_in("child", cx.bump).into_bump_str();
56−
57− div(&cx)
58− .children([Child { name: child_name }.render(cx)])
59− .finish()
60− }
61− }
62−
63− let parent = Rc::new(RenderFn(|cx| {
64− Parent.render(cx)
65− }));
66−
67− let container = create_element("div");
68− let _vdom = Vdom::new(&container, parent.clone());
69−
70− assert_rendered(&container, &parent);
71−}
72−
73−/// Originally, dodrio would use the className property for SVGs.
74−///
75−/// This is problematic because when SVG elements are created, the className is flagged as a read
76−/// only property, so setting it causes an exception to be thrown. Here's an example of how this
77−/// happens:
78−///
79−/// let elem = web_sys::window()
80−/// .unwrap()
81−/// .document()
82−/// .unwrap()
83−/// .create_element_ns(Some("http://www.w3.org/2000/svg"), "svg")
84−/// .unwrap();
85−///
86−/// elem.set_class_name("does-not-work");
87−///
88−/// -----------------------------------------------------------------------------------------------
89−///
90−/// wasm-bindgen: imported JS function that was not marked as `catch` threw an error:
91−/// setting getter-only property "className"
92−///
93−/// -----------------------------------------------------------------------------------------------
94−///
95−/// Now, dodrio passes the 'class' attribute of all namespaced elements into set_attribute. This
96−/// satisfies the restrictions on SVG and keeps the optimized path for non-namespaced elements
97−#[wasm_bindgen_test(async)]
98−async fn test_svg_set_class() {
99− let container = create_element("div");
100−
101−
102− let valid_svg = Rc::new(RenderFn(|cx| {
103− ElementBuilder::new(cx.bump, "svg")
104− .namespace(Some("http://www.w3.org/2000/svg"))
105− .attr("class", "works")
106− .finish()
107− }));
108−
109− let vdom = Vdom::new(&container, valid_svg.clone());
110− let weak = vdom.weak();
111−
112− weak.render().await.unwrap();
113−
114− assert_eq!(
115− "works",
116− container.first_child()
117− .expect("unable to get svg")
118− .dyn_ref::<web_sys::Element>()
119− .expect("svg should be an element")
120− .get_attribute("class")
121− .expect("unable to get 'class' of svg")
122− );
123−}
124−
125before_after! {
38before_after! {
126 same_text {
39 same_text {
127− before(_cx) {
40+ before(_bump) {
128 text("hello")
41 text("hello")
129 }
42 }
130− after(_cx) {
43+ after(_bump) {
131 text("hello")
44 text("hello")
132 }
45 }
133 }
46 }
134
47
135 update_text {
48 update_text {
136− before(_cx) {
49+ before(_bump) {
137 text("before")
50 text("before")
138 }
51 }
139− after(_cx) {
52+ after(_bump) {
140 text("after")
53 text("after")
141 }
54 }
142 }
55 }
143
56
144 replace_text_with_elem {
57 replace_text_with_elem {
145− before(_cx) {
58+ before(_bump) {
146 text("before")
59 text("before")
147 }
60 }
148− after(cx) {
61+ after(bump) {
149− div(&cx).finish()
62+ div(bump).finish()
150 }
63 }
151 }
64 }
152
65
153 replace_elem_with_text {
66 replace_elem_with_text {
154− before(cx) {
67+ before(bump) {
155− div(&cx).finish()
68+ div(bump).finish()
156 }
69 }
157− after(_cx) {
70+ after(_bump) {
158 text("before")
71 text("before")
159 }
72 }
160 }
73 }
161
74
162 same_elem {
75 same_elem {
163− before(cx) {
76+ before(bump) {
164− div(&cx).finish()
77+ div(bump).finish()
165 }
78 }
166− after(cx) {
79+ after(bump) {
167− div(&cx).finish()
80+ div(bump).finish()
168 }
81 }
169 }
82 }
170
83
171 elems_with_different_tag_names {
84 elems_with_different_tag_names {
172− before(cx) {
85+ before(bump) {
173− span(&cx).finish()
86+ span(bump).finish()
174 }
87 }
175− after(cx) {
88+ after(bump) {
176− div(&cx).finish()
89+ div(bump).finish()
177 }
90 }
178 }
91 }
179
92
180 same_tag_name_update_attribute {
93 same_tag_name_update_attribute {
181− before(cx) {
94+ before(bump) {
182− div(&cx).attr("value", "1").finish()
95+ div(bump).attr("value", "1").finish()
183 }
96 }
184− after(cx) {
97+ after(bump) {
185− div(&cx).attr("value", "2").finish()
98+ div(bump).attr("value", "2").finish()
186 }
99 }
187 }
100 }
188
101
189 same_tag_name_remove_attribute {
102 same_tag_name_remove_attribute {
190− before(cx) {
103+ before(bump) {
191− div(&cx).attr("value", "1").finish()
104+ div(bump).attr("value", "1").finish()
192 }
105 }
193− after(cx) {
106+ after(bump) {
194− div(&cx).finish()
107+ div(bump).finish()
195 }
108 }
196 }
109 }
197
110
198 same_tag_name_add_attribute {
111 same_tag_name_add_attribute {
199− before(cx) {
112+ before(bump) {
200− div(&cx).finish()
113+ div(bump).finish()
201 }
114 }
202− after(cx) {
115+ after(bump) {
203− div(&cx).attr("value", "2").finish()
116+ div(bump).attr("value", "2").finish()
204 }
117 }
205 }
118 }
206
119
207 same_tag_name_many_attributes {
120 same_tag_name_many_attributes {
208− before(cx) {
121+ before(bump) {
209− div(&cx)
122+ div(bump)
210 .attr("before-1", "1")
123 .attr("before-1", "1")
211 .attr("shared-1", "1")
124 .attr("shared-1", "1")
212 .attr("modified-1", "1")
125 .attr("modified-1", "1")
@@ -218,8 +131,8 @@ before_after! {
218 .attr("modified-3", "3")
131 .attr("modified-3", "3")
219 .finish()
132 .finish()
220 }
133 }
221− after(cx) {
134+ after(bump) {
222− div(&cx)
135+ div(bump)
223 .attr("after-1", "1")
136 .attr("after-1", "1")
224 .attr("shared-1", "1")
137 .attr("shared-1", "1")
225 .attr("modified-1", "100")
138 .attr("modified-1", "100")
@@ -234,110 +147,97 @@ before_after! {
234 }
147 }
235
148
236 same_tag_same_children {
149 same_tag_same_children {
237− before(cx) {
150+ before(bump) {
238− div(&cx).child(text("child")).finish()
151+ div(bump).child(text("child")).finish()
239 }
152 }
240− after(cx) {
153+ after(bump) {
241− div(&cx).child(text("child")).finish()
154+ div(bump).child(text("child")).finish()
242 }
155 }
243 }
156 }
244
157
245 same_tag_update_child {
158 same_tag_update_child {
246− before(cx) {
159+ before(bump) {
247− div(&cx).child(text("before")).finish()
160+ div(bump).child(text("before")).finish()
248 }
161 }
249− after(cx) {
162+ after(bump) {
250− div(&cx).child(text("after")).finish()
163+ div(bump).child(text("after")).finish()
251 }
164 }
252 }
165 }
253
166
254 same_tag_add_child {
167 same_tag_add_child {
255− before(cx) {
168+ before(bump) {
256− div(&cx).finish()
169+ div(bump).finish()
257 }
170 }
258− after(cx) {
171+ after(bump) {
259− div(&cx).child(text("child")).finish()
172+ div(bump).child(text("child")).finish()
260 }
173 }
261 }
174 }
262
175
263 same_tag_remove_child {
176 same_tag_remove_child {
264− before(cx) {
177+ before(bump) {
265− div(&cx).child(text("child")).finish()
178+ div(bump).child(text("child")).finish()
266 }
179 }
267− after(cx) {
180+ after(bump) {
268− div(&cx).finish()
181+ div(bump).finish()
269 }
182 }
270 }
183 }
271
184
272 same_tag_update_many_children {
185 same_tag_update_many_children {
273− before(cx) {
186+ before(bump) {
274− div(&cx)
187+ div(bump)
275 .children([
188 .children([
276− div(&cx).finish(),
189+ div(bump).finish(),
277− span(&cx).finish(),
190+ span(bump).finish(),
278− p(&cx).finish(),
191+ p(bump).finish(),
279 ])
192 ])
280 .finish()
193 .finish()
281 }
194 }
282− after(cx) {
195+ after(bump) {
283− div(&cx)
196+ div(bump)
284 .children([
197 .children([
285− span(&cx).finish(),
198+ span(bump).finish(),
286− p(&cx).finish(),
199+ p(bump).finish(),
287− div(&cx).finish(),
200+ div(bump).finish(),
288 ])
201 ])
289 .finish()
202 .finish()
290 }
203 }
291 }
204 }
292
205
293 same_tag_remove_many_children {
206 same_tag_remove_many_children {
294− before(cx) {
207+ before(bump) {
295− div(&cx)
208+ div(bump)
296 .children([
209 .children([
297− div(&cx).finish(),
210+ div(bump).finish(),
298− span(&cx).finish(),
211+ span(bump).finish(),
299− p(&cx).finish(),
212+ p(bump).finish(),
300 ])
213 ])
301 .finish()
214 .finish()
302 }
215 }
303− after(cx) {
216+ after(bump) {
304− div(&cx)
217+ div(bump)
305 .children([
218 .children([
306− div(&cx).finish(),
219+ div(bump).finish(),
307 ])
220 ])
308 .finish()
221 .finish()
309 }
222 }
310 }
223 }
311
224
312 same_tag_add_many_children {
225 same_tag_add_many_children {
313− before(cx) {
226+ before(bump) {
314− div(&cx)
227+ div(bump)
315 .children([
228 .children([
316− div(&cx).finish(),
229+ div(bump).finish(),
317 ])
230 ])
318 .finish()
231 .finish()
319 }
232 }
320− after(cx) {
233+ after(bump) {
321− div(&cx)
234+ div(bump)
322 .children([
235 .children([
323− div(&cx).finish(),
236+ div(bump).finish(),
324− span(&cx).finish(),
237+ span(bump).finish(),
325− p(&cx).finish(),
238+ p(bump).finish(),
326 ])
239 ])
327 .finish()
240 .finish()
328 }
241 }
329 }
242 }
330−
331− same_tag_different_namespace {
332− before(cx) {
333− div(&cx)
334− .namespace(Some("http://example.com"))
335− .finish()
336− }
337− after(cx) {
338− div(&cx)
339− .namespace(Some("http://example.net"))
340− .finish()
341− }
342− }
343}
243}