on 851e50b 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+330−642
@@ -1,204 +1,102 @@
1−# This file is automatically @generated by Cargo.
2−# It is not intended for manual editing.
3[[package]]
1[[package]]
4−name = "anyhow"
2+name = "aho-corasick"
5−version = "1.0.25"
3+version = "0.6.10"
6source = "registry+https://github.com/rust-lang/crates.io-index"
4source = "registry+https://github.com/rust-lang/crates.io-index"
5+dependencies = [
6+ "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
7+]
7
8
8[[package]]
9[[package]]
9name = "atty"
10name = "atty"
10−version = "0.2.13"
11+version = "0.2.11"
11source = "registry+https://github.com/rust-lang/crates.io-index"
12source = "registry+https://github.com/rust-lang/crates.io-index"
12dependencies = [
13dependencies = [
14+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
13− "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
15+ "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)",
16+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
15]
17]
16
18
17[[package]]
19[[package]]
18name = "autocfg"
20name = "autocfg"
19−version = "0.1.7"
21+version = "0.1.2"
20source = "registry+https://github.com/rust-lang/crates.io-index"
22source = "registry+https://github.com/rust-lang/crates.io-index"
21
23
22[[package]]
24[[package]]
23−name = "bitflags"
25+name = "backtrace"
24−version = "1.2.1"
26+version = "0.3.14"
25source = "registry+https://github.com/rust-lang/crates.io-index"
27source = "registry+https://github.com/rust-lang/crates.io-index"
28+dependencies = [
29+ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
30+ "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
31+ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
32+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
33+ "rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
34+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
35+]
26
36
27[[package]]
37[[package]]
28−name = "bstr"
38+name = "backtrace-sys"
29−version = "0.2.8"
39+version = "0.1.28"
30source = "registry+https://github.com/rust-lang/crates.io-index"
40source = "registry+https://github.com/rust-lang/crates.io-index"
31dependencies = [
41dependencies = [
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)",
42+ "cc 1.0.31 (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)",
43+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
36]
44]
37
45
38[[package]]
46[[package]]
39name = "bumpalo"
47name = "bumpalo"
40−version = "2.6.0"
48+version = "2.2.2"
41source = "registry+https://github.com/rust-lang/crates.io-index"
49source = "registry+https://github.com/rust-lang/crates.io-index"
42
50
43[[package]]
51[[package]]
44name = "byteorder"
52name = "byteorder"
45−version = "1.3.2"
53+version = "1.3.1"
46source = "registry+https://github.com/rust-lang/crates.io-index"
54source = "registry+https://github.com/rust-lang/crates.io-index"
47
55
48[[package]]
56[[package]]
49−name = "cast"
57+name = "cc"
50−version = "0.2.3"
58+version = "1.0.31"
51source = "registry+https://github.com/rust-lang/crates.io-index"
59source = "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
60
56[[package]]
61[[package]]
57name = "cfg-if"
62name = "cfg-if"
58−version = "0.1.10"
63+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"
64source = "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
65
71[[package]]
66[[package]]
72name = "console_error_panic_hook"
67name = "console_error_panic_hook"
73version = "0.1.6"
68version = "0.1.6"
74source = "registry+https://github.com/rust-lang/crates.io-index"
69source = "registry+https://github.com/rust-lang/crates.io-index"
75dependencies = [
70dependencies = [
76− "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
71+ "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)",
72+ "wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
78]
73]
79
74
80[[package]]
75[[package]]
81name = "console_log"
76name = "console_log"
82−version = "0.2.0"
77+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"
78source = "registry+https://github.com/rust-lang/crates.io-index"
179dependencies = [
79dependencies = [
80+ "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)",
81+ "web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
181]
82]
182
83
183[[package]]
84[[package]]
184name = "dodrio"
85name = "dodrio"
185−version = "0.2.0"
86+version = "0.1.0"
186dependencies = [
87dependencies = [
187− "bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
88+ "bumpalo 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
188− "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
89+ "cfg-if 0.1.7 (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)",
90+ "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
191− "dodrio-js-api 0.2.0",
91+ "dodrio-js-api 0.1.0",
192− "futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
92+ "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
193 "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
93 "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)",
94+ "js-sys 0.3.16 (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)",
95+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
198− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
96+ "wasm-bindgen 0.2.39 (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)",
97+ "wasm-bindgen-futures 0.3.16 (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)",
98+ "wasm-bindgen-test 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
201− "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
99+ "web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
202]
100]
203
101
204[[package]]
102[[package]]
@@ -206,12 +104,12 @@ name = "dodrio-counter"
206version = "0.1.0"
104version = "0.1.0"
207dependencies = [
105dependencies = [
208 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
106 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
209− "console_log 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
107+ "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
210− "dodrio 0.2.0",
108+ "dodrio 0.1.0",
211− "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
109+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
212− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
110+ "wasm-bindgen 0.2.39 (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)",
111+ "wasm-bindgen-test 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
214− "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
112+ "web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
215]
113]
216
114
217[[package]]
115[[package]]
@@ -219,12 +117,13 @@ name = "dodrio-game-of-life"
219version = "0.1.0"
117version = "0.1.0"
220dependencies = [
118dependencies = [
221 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
119 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
222− "dodrio 0.2.0",
120+ "dodrio 0.1.0",
121+ "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
223− "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
122+ "js-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
224− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
123+ "wasm-bindgen 0.2.39 (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)",
124+ "wasm-bindgen-futures 0.3.16 (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)",
125+ "wasm-bindgen-test 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
227− "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
126+ "web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
228]
127]
229
128
230[[package]]
129[[package]]
@@ -232,10 +131,10 @@ name = "dodrio-hello-world"
232version = "0.1.0"
131version = "0.1.0"
233dependencies = [
132dependencies = [
234 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
133 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
235− "dodrio 0.2.0",
134+ "dodrio 0.1.0",
236− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
135+ "wasm-bindgen 0.2.39 (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)",
136+ "wasm-bindgen-test 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
238− "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
137+ "web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
239]
138]
240
139
241[[package]]
140[[package]]
@@ -243,21 +142,22 @@ name = "dodrio-input-form"
243version = "0.1.0"
142version = "0.1.0"
244dependencies = [
143dependencies = [
245 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
144 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
246− "dodrio 0.2.0",
145+ "dodrio 0.1.0",
247− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
146+ "wasm-bindgen 0.2.39 (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)",
147+ "wasm-bindgen-test 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
249− "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
148+ "web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
250]
149]
251
150
252[[package]]
151[[package]]
253name = "dodrio-js-api"
152name = "dodrio-js-api"
254−version = "0.2.0"
153+version = "0.1.0"
255dependencies = [
154dependencies = [
256− "dodrio 0.2.0",
155+ "dodrio 0.1.0",
156+ "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)",
157+ "js-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
258− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
158+ "wasm-bindgen 0.2.39 (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)",
159+ "wasm-bindgen-futures 0.3.16 (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)",
160+ "wasm-bindgen-test 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
261]
161]
262
162
263[[package]]
163[[package]]
@@ -265,27 +165,28 @@ name = "dodrio-js-component"
265version = "0.1.0"
165version = "0.1.0"
266dependencies = [
166dependencies = [
267 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
167 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
268− "dodrio 0.2.0",
168+ "dodrio 0.1.0",
269− "dodrio-js-api 0.2.0",
169+ "dodrio-js-api 0.1.0",
270− "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
170+ "js-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
271− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
171+ "wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
272− "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
172+ "web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
273]
173]
274
174
275[[package]]
175[[package]]
276name = "dodrio-moire"
176name = "dodrio-moire"
277version = "0.1.0"
177version = "0.1.0"
278dependencies = [
178dependencies = [
279− "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
179+ "cfg-if 0.1.7 (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)",
180 "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)",
181+ "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
282− "dodrio 0.2.0",
182+ "dodrio 0.1.0",
183+ "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
283− "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
184+ "js-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
284− "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
185+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
285− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
186+ "wasm-bindgen 0.2.39 (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)",
187+ "wasm-bindgen-futures 0.3.16 (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)",
188+ "wasm-bindgen-test 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
288− "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
189+ "web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
289]
190]
290
191
291[[package]]
192[[package]]
@@ -293,376 +194,203 @@ name = "dodrio-sierpinski"
293version = "0.1.0"
194version = "0.1.0"
294dependencies = [
195dependencies = [
295 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
196 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
296− "dodrio 0.2.0",
197+ "dodrio 0.1.0",
198+ "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)",
199+ "js-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
298− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
200+ "wasm-bindgen 0.2.39 (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)",
201+ "wasm-bindgen-futures 0.3.16 (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)",
202+ "wasm-bindgen-test 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
301− "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
203+ "web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
302]
204]
303
205
304[[package]]
206[[package]]
305name = "dodrio-todomvc"
207name = "dodrio-todomvc"
306version = "0.1.0"
208version = "0.1.0"
307dependencies = [
209dependencies = [
308− "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
210+ "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)",
211 "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)",
212+ "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
311− "dodrio 0.2.0",
213+ "dodrio 0.1.0",
214+ "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)",
215+ "js-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
313− "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
216+ "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)",
217+ "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)",
218+ "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)",
219+ "wasm-bindgen 0.2.39 (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)",
220+ "wasm-bindgen-futures 0.3.16 (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)",
221+ "wasm-bindgen-test 0.2.39 (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)",
222+ "web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
339]
223]
340
224
341[[package]]
225[[package]]
342−name = "futures-channel"
226+name = "env_logger"
343−version = "0.3.5"
227+version = "0.6.1"
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)",
348−]
349−
350−[[package]]
351−name = "futures-core"
352−version = "0.3.5"
353−source = "registry+https://github.com/rust-lang/crates.io-index"
354−
355−[[package]]
356−name = "futures-executor"
357−version = "0.3.5"
358source = "registry+https://github.com/rust-lang/crates.io-index"
228source = "registry+https://github.com/rust-lang/crates.io-index"
359dependencies = [
229dependencies = [
360− "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
230+ "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
231+ "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
361− "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
232+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
233+ "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
362− "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
234+ "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
363]
235]
364
236
365[[package]]
237[[package]]
366−name = "futures-io"
238+name = "failure"
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"
239+version = "0.1.5"
373source = "registry+https://github.com/rust-lang/crates.io-index"
240source = "registry+https://github.com/rust-lang/crates.io-index"
374dependencies = [
241dependencies = [
375− "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
242+ "backtrace 0.3.14 (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)",
243+ "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
379]
244]
380
245
381[[package]]
246[[package]]
382−name = "futures-sink"
247+name = "failure_derive"
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"
248+version = "0.1.5"
389source = "registry+https://github.com/rust-lang/crates.io-index"
249source = "registry+https://github.com/rust-lang/crates.io-index"
390dependencies = [
250dependencies = [
251+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
252+ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
253+ "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
391− "once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
254+ "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
392]
255]
393
256
394[[package]]
257[[package]]
395−name = "futures-util"
258+name = "futures"
396−version = "0.3.5"
259+version = "0.1.25"
397source = "registry+https://github.com/rust-lang/crates.io-index"
260source = "registry+https://github.com/rust-lang/crates.io-index"
398−dependencies = [
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)",
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)",
411−]
412
261
413[[package]]
262[[package]]
414name = "fxhash"
263name = "fxhash"
415version = "0.2.1"
264version = "0.2.1"
416source = "registry+https://github.com/rust-lang/crates.io-index"
265source = "registry+https://github.com/rust-lang/crates.io-index"
417dependencies = [
266dependencies = [
418− "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
267+ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
419]
268]
420
269
421−[[package]]
422−name = "half"
423−version = "1.6.0"
424−source = "registry+https://github.com/rust-lang/crates.io-index"
425−
426[[package]]
270[[package]]
427name = "heck"
271name = "heck"
428version = "0.3.1"
272version = "0.3.1"
429source = "registry+https://github.com/rust-lang/crates.io-index"
273source = "registry+https://github.com/rust-lang/crates.io-index"
430dependencies = [
274dependencies = [
431− "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
275+ "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
432]
276]
433
277
434[[package]]
278[[package]]
435−name = "hermit-abi"
279+name = "humantime"
436−version = "0.1.3"
280+version = "1.2.0"
437source = "registry+https://github.com/rust-lang/crates.io-index"
281source = "registry+https://github.com/rust-lang/crates.io-index"
438dependencies = [
282dependencies = [
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)",
283+ "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
448]
284]
449
285
450[[package]]
286[[package]]
451name = "itoa"
287name = "itoa"
452−version = "0.4.4"
288+version = "0.4.3"
453source = "registry+https://github.com/rust-lang/crates.io-index"
289source = "registry+https://github.com/rust-lang/crates.io-index"
454
290
455[[package]]
291[[package]]
456name = "js-sys"
292name = "js-sys"
457−version = "0.3.32"
293+version = "0.3.16"
458source = "registry+https://github.com/rust-lang/crates.io-index"
294source = "registry+https://github.com/rust-lang/crates.io-index"
459dependencies = [
295dependencies = [
460− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
296+ "wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
461]
297]
462
298
463[[package]]
299[[package]]
464name = "lazy_static"
300name = "lazy_static"
465−version = "1.4.0"
301+version = "1.3.0"
466source = "registry+https://github.com/rust-lang/crates.io-index"
302source = "registry+https://github.com/rust-lang/crates.io-index"
467
303
468[[package]]
304[[package]]
469name = "libc"
305name = "libc"
470−version = "0.2.66"
306+version = "0.2.50"
471source = "registry+https://github.com/rust-lang/crates.io-index"
307source = "registry+https://github.com/rust-lang/crates.io-index"
472
308
473[[package]]
309[[package]]
474name = "log"
310name = "log"
475−version = "0.4.11"
311+version = "0.4.6"
476source = "registry+https://github.com/rust-lang/crates.io-index"
312source = "registry+https://github.com/rust-lang/crates.io-index"
477dependencies = [
313dependencies = [
478− "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
314+ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
479]
315]
480
316
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]]
317[[package]]
487name = "memchr"
318name = "memchr"
488−version = "2.2.1"
319+version = "2.2.0"
489source = "registry+https://github.com/rust-lang/crates.io-index"
320source = "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
321
502[[package]]
322[[package]]
503name = "nom"
323name = "nom"
504−version = "4.2.3"
324+version = "4.2.2"
505source = "registry+https://github.com/rust-lang/crates.io-index"
325source = "registry+https://github.com/rust-lang/crates.io-index"
506dependencies = [
326dependencies = [
507− "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
327+ "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)",
328 "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
509]
329]
510
330
511[[package]]
331[[package]]
512−name = "num-traits"
332+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"
333+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"
334source = "registry+https://github.com/rust-lang/crates.io-index"
576dependencies = [
335dependencies = [
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)",
336+ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
580]
337]
581
338
582[[package]]
339[[package]]
583−name = "proc-macro-nested"
340+name = "quick-error"
584−version = "0.1.3"
341+version = "1.2.2"
585source = "registry+https://github.com/rust-lang/crates.io-index"
342source = "registry+https://github.com/rust-lang/crates.io-index"
586
343
587−[[package]]
588−name = "proc-macro2"
589−version = "1.0.18"
590−source = "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−
595[[package]]
344[[package]]
596name = "quote"
345name = "quote"
597−version = "1.0.2"
346+version = "0.6.11"
598source = "registry+https://github.com/rust-lang/crates.io-index"
347source = "registry+https://github.com/rust-lang/crates.io-index"
599dependencies = [
348dependencies = [
600− "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
349+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
601]
350]
602
351
603[[package]]
352[[package]]
604−name = "rayon"
353+name = "redox_syscall"
605−version = "1.3.0"
354+version = "0.1.51"
606source = "registry+https://github.com/rust-lang/crates.io-index"
355source = "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
356
613[[package]]
357[[package]]
614−name = "rayon-core"
358+name = "redox_termios"
615−version = "1.7.0"
359+version = "0.1.1"
616source = "registry+https://github.com/rust-lang/crates.io-index"
360source = "registry+https://github.com/rust-lang/crates.io-index"
617dependencies = [
361dependencies = [
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)",
362+ "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
623]
363]
624
364
625[[package]]
365[[package]]
626name = "regex"
366name = "regex"
627−version = "1.3.3"
367+version = "1.1.2"
628source = "registry+https://github.com/rust-lang/crates.io-index"
368source = "registry+https://github.com/rust-lang/crates.io-index"
629dependencies = [
369dependencies = [
370+ "aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
371+ "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
630− "regex-syntax 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
372+ "regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
631−]
373+ "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
632−
633−[[package]]
634−name = "regex-automata"
635−version = "0.1.8"
636−source = "registry+https://github.com/rust-lang/crates.io-index"
637−dependencies = [
638− "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
374+ "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
639]
375]
640
376
641[[package]]
377[[package]]
642name = "regex-syntax"
378name = "regex-syntax"
643−version = "0.6.13"
379+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"
380source = "registry+https://github.com/rust-lang/crates.io-index"
650dependencies = [
381dependencies = [
651− "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
382+ "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
652]
383]
653
384
654[[package]]
385[[package]]
655−name = "ryu"
386+name = "rustc-demangle"
656−version = "1.0.2"
387+version = "0.1.13"
657source = "registry+https://github.com/rust-lang/crates.io-index"
388source = "registry+https://github.com/rust-lang/crates.io-index"
658
389
659[[package]]
390[[package]]
660−name = "same-file"
391+name = "ryu"
661−version = "1.0.5"
392+version = "0.2.7"
662source = "registry+https://github.com/rust-lang/crates.io-index"
393source = "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
394
667[[package]]
395[[package]]
668name = "scoped-tls"
396name = "scoped-tls"
@@ -670,247 +398,230 @@ version = "1.0.0"
670source = "registry+https://github.com/rust-lang/crates.io-index"
398source = "registry+https://github.com/rust-lang/crates.io-index"
671
399
672[[package]]
400[[package]]
673−name = "scopeguard"
401+name = "serde"
674−version = "1.0.0"
402+version = "1.0.89"
675−source = "registry+https://github.com/rust-lang/crates.io-index"
676−
677−[[package]]
678−name = "semver"
679−version = "0.9.0"
680source = "registry+https://github.com/rust-lang/crates.io-index"
403source = "registry+https://github.com/rust-lang/crates.io-index"
681dependencies = [
404dependencies = [
682− "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
405+ "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
683]
406]
684
407
685[[package]]
408[[package]]
686−name = "semver-parser"
409+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"
410+version = "1.0.89"
693source = "registry+https://github.com/rust-lang/crates.io-index"
411source = "registry+https://github.com/rust-lang/crates.io-index"
694dependencies = [
412dependencies = [
413+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
414+ "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)",
415+ "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
696]
416]
697
417
698[[package]]
418[[package]]
699−name = "serde_cbor"
419+name = "serde_json"
700−version = "0.11.1"
420+version = "1.0.39"
701source = "registry+https://github.com/rust-lang/crates.io-index"
421source = "registry+https://github.com/rust-lang/crates.io-index"
702dependencies = [
422dependencies = [
423+ "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)",
424+ "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)",
425+ "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
705]
426]
706
427
707[[package]]
428[[package]]
708−name = "serde_derive"
429+name = "sourcefile"
709−version = "1.0.114"
430+version = "0.1.4"
710source = "registry+https://github.com/rust-lang/crates.io-index"
431source = "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
432
717[[package]]
433[[package]]
718−name = "serde_json"
434+name = "syn"
719−version = "1.0.57"
435+version = "0.15.29"
720source = "registry+https://github.com/rust-lang/crates.io-index"
436source = "registry+https://github.com/rust-lang/crates.io-index"
721dependencies = [
437dependencies = [
722− "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
438+ "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)",
439+ "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)",
440+ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
725]
441]
726
442
727[[package]]
443[[package]]
728−name = "slab"
444+name = "synstructure"
729−version = "0.4.2"
445+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"
446source = "registry+https://github.com/rust-lang/crates.io-index"
447+dependencies = [
448+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
449+ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
450+ "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
451+ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
452+]
736
453
737[[package]]
454[[package]]
738−name = "syn"
455+name = "termcolor"
739−version = "1.0.33"
456+version = "1.0.4"
740source = "registry+https://github.com/rust-lang/crates.io-index"
457source = "registry+https://github.com/rust-lang/crates.io-index"
741dependencies = [
458dependencies = [
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)",
459+ "wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
745]
460]
746
461
747[[package]]
462[[package]]
748−name = "textwrap"
463+name = "termion"
749−version = "0.11.0"
464+version = "1.5.1"
750source = "registry+https://github.com/rust-lang/crates.io-index"
465source = "registry+https://github.com/rust-lang/crates.io-index"
751dependencies = [
466dependencies = [
467+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
468+ "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)",
469+ "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
753]
470]
754
471
755[[package]]
472[[package]]
756−name = "tinytemplate"
473+name = "thread_local"
757−version = "1.1.0"
474+version = "0.3.6"
758source = "registry+https://github.com/rust-lang/crates.io-index"
475source = "registry+https://github.com/rust-lang/crates.io-index"
759dependencies = [
476dependencies = [
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)",
477+ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
762]
478]
763
479
764[[package]]
480[[package]]
765−name = "unicode-segmentation"
481+name = "ucd-util"
766−version = "1.6.0"
482+version = "0.1.3"
767source = "registry+https://github.com/rust-lang/crates.io-index"
483source = "registry+https://github.com/rust-lang/crates.io-index"
768
484
769[[package]]
485[[package]]
770−name = "unicode-width"
486+name = "unicode-segmentation"
771−version = "0.1.6"
487+version = "1.2.1"
772source = "registry+https://github.com/rust-lang/crates.io-index"
488source = "registry+https://github.com/rust-lang/crates.io-index"
773
489
774[[package]]
490[[package]]
775name = "unicode-xid"
491name = "unicode-xid"
776−version = "0.2.0"
492+version = "0.1.0"
777source = "registry+https://github.com/rust-lang/crates.io-index"
493source = "registry+https://github.com/rust-lang/crates.io-index"
778
494
779[[package]]
495[[package]]
780−name = "version_check"
496+name = "utf8-ranges"
781−version = "0.1.5"
497+version = "1.0.2"
782source = "registry+https://github.com/rust-lang/crates.io-index"
498source = "registry+https://github.com/rust-lang/crates.io-index"
783
499
784[[package]]
500[[package]]
785−name = "walkdir"
501+name = "version_check"
786−version = "2.3.1"
502+version = "0.1.5"
787source = "registry+https://github.com/rust-lang/crates.io-index"
503source = "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
504
794[[package]]
505[[package]]
795name = "wasm-bindgen"
506name = "wasm-bindgen"
796−version = "0.2.55"
507+version = "0.2.39"
797source = "registry+https://github.com/rust-lang/crates.io-index"
508source = "registry+https://github.com/rust-lang/crates.io-index"
798dependencies = [
509dependencies = [
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)",
510+ "wasm-bindgen-macro 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
801]
511]
802
512
803[[package]]
513[[package]]
804name = "wasm-bindgen-backend"
514name = "wasm-bindgen-backend"
805−version = "0.2.55"
515+version = "0.2.39"
806source = "registry+https://github.com/rust-lang/crates.io-index"
516source = "registry+https://github.com/rust-lang/crates.io-index"
807dependencies = [
517dependencies = [
808− "bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
518+ "bumpalo 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
809− "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
519+ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
810− "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
520+ "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)",
521+ "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)",
522+ "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)",
523+ "syn 0.15.29 (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)",
524+ "wasm-bindgen-shared 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
815]
525]
816
526
817[[package]]
527[[package]]
818name = "wasm-bindgen-futures"
528name = "wasm-bindgen-futures"
819−version = "0.4.5"
529+version = "0.3.16"
820source = "registry+https://github.com/rust-lang/crates.io-index"
530source = "registry+https://github.com/rust-lang/crates.io-index"
821dependencies = [
531dependencies = [
822− "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
532+ "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)",
533+ "js-sys 0.3.16 (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)",
534+ "wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
826]
535]
827
536
828[[package]]
537[[package]]
829name = "wasm-bindgen-macro"
538name = "wasm-bindgen-macro"
830−version = "0.2.55"
539+version = "0.2.39"
831source = "registry+https://github.com/rust-lang/crates.io-index"
540source = "registry+https://github.com/rust-lang/crates.io-index"
832dependencies = [
541dependencies = [
833− "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)",
834− "wasm-bindgen-macro-support 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
543+ "wasm-bindgen-macro-support 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
835]
544]
836
545
837[[package]]
546[[package]]
838name = "wasm-bindgen-macro-support"
547name = "wasm-bindgen-macro-support"
839−version = "0.2.55"
548+version = "0.2.39"
840source = "registry+https://github.com/rust-lang/crates.io-index"
549source = "registry+https://github.com/rust-lang/crates.io-index"
841dependencies = [
550dependencies = [
842− "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
551+ "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)",
552+ "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)",
553+ "syn 0.15.29 (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)",
554+ "wasm-bindgen-backend 0.2.39 (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)",
555+ "wasm-bindgen-shared 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
847]
556]
848
557
849[[package]]
558[[package]]
850name = "wasm-bindgen-shared"
559name = "wasm-bindgen-shared"
851−version = "0.2.55"
560+version = "0.2.39"
852source = "registry+https://github.com/rust-lang/crates.io-index"
561source = "registry+https://github.com/rust-lang/crates.io-index"
853
562
854[[package]]
563[[package]]
855name = "wasm-bindgen-test"
564name = "wasm-bindgen-test"
856−version = "0.3.5"
565+version = "0.2.39"
857source = "registry+https://github.com/rust-lang/crates.io-index"
566source = "registry+https://github.com/rust-lang/crates.io-index"
858dependencies = [
567dependencies = [
859 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
568 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
569+ "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)",
570+ "js-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
861 "scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
571 "scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
862− "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
572+ "wasm-bindgen 0.2.39 (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)",
573+ "wasm-bindgen-futures 0.3.16 (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)",
574+ "wasm-bindgen-test-macro 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
865]
575]
866
576
867[[package]]
577[[package]]
868name = "wasm-bindgen-test-macro"
578name = "wasm-bindgen-test-macro"
869−version = "0.3.5"
579+version = "0.2.39"
870source = "registry+https://github.com/rust-lang/crates.io-index"
580source = "registry+https://github.com/rust-lang/crates.io-index"
871dependencies = [
581dependencies = [
872− "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
582+ "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)",
583+ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
874]
584]
875
585
876[[package]]
586[[package]]
877name = "wasm-bindgen-webidl"
587name = "wasm-bindgen-webidl"
878−version = "0.2.55"
588+version = "0.2.39"
879source = "registry+https://github.com/rust-lang/crates.io-index"
589source = "registry+https://github.com/rust-lang/crates.io-index"
880dependencies = [
590dependencies = [
881− "anyhow 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
591+ "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)",
592 "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)",
593+ "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)",
594+ "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)",
595+ "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)",
596+ "syn 0.15.29 (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)",
597+ "wasm-bindgen-backend 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
888− "weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
598+ "weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
889]
599]
890
600
891[[package]]
601[[package]]
892name = "web-sys"
602name = "web-sys"
893−version = "0.3.32"
603+version = "0.3.16"
894source = "registry+https://github.com/rust-lang/crates.io-index"
604source = "registry+https://github.com/rust-lang/crates.io-index"
895dependencies = [
605dependencies = [
606+ "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
896− "anyhow 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
607+ "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)",
608+ "js-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
898 "sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
609 "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)",
610+ "wasm-bindgen 0.2.39 (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)",
611+ "wasm-bindgen-webidl 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
901]
612]
902
613
903[[package]]
614[[package]]
904name = "weedle"
615name = "weedle"
905−version = "0.10.0"
616+version = "0.8.0"
906source = "registry+https://github.com/rust-lang/crates.io-index"
617source = "registry+https://github.com/rust-lang/crates.io-index"
907dependencies = [
618dependencies = [
908− "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
619+ "nom 4.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
909]
620]
910
621
911[[package]]
622[[package]]
912name = "winapi"
623name = "winapi"
913−version = "0.3.8"
624+version = "0.3.6"
914source = "registry+https://github.com/rust-lang/crates.io-index"
625source = "registry+https://github.com/rust-lang/crates.io-index"
915dependencies = [
626dependencies = [
916 "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
627 "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -927,7 +638,7 @@ name = "winapi-util"
927version = "0.1.2"
638version = "0.1.2"
928source = "registry+https://github.com/rust-lang/crates.io-index"
639source = "registry+https://github.com/rust-lang/crates.io-index"
929dependencies = [
640dependencies = [
930− "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
641+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
931]
642]
932
643
933[[package]]
644[[package]]
@@ -935,101 +646,78 @@ name = "winapi-x86_64-pc-windows-gnu"
935version = "0.4.0"
646version = "0.4.0"
936source = "registry+https://github.com/rust-lang/crates.io-index"
647source = "registry+https://github.com/rust-lang/crates.io-index"
937
648
649+[[package]]
650+name = "wincolor"
651+version = "1.0.1"
652+source = "registry+https://github.com/rust-lang/crates.io-index"
653+dependencies = [
654+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
655+ "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
656+]
657+
938[metadata]
658[metadata]
939−"checksum anyhow 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9267dff192e68f3399525901e709a48c1d3982c9c072fa32f2127a0cb0babf14"
659+"checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5"
940−"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
660+"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
941−"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
661+"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799"
942−"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
662+"checksum backtrace 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "cd5a90e2b463010cd0e0ce9a11d4a9d5d58d9f41d4a6ba3dcaf9e68b466e88b4"
943−"checksum bstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8d6c2c5b58ab920a4f5aeaaca34b4488074e8cc7596af94e6f8c6ff247c60245"
663+"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6"
944−"checksum bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad807f2fc2bf185eeb98ff3a901bd46dc5ad58163d0fa4577ba0d25674d71708"
664+"checksum bumpalo 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "20bd7761c869bd69b5bd9dbbb1bd84de2f3970b764172efc41e67a06b508c56e"
945−"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
665+"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb"
946−"checksum cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0"
666+"checksum cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "c9ce8bb087aacff865633f0bd5aeaed910fe2fe55b55f4739527f2e023a2e53d"
947−"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
667+"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"
668"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"
669+"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"
670+"checksum env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b61fa891024a945da30a9581546e8cfaf5602c7b3f4c137a2805cf388f92075a"
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"
671+"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2"
961−"checksum futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5"
672+"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1"
962−"checksum futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399"
673+"checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b"
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"
965−"checksum futures-macro 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39"
966−"checksum futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc"
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"
674"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"
675"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"
676+"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"
677+"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"
678+"checksum js-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "3970f67ef4a716efeba501dbedcc01b4686301b661798a42b24dc8a59d2c6b01"
976−"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
679+"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
977−"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
680+"checksum libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "aab692d7759f5cd8c859e169db98ae5b52c924add2af5fbbca11d12fefb567c1"
978−"checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
681+"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"
682+"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"
683+"checksum nom 4.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "22293d25d3f33a8567cc8a1dc20f40c7eeb761ce83d0fcca059858580790cac3"
981−"checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9"
684+"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"
685+"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"
686+"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"
687+"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"
688+"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"
689+"checksum regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53ee8cfdddb2e0291adfb9f13d31d3bbe0a03c9a402c01b1e24188d86c35b24f"
999−"checksum regex-syntax 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e734e891f5b408a29efbf8309e656876276f49ab6a6ac208600b4419bd893d90"
690+"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"
691+"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"
692+"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"
1003"checksum scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
693"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"
694+"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"
695+"checksum serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6eabf4b5914e88e24eea240bb7c9f9a2cbc1bbbe8d961d381975ec3c6b806c"
1010−"checksum serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)" = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c"
696+"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"
697"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"
698+"checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2"
1014−"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
699+"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"
700+"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"
701+"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"
702+"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"
703+"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86"
704+"checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1"
705+"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
706+"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"
707"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"
708+"checksum wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "06e36714cc3947a9c80ddc9715edb319e445e5ab0d0b083debec0dd52558580e"
1022−"checksum wasm-bindgen-backend 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "1845584bd3593442dc0de6e6d9f84454a59a057722f36f005e44665d6ab19d85"
709+"checksum wasm-bindgen-backend 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "6764493f6eeb3f8632ffd418bf7db6a7e7aca98417c115833e4663976cd60a5e"
1023−"checksum wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1458706aa1b8fe6898d19433c9f110d93a05d1f22ae6adf55810409a94df34b4"
710+"checksum wasm-bindgen-futures 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4327c859969477dbc2033bbbe3a6cc045cf155ef283ad9f3c2d804ba27d3fd9b"
1024−"checksum wasm-bindgen-macro 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "87fcc747e6b73c93d22c947a6334644d22cfec5abd8b66238484dc2b0aeb9fe4"
711+"checksum wasm-bindgen-macro 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a473501c40a2a4da83ae7176fead61f10d003554b701071a704dd0667397027a"
1025−"checksum wasm-bindgen-macro-support 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "3dc4b3f2c4078c8c4a5f363b92fcf62604c5913cbd16c6ff5aaf0f74ec03f570"
712+"checksum wasm-bindgen-macro-support 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "72e5a249738c6315f65ce5527632e2a3a067cdf1765422d8ac1572dcee14f819"
1026−"checksum wasm-bindgen-shared 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "ca0b78d6d3be8589b95d1d49cdc0794728ca734adf36d7c9f07e6459508bb53d"
713+"checksum wasm-bindgen-shared 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "70f57643dac7decc3b53f0e4ffd027e225b79281648842eb46015e58a2400870"
1027−"checksum wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ed5833682054e941db0b7e2b4877b61bbaafd680608e40d268e070d34558b305"
714+"checksum wasm-bindgen-test 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "c864512cd0031acd354ba9252c27e426d95f8e7776e877104d54b2eb482999ac"
1028−"checksum wasm-bindgen-test-macro 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9c5dcf70d21d0ff0ea5cdb97dba9a9b56ddad4a9bd10c8912eedc55618be022f"
715+"checksum wasm-bindgen-test-macro 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "c9ec739799351a564254f1f8a7b2b5fdb4117e11c5ee68082e47301722d8973e"
1029−"checksum wasm-bindgen-webidl 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "3126356474ceb717c8fb5549ae387c9fbf4872818454f4d87708bee997214bb5"
716+"checksum wasm-bindgen-webidl 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "ef834a1f96cdc811875c00f20680bf15e038e0d0d458a82772afec5fe31ec7c1"
1030−"checksum web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)" = "98405c0a2e722ed3db341b4c5b70eb9fe0021621f7350bab76df93b09b649bbf"
717+"checksum web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "a8286eada98bf34db48fc89ca53428c529a906bf161c6bae4e022f826323eb38"
1031−"checksum weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bb43f70885151e629e2a19ce9e50bd730fd436cfd4b666894c9ce4de9141164"
718+"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"
719+"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"
720"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"
721"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"
722"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
723+"checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba"
Cargo.toml+14−50
@@ -8,52 +8,23 @@ license = "MPL-2.0"
8name = "dodrio"
8name = "dodrio"
9readme = "./README.md"
9readme = "./README.md"
10repository = "https://github.com/fitzgen/dodrio"
10repository = "https://github.com/fitzgen/dodrio"
11−version = "0.2.0"
11+version = "0.1.0"
12
12
13[badges]
13[badges]
14travis-ci = { repository = "fitzgen/dodrio" }
14travis-ci = { repository = "fitzgen/dodrio" }
15
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−
44[dependencies]
16[dependencies]
45−bumpalo = "2.6.0"
17+bumpalo = "2.2.2"
46−cfg-if = "0.1.10"
18+cfg-if = "0.1.7"
19+futures = "0.1.25"
20+wasm-bindgen = "0.2.39"
21+wasm-bindgen-futures = "0.3.16"
22+js-sys = "0.3.16"
47fxhash = "0.2.1"
23fxhash = "0.2.1"
48−js-sys = "0.3.32"
49−log = { version = "0.4.11", optional = true }
24+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
25
55[dependencies.web-sys]
26[dependencies.web-sys]
56−version = "0.3.32"
27+version = "0.3.16"
57features = [
28features = [
58 "console",
29 "console",
59 "Document",
30 "Document",
@@ -64,14 +35,13 @@ features = [
64]
35]
65
36
66[dev-dependencies]
37[dev-dependencies]
67−console_log = "0.2.0"
38+console_log = "0.1.2"
68−dodrio-js-api = { version = "=0.2.0", path = "./crates/js-api" }
39+dodrio-js-api = { version = "=0.1.0", path = "./crates/js-api" }
69−futures = "0.3.5"
70−log = "0.4.11"
40+log = "0.4.6"
71−wasm-bindgen-test = "0.3.5"
41+wasm-bindgen-test = "0.2.39"
72
42
73[dev-dependencies.web-sys]
43[dev-dependencies.web-sys]
74−version = "0.3.32"
44+version = "0.3.16"
75features = [
45features = [
76 "Attr",
46 "Attr",
77 "EventTarget",
47 "EventTarget",
@@ -80,12 +50,6 @@ features = [
80 "NodeList",
50 "NodeList",
81]
51]
82
52
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]
53[profile.release]
90incremental = false
54incremental = false
91lto = true
55lto = true
README.md+6−13
@@ -31,10 +31,12 @@ struct Hello {
31}
31}
32
32
33impl Render for Hello {
33impl Render for Hello {
34− fn render<'a>(&self, cx: &mut RenderContext<a>) -> Node<'a> {
34+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
35− let who = bumpalo::format!(in cx.bump, "Hello, {}!", self.who);
35+ where
36− div(cx)
36+ 'a: 'bump,
37+ {
38+ span(bump)
37− .children([text(who.into_bump_str())])
39+ .children([text("Hello, "), text(&self.who), text("!")])
38 .finish()
40 .finish()
39 }
41 }
40}
42}
@@ -57,15 +59,6 @@ directory](https://github.com/fitzgen/dodrio/tree/master/examples), including:
57* [`js-component`](https://github.com/fitzgen/dodrio/tree/master/examples/js-component):
59* [`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.
60 Defines a rendering component in JavaScript with the `dodrio-js-api` crate.
59
61
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−
69## Design
62## Design
70
63
71### Bump Allocation
64### Bump Allocation
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+7−6
@@ -7,16 +7,17 @@ keywords = ["javascript", "js", "vdom", "virtual-dom"]
7license = "MPL-2.0"
7license = "MPL-2.0"
8name = "dodrio-js-api"
8name = "dodrio-js-api"
9repository = "https://github.com/fitzgen/dodrio"
9repository = "https://github.com/fitzgen/dodrio"
10−version = "0.2.0"
10+version = "0.1.0"
11
11
12[badges]
12[badges]
13travis-ci = { repository = "fitzgen/dodrio" }
13travis-ci = { repository = "fitzgen/dodrio" }
14
14
15[dependencies]
15[dependencies]
16−dodrio = { version = "=0.2.0", path = "../.." }
16+dodrio = { version = "=0.1.0", path = "../.." }
17−wasm-bindgen = "0.2.55"
17+wasm-bindgen = "0.2.39"
18−js-sys = "0.3.32"
18+js-sys = "0.3.16"
19−wasm-bindgen-futures = "0.4.5"
19+wasm-bindgen-futures = "0.3.16"
20+futures = "0.1.25"
20
21
21[dev-dependencies]
22[dev-dependencies]
22−wasm-bindgen-test = "0.3.5"
23+wasm-bindgen-test = "0.2.39"
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.39"
18
18
19[dependencies.web-sys]
19[dependencies.web-sys]
20−version = "0.3.32"
20+version = "0.3.16"
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.39"
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+12−9
@@ -1,4 +1,5 @@
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
@@ -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.toml+6−5
@@ -11,13 +11,14 @@ crate-type = ["cdylib"]
11
11
12[dependencies]
12[dependencies]
13dodrio = { path = "../.." }
13dodrio = { path = "../.." }
14−wasm-bindgen = "0.2.55"
14+wasm-bindgen = "0.2.39"
15console_error_panic_hook = "0.1.6"
15console_error_panic_hook = "0.1.6"
16−js-sys = "0.3.32"
16+js-sys = "0.3.16"
17−wasm-bindgen-futures = "0.4.5"
17+wasm-bindgen-futures = "0.3.16"
18+futures = "0.1.25"
18
19
19[dependencies.web-sys]
20[dependencies.web-sys]
20−version = "0.3.32"
21+version = "0.3.16"
21features = [
22features = [
22 "Document",
23 "Document",
23 "HtmlElement",
24 "HtmlElement",
@@ -26,4 +27,4 @@ features = [
26]
27]
27
28
28[dev-dependencies]
29[dev-dependencies]
29−wasm-bindgen-test = "0.3.5"
30+wasm-bindgen-test = "0.2.39"
examples/game-of-life/README.md+1−1
@@ -11,7 +11,7 @@ See `src/lib.rs`.
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/game-of-life/index.htmlmode 100755 → 100644+6−3
@@ -19,6 +19,9 @@
19 padding: 0;
19 padding: 0;
20 }
20 }
21
21
22+ .universe {
23+ }
24+
22 body {
25 body {
23 width: 100vw;
26 width: 100vw;
24 height: 100vh;
27 height: 100vh;
@@ -36,9 +39,9 @@
36 </style>
39 </style>
37 </head>
40 </head>
38 <body>
41 <body>
39− <script type="module">
40− import init from "./pkg/dodrio_game_of_life.js";
42+ <script src="pkg/dodrio_game_of_life.js"></script>
43+ <script>
41− init("./pkg/dodrio_game_of_life_bg.wasm");
44+ wasm_bindgen("pkg/dodrio_game_of_life_bg.wasm");
42 </script>
45 </script>
43 </body>
46 </body>
44</html>
47</html>
examples/game-of-life/src/lib.rs+23−25
@@ -1,4 +1,6 @@
1−use dodrio::{bumpalo, Node, Render, RenderContext, Vdom};
1+use dodrio::bumpalo::{self, Bump};
2+use dodrio::{Node, Render, Vdom};
3+use futures::prelude::*;
2use std::cell::RefCell;
4use std::cell::RefCell;
3use std::rc::Rc;
5use std::rc::Rc;
4use wasm_bindgen::{prelude::*, JsCast};
6use wasm_bindgen::{prelude::*, JsCast};
@@ -126,19 +128,21 @@ impl Universe {
126}
128}
127
129
128/// The rendering implementation for our Game of Life.
130/// The rendering implementation for our Game of Life.
129−impl<'a> Render<'a> for Universe {
131+impl Render for Universe {
130− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
132+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
133+ where
134+ 'a: 'bump,
135+ {
131 use dodrio::builder::*;
136 use dodrio::builder::*;
132
137
133− let mut rows = bumpalo::collections::Vec::with_capacity_in(self.height as usize, cx.bump);
138+ let mut rows = bumpalo::collections::Vec::with_capacity_in(self.height as usize, bump);
134
139
135 for row in self.cells.chunks(self.width as usize) {
140 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);
141+ let mut cells = bumpalo::collections::Vec::with_capacity_in(self.width as usize, bump);
138
142
139 for cell in row {
143 for cell in row {
140 cells.push(
144 cells.push(
141− span(&cx)
145+ span(bump)
142 .attr("class", "cell")
146 .attr("class", "cell")
143 .attr(
147 .attr(
144 "style",
148 "style",
@@ -151,10 +155,10 @@ impl<'a> Render<'a> for Universe {
151 );
155 );
152 }
156 }
153
157
154− rows.push(div(&cx).attr("class", "row").children(cells).finish());
158+ rows.push(div(bump).attr("class", "row").children(cells).finish());
155 }
159 }
156
160
157− div(&cx).attr("class", "universe").children(rows).finish()
161+ div(bump).attr("class", "universe").children(rows).finish()
158 }
162 }
159}
163}
160
164
@@ -171,32 +175,26 @@ pub fn run() {
171 // Create a new Game of Life `Universe` render component.
175 // Create a new Game of Life `Universe` render component.
172 let universe = Universe::new();
176 let universe = Universe::new();
173
177
174− // Create a virtual DOM and mount it and the `Universe` render component to the
178+ // Create a virtual DOM and mount it and the `Hello` render component to the
175 // `<body>`.
179 // `<body>`.
176 let vdom = Vdom::new(body.as_ref(), universe);
180 let vdom = Vdom::new(body.as_ref(), universe);
177
181
178 // Kick off a loop that keeps computing a tick of the universe and then
182 // Kick off a loop that keeps computing a tick of the universe and then
179 // re-rendering on every animation frame.
183 // re-rendering on every animation frame.
180− let rc = <Rc<RefCell<Option<Closure<dyn FnMut()>>>>>::default();
184+ let rc: Rc<RefCell<Option<Closure<FnMut()>>>> = Rc::new(RefCell::new(None));
181 let rc2 = rc.clone();
185 let rc2 = rc.clone();
182 let window2 = window.clone();
186 let window2 = window.clone();
183 let weak = vdom.weak();
187 let weak = vdom.weak();
184 let f = Closure::wrap(Box::new(move || {
188 let f = Closure::wrap(Box::new(move || {
185 weak.schedule_render();
189 weak.schedule_render();
186
190
187− wasm_bindgen_futures::spawn_local({
191+ wasm_bindgen_futures::spawn_local(
188− let weak = weak.clone();
189− async move {
190− let fut = weak.with_component(|root| {
192+ weak.with_component(|root| {
191− let universe = root.unwrap_mut::<Universe>();
193+ let universe = root.unwrap_mut::<Universe>();
192− universe.tick();
194+ universe.tick();
193− });
195+ })
194−
195− if fut.await.is_err() {
196− wasm_bindgen::throw_str("impossible, we always keep the vdom alive");
196+ .map_err(|_| wasm_bindgen::throw_str("impossible, we always keep the vdom alive")),
197− }
198− }
199− });
197+ );
200
198
201 window
199 window
202 .request_animation_frame(
200 .request_animation_frame(
@@ -207,7 +205,7 @@ pub fn run() {
207 .unchecked_ref::<js_sys::Function>(),
205 .unchecked_ref::<js_sys::Function>(),
208 )
206 )
209 .unwrap_throw();
207 .unwrap_throw();
210− }) as Box<dyn FnMut()>);
208+ }) as Box<FnMut()>);
211 window2
209 window2
212 .request_animation_frame(f.as_ref().unchecked_ref::<js_sys::Function>())
210 .request_animation_frame(f.as_ref().unchecked_ref::<js_sys::Function>())
213 .unwrap_throw();
211 .unwrap_throw();
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.39"
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.16"
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.39"
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.39"
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.16"
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.39"
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.39"
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.16"
18
18
19[dependencies.web-sys]
19[dependencies.web-sys]
20−version = "0.3.32"
20+version = "0.3.16"
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.39"
15−wasm-bindgen-futures = "0.4.5"
16+wasm-bindgen-futures = "0.3.16"
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.16"
21
22
22[dependencies.web-sys]
23[dependencies.web-sys]
23−version = "0.3.32"
24+version = "0.3.16"
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.39"
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.toml+6−5
@@ -9,13 +9,14 @@ crate-type = ["cdylib"]
9
9
10[dependencies]
10[dependencies]
11dodrio = { path = "../.." }
11dodrio = { path = "../.." }
12−wasm-bindgen = "0.2.55"
12+wasm-bindgen = "0.2.39"
13console_error_panic_hook = "0.1.6"
13console_error_panic_hook = "0.1.6"
14−js-sys = "0.3.32"
14+js-sys = "0.3.16"
15−wasm-bindgen-futures = "0.4.5"
15+wasm-bindgen-futures = "0.3.16"
16+futures = "0.1.25"
16
17
17[dependencies.web-sys]
18[dependencies.web-sys]
18−version = "0.3.32"
19+version = "0.3.16"
19features = [
20features = [
20 "Document",
21 "Document",
21 "HtmlElement",
22 "HtmlElement",
@@ -25,4 +26,4 @@ features = [
25]
26]
26
27
27[dev-dependencies]
28[dev-dependencies]
28−wasm-bindgen-test = "0.3.5"
29+wasm-bindgen-test = "0.2.39"
examples/sierpinski-triangle/README.mdmode 100755 → 100644+2−2
@@ -12,7 +12,7 @@ See `src/lib.rs`.
12## Build
12## Build
13
13
14```
14```
15−wasm-pack build --target web
15+wasm-pack build --target no-modules
16```
16```
17
17
18## Serve
18## Serve
@@ -20,5 +20,5 @@ wasm-pack build --target web
20Use any HTTP server, for example:
20Use any HTTP server, for example:
21
21
22```
22```
23−python -m SimpleHTTPServer
23+python -m http.server
24```
24```
examples/sierpinski-triangle/index.html+4−4
@@ -39,9 +39,9 @@
39 <code>FPS: <output id="fps">...</output></code>
39 <code>FPS: <output id="fps">...</output></code>
40 <div id="scene"></div>
40 <div id="scene"></div>
41
41
42− <script type="module">
43− import init from "./pkg/dodrio_sierpinski.js";
42+ <script src="pkg/dodrio_sierpinski.js"></script>
43+ <script>
44− init("./pkg/dodrio_sierpinski_bg.wasm");
44+ wasm_bindgen("pkg/dodrio_sierpinski_bg.wasm");
45 </script>
45 </script>
46
46
47 <script>
47 <script>
@@ -66,4 +66,4 @@
66 </script>
66 </script>
67</body>
67</body>
68
68
69−</html>
69+</html>
examples/sierpinski-triangle/src/lib.rs+43−27
@@ -1,7 +1,9 @@
1−use dodrio::{bumpalo, Node, Render, RenderContext, Vdom};
1+use dodrio::bumpalo::{self, Bump};
2+use dodrio::{Node, Render, Vdom};
2use std::cell::RefCell;
3use std::cell::RefCell;
3use std::rc::Rc;
4use std::rc::Rc;
4−use wasm_bindgen::{prelude::*, JsCast};
5+use wasm_bindgen::prelude::*;
6+use wasm_bindgen::JsCast;
5use web_sys::Window;
7use web_sys::Window;
6
8
7// Triangle container defining target size.
9// Triangle container defining target size.
@@ -16,11 +18,14 @@ impl Container {
16 }
18 }
17
19
18 // Generate the container style to fluctuate triangle width.
20 // Generate the container style to fluctuate triangle width.
19− fn container_transform<'a>(&self, cx: &mut RenderContext<'a>, elapsed: f64) -> &'a str {
21+ fn container_transform<'a, 'bump>(&'a self, bump: &'bump Bump, elapsed: f64) -> &'bump str
22+ where
23+ 'a: 'bump,
24+ {
20 let t = elapsed % 10.0;
25 let t = elapsed % 10.0;
21 let scale = 0.45 + (if t > 5.0 { 10.0 - t } else { t }) / 40.0;
26 let scale = 0.45 + (if t > 5.0 { 10.0 - t } else { t }) / 40.0;
22 let transform = bumpalo::format!(
27 let transform = bumpalo::format!(
23− in cx.bump,
28+ in bump,
24 "transform: scaleX({}) scaleY(0.7) translateZ(0.1px)",
29 "transform: scaleX({}) scaleY(0.7) translateZ(0.1px)",
25 scale
30 scale
26 );
31 );
@@ -28,11 +33,14 @@ impl Container {
28 }
33 }
29
34
30 // Generate the dot's position on the grid.
35 // Generate the dot's position on the grid.
31− fn dot_style<'a>(&self, cx: &mut RenderContext<'a>, x: f64, y: f64) -> &'a str {
36+ fn dot_style<'a, 'bump>(&'a self, bump: &'bump Bump, x: f64, y: f64) -> &'bump str
37+ where
38+ 'a: 'bump,
39+ {
32 let s = self.size * 1.3;
40 let s = self.size * 1.3;
33 let radius = s / 2.0;
41 let radius = s / 2.0;
34 let styles = bumpalo::format!(
42 let styles = bumpalo::format!(
35− in cx.bump,
43+ in bump,
36 r#"
44 r#"
37 width: {}px;
45 width: {}px;
38 height: {}px;
46 height: {}px;
@@ -47,46 +55,54 @@ impl Container {
47 }
55 }
48
56
49 // Create a dot node.
57 // Create a dot node.
50− fn dot<'a>(&self, cx: &mut RenderContext<'a>, x: f64, y: f64, content: u32) -> Node<'a> {
58+ fn dot<'a, 'bump>(&'a self, bump: &'bump Bump, x: f64, y: f64, content: u32) -> Node<'bump>
59+ where
60+ 'a: 'bump,
61+ {
51 use dodrio::builder::{div, text};
62 use dodrio::builder::{div, text};
52
63
53− div(&cx)
64+ div(bump)
54 .attr("class", "dot")
65 .attr("class", "dot")
55− .attr("style", self.dot_style(cx, x, y))
66+ .attr("style", self.dot_style(bump, x, y))
56 .child(text(
67 .child(text(
57− bumpalo::format!(in cx.bump, "{}", content).into_bump_str(),
68+ bumpalo::format!(in bump, "{}", content).into_bump_str(),
58 ))
69 ))
59 .finish()
70 .finish()
60 }
71 }
61
72
62 // Create a flattened vector of dot nodes (arranged in recursive triangles)
73 // Create a flattened vector of dot nodes (arranged in recursive triangles)
63− fn triangle<'a>(
74+ fn triangle<'a, 'bump>(
64− &self,
75+ &'a self,
65− cx: &mut RenderContext<'a>,
76+ bump: &'bump Bump,
66 x: f64,
77 x: f64,
67 y: f64,
78 y: f64,
68 s: f64,
79 s: f64,
69 content: u32,
80 content: u32,
70− children: &mut bumpalo::collections::Vec<Node<'a>>,
81+ children: &mut bumpalo::collections::Vec<Node<'bump>>,
82+ ) where
83+ 'a: 'bump,
71− ) {
84+ {
72 if s <= self.size {
85 if s <= self.size {
73− children.push(self.dot(cx, x, y, content));
86+ children.push(self.dot(bump, x, y, content));
74 return;
87 return;
75 }
88 }
76
89
77 let s = s / 2.0;
90 let s = s / 2.0;
78
91
79 // Top of triangle
92 // Top of triangle
80− self.triangle(cx, x, y - (s / 2.0), s, content, children);
93+ self.triangle(bump, x, y - (s / 2.0), s, content, children);
81 // Bottom-left of triangle
94 // Bottom-left of triangle
82− self.triangle(cx, x - s, y + (s / 2.0), s, content, children);
95+ self.triangle(bump, x - s, y + (s / 2.0), s, content, children);
83 // Bottom-right of triangle
96 // Bottom-right of triangle
84− self.triangle(cx, x + s, y + (s / 2.0), s, content, children);
97+ self.triangle(bump, x + s, y + (s / 2.0), s, content, children);
85 }
98 }
86}
99}
87
100
88−impl<'a> Render<'a> for Container {
101+impl Render for Container {
89− fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
102+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
103+ where
104+ 'a: 'bump,
105+ {
90 use dodrio::builder::div;
106 use dodrio::builder::div;
91
107
92 let elapsed = web_sys::window()
108 let elapsed = web_sys::window()
@@ -98,12 +114,12 @@ impl<'a> Render<'a> for Container {
98
114
99 let modulus = elapsed as u32 % 10;
115 let modulus = elapsed as u32 % 10;
100
116
101− let mut children = bumpalo::collections::Vec::new_in(cx.bump);
117+ let mut children = bumpalo::collections::Vec::new_in(bump);
102− self.triangle(cx, 0.0, 0.0, 1000.0, modulus, &mut children);
118+ self.triangle(bump, 0.0, 0.0, 1000.0, modulus, &mut children);
103
119
104− div(&cx)
120+ div(bump)
105 .attr("class", "container")
121 .attr("class", "container")
106− .attr("style", self.container_transform(cx, elapsed))
122+ .attr("style", self.container_transform(bump, elapsed))
107 .children(children)
123 .children(children)
108 .finish()
124 .finish()
109 }
125 }
@@ -111,7 +127,7 @@ impl<'a> Render<'a> for Container {
111
127
112// Kick off a loop that keeps re-rendering on every animation frame.
128// Kick off a loop that keeps re-rendering on every animation frame.
113fn animate(window: Window, vdom: Vdom) {
129fn animate(window: Window, vdom: Vdom) {
114− let rc = <Rc<RefCell<Option<Closure<dyn FnMut()>>>>>::default();
130+ let rc: Rc<RefCell<Option<Closure<FnMut()>>>> = Rc::new(RefCell::new(None));
115 let rc2 = rc.clone();
131 let rc2 = rc.clone();
116 let window2 = window.clone();
132 let window2 = window.clone();
117 let weak = vdom.weak();
133 let weak = vdom.weak();
@@ -126,7 +142,7 @@ fn animate(window: Window, vdom: Vdom) {
126 .unchecked_ref::<js_sys::Function>(),
142 .unchecked_ref::<js_sys::Function>(),
127 )
143 )
128 .unwrap_throw();
144 .unwrap_throw();
129− }) as Box<dyn FnMut()>);
145+ }) as Box<FnMut()>);
130 window2
146 window2
131 .request_animation_frame(f.as_ref().unchecked_ref::<js_sys::Function>())
147 .request_animation_frame(f.as_ref().unchecked_ref::<js_sys::Function>())
132 .unwrap_throw();
148 .unwrap_throw();
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.16"
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.39"
14−wasm-bindgen-futures = "0.4.5"
15+wasm-bindgen-futures = "0.3.16"
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.16"
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.39"
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+268
@@ -0,0 +1,268 @@
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 nameId = mem32[i++];
47+ const valueId = mem32[i++];
48+ const name = changeList.getString(nameId);
49+ const value = changeList.getString(valueId);
50+ const node = top(changeList.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(changeList, mem8, mem32, i) {
69+ const nameId = mem32[i++];
70+ const name = changeList.getString(nameId);
71+ const node = top(changeList.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 pushFirstChild(changeList, mem8, mem32, i) {
90+ changeList.stack.push(top(changeList.stack).firstChild);
91+ return i;
92+ },
93+
94+ // 6
95+ function popPushNextSibling(changeList, mem8, mem32, i) {
96+ const node = changeList.stack.pop();
97+ changeList.stack.push(node.nextSibling);
98+ return i;
99+ },
100+
101+ // 7
102+ function pop(changeList, mem8, mem32, i) {
103+ changeList.stack.pop();
104+ return i;
105+ },
106+
107+ // 8
108+ function appendChild(changeList, mem8, mem32, i) {
109+ const child = changeList.stack.pop();
110+ top(changeList.stack).appendChild(child);
111+ return i;
112+ },
113+
114+ // 9
115+ function createTextNode(changeList, mem8, mem32, i) {
116+ const pointer = mem32[i++];
117+ const length = mem32[i++];
118+ const text = string(mem8, pointer, length);
119+ changeList.stack.push(document.createTextNode(text));
120+ return i;
121+ },
122+
123+ // 10
124+ function createElement(changeList, mem8, mem32, i) {
125+ const tagNameId = mem32[i++];
126+ const tagName = changeList.getString(tagNameId);
127+ changeList.stack.push(document.createElement(tagName));
128+ return i;
129+ },
130+
131+ // 11
132+ function newEventListener(changeList, mem8, mem32, i) {
133+ const eventId = mem32[i++];
134+ const eventType = changeList.getString(eventId);
135+ const a = mem32[i++];
136+ const b = mem32[i++];
137+ const el = top(changeList.stack);
138+ el.addEventListener(eventType, changeList.eventHandler);
139+ el[`dodrio-a-${eventType}`] = a;
140+ el[`dodrio-b-${eventType}`] = b;
141+ return i;
142+ },
143+
144+ // 12
145+ function updateEventListener(changeList, mem8, mem32, i) {
146+ const eventId = mem32[i++];
147+ const eventType = changeList.getString(eventId);
148+ const el = top(changeList.stack);
149+ el[`dodrio-a-${eventType}`] = mem32[i++];
150+ el[`dodrio-b-${eventType}`] = mem32[i++];
151+ return i;
152+ },
153+
154+ // 13
155+ function removeEventListener(changeList, mem8, mem32, i) {
156+ const eventId = mem32[i++];
157+ const eventType = changeList.getString(eventId);
158+ const el = top(changeList.stack);
159+ el.removeEventListener(eventType, changeList.eventHandler);
160+ return i;
161+ },
162+
163+ // 14
164+ function addString(changeList, mem8, mem32, i) {
165+ const pointer = mem32[i++];
166+ const length = mem32[i++];
167+ const id = mem32[i++];
168+ const str = string(mem8, pointer, length);
169+ changeList.addString(str, id);
170+ return i;
171+ },
172+
173+ // 15
174+ function dropString(changeList, mem8, mem32, i) {
175+ const id = mem32[i++];
176+ changeList.dropString(id);
177+ return i;
178+ }
179+];
180+
181+// export
182+class ChangeList {
183+ constructor(container) {
184+ this.trampoline = null;
185+ this.container = container;
186+ this.ranges = [];
187+ this.stack = [];
188+ this.strings = new Map();
189+ }
190+
191+ unmount() {
192+ this.trampoline.mounted = false;
193+
194+ // Null out all of our properties just to ensure that if we mistakenly ever
195+ // call a method on this instance again, it will throw.
196+ this.trampoline = null;
197+ this.container = null;
198+ this.ranges = null;
199+ this.stack = null;
200+ this.strings = null;
201+ }
202+
203+ addChangeListRange(start, len) {
204+ this.ranges.push(start);
205+ this.ranges.push(len);
206+ }
207+
208+ applyChanges(memory) {
209+ if (this.ranges.length == 0) {
210+ return;
211+ }
212+
213+ this.stack.push(this.container.firstChild);
214+ const mem8 = new Uint8Array(memory.buffer);
215+ const mem32 = new Uint32Array(memory.buffer);
216+
217+ for (let i = 0; i < this.ranges.length; i += 2) {
218+ const start = this.ranges[i];
219+ const len = this.ranges[i + 1];
220+ this.applyChangeRange(mem8, mem32, start, len);
221+ }
222+
223+ this.ranges.length = 0;
224+ this.stack.length = 0;
225+ }
226+
227+ applyChangeRange(mem8, mem32, start, len) {
228+ const end = (start + len) / 4;
229+ for (let i = start / 4; i < end; ) {
230+ const op = mem32[i++];
231+ i = OP_TABLE[op](this, mem8, mem32, i);
232+ }
233+ }
234+
235+ addString(str, id) {
236+ this.strings.set(id, str);
237+ }
238+
239+ dropString(id) {
240+ this.strings.delete(id);
241+ }
242+
243+ getString(id) {
244+ return this.strings.get(id);
245+ }
246+
247+ initEventsTrampoline(trampoline) {
248+ this.trampoline = trampoline;
249+ trampoline.mounted = true;
250+ this.eventHandler = function(event) {
251+ if (!trampoline.mounted) {
252+ throw new Error("invocation of listener after VDOM has been unmounted");
253+ }
254+
255+ // `this` always refers to the element the handler was added to.
256+ // Since we're adding the handler to all elements our content wants
257+ // to listen for events on, this ensures that we always get the right
258+ // values for `a` and `b`.
259+ const type = event.type;
260+ const a = this[`dodrio-a-${type}`];
261+ const b = this[`dodrio-b-${type}`];
262+ trampoline(event, a, b);
263+ }
264+ }
265+}
266+window.ChangeList = ChangeList;
267+
268+//# 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−}
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+72−138
@@ -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.
@@ -77,20 +76,22 @@ where
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+426
@@ -0,0 +1,426 @@
1+use crate::Listener;
2+use bumpalo::Bump;
3+use fxhash::FxHashMap;
4+use std::fmt;
5+use std::sync::Once;
6+use wasm_bindgen::prelude::*;
7+
8+pub mod js {
9+ use wasm_bindgen::prelude::*;
10+
11+ #[wasm_bindgen]
12+ extern "C" {
13+ #[derive(Clone, Debug)]
14+ pub type ChangeList;
15+
16+ #[wasm_bindgen(constructor)]
17+ pub fn new(container: &web_sys::Node) -> ChangeList;
18+
19+ #[wasm_bindgen(structural, method)]
20+ pub fn unmount(this: &ChangeList);
21+
22+ #[wasm_bindgen(structural, method, js_name = addChangeListRange)]
23+ pub fn add_change_list_range(this: &ChangeList, start: usize, len: usize);
24+
25+ #[wasm_bindgen(structural, method, js_name = applyChanges)]
26+ pub fn apply_changes(this: &ChangeList, memory: JsValue);
27+
28+ #[wasm_bindgen(structural, method, js_name = initEventsTrampoline)]
29+ pub fn init_events_trampoline(
30+ this: &ChangeList,
31+ trampoline: &Closure<Fn(web_sys::Event, u32, u32)>,
32+ );
33+ }
34+}
35+
36+struct StringsCacheEntry {
37+ key: u32,
38+ used: bool,
39+}
40+
41+pub(crate) struct ChangeList {
42+ bump: Bump,
43+ strings_cache: FxHashMap<String, StringsCacheEntry>,
44+ next_string_key: u32,
45+ js: js::ChangeList,
46+ events_trampoline: Option<Closure<Fn(web_sys::Event, u32, u32)>>,
47+}
48+
49+impl Drop for ChangeList {
50+ fn drop(&mut self) {
51+ debug!("Dropping ChangeList");
52+ self.js.unmount();
53+ }
54+}
55+
56+impl fmt::Debug for ChangeList {
57+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
58+ f.debug_struct("ChangeList")
59+ .field("bump", &self.bump)
60+ .field("js", &self.js)
61+ .field("events_trampoline", &"..")
62+ .finish()
63+ }
64+}
65+
66+impl ChangeList {
67+ pub(crate) fn new(container: &web_sys::Node) -> ChangeList {
68+ // XXX: Because wasm-bindgen-test doesn't support third party JS
69+ // dependencies, we can't use `wasm_bindgen(module = "...")` for our
70+ // `ChangeList` JS import. Instead, this *should* be a local JS snippet,
71+ // but that isn't implemented yet:
72+ // https://github.com/rustwasm/rfcs/pull/6
73+ static EVAL: Once = Once::new();
74+ EVAL.call_once(|| {
75+ js_sys::eval(include_str!("../js/change-list.js"))
76+ .expect("should eval change-list.js OK");
77+ });
78+
79+ let bump = Bump::new();
80+ let strings_cache = FxHashMap::default();
81+ let js = js::ChangeList::new(container);
82+ ChangeList {
83+ bump,
84+ strings_cache,
85+ next_string_key: 0,
86+ js,
87+ events_trampoline: None,
88+ }
89+ }
90+
91+ pub(crate) fn apply_changes(&mut self) {
92+ let js = &self.js;
93+ unsafe {
94+ self.bump.each_allocated_chunk(|ch| {
95+ js.add_change_list_range(ch.as_ptr() as usize, ch.len());
96+ });
97+ }
98+ js.apply_changes(wasm_bindgen::memory());
99+ self.bump.reset();
100+ }
101+
102+ pub(crate) fn init_events_trampoline(
103+ &mut self,
104+ trampoline: Closure<Fn(web_sys::Event, u32, u32)>,
105+ ) {
106+ debug_assert!(self.events_trampoline.is_none());
107+ self.js.init_events_trampoline(&trampoline);
108+ self.events_trampoline = Some(trampoline);
109+ }
110+}
111+
112+#[repr(u32)]
113+#[derive(Clone, Copy, Debug)]
114+enum ChangeDiscriminant {
115+ /// Immediates: `(pointer, length)`
116+ ///
117+ /// Stack: `[... TextNode] -> [... TextNode]`
118+ ///
119+ /// ```text
120+ /// stack.top().textContent = String(pointer, length)
121+ /// ```
122+ SetText = 0,
123+
124+ /// Immediates: `()`
125+ ///
126+ /// Stack: `[... Node] -> [...]`
127+ ///
128+ /// ```text
129+ /// node = stack.pop()
130+ /// while (node.nextSibling) {
131+ /// node.nextSibling.remove();
132+ /// }
133+ /// node.remove()
134+ /// ```
135+ RemoveSelfAndNextSiblings = 1,
136+
137+ /// Immediates: `()`
138+ ///
139+ /// Stack: `[... Node Node] -> [... Node]`
140+ ///
141+ /// ```text
142+ /// new = stack.pop()
143+ /// old = stack.pop()
144+ /// old.replaceWith(new)
145+ /// stack.push(new)
146+ /// ```
147+ ReplaceWith = 2,
148+
149+ /// Immediates: `(pointer1, length1, pointer2, length2)`
150+ ///
151+ /// Stack: `[... Node] -> [... Node]`
152+ ///
153+ /// ```text
154+ /// stack.top().setAttribute(String(pointer1, length1), String(pointer2, length2))
155+ /// ```
156+ SetAttribute = 3,
157+
158+ /// Immediates: (pointer, length)
159+ ///
160+ /// Stack: `[... Node] -> [... Node]`
161+ ///
162+ /// ```text
163+ /// stack.top().removeAttribute(String(pointer, length))
164+ /// ```
165+ RemoveAttribute = 4,
166+
167+ /// Immediates: `()`
168+ ///
169+ /// Stack: `[... Node] -> [... Node node]`
170+ ///
171+ /// ```text
172+ /// stack.push(stack.top().firstChild)
173+ /// ```
174+ PushFirstChild = 5,
175+
176+ /// Immediates: `()`
177+ ///
178+ /// Stack: `[... Node] -> [... Node]`
179+ ///
180+ /// ```text
181+ /// stack.push(stack.pop().nextSibling)
182+ /// ```
183+ PopPushNextSibling = 6,
184+
185+ /// Immediates: `()`
186+ ///
187+ /// Stack: `[... T] -> [...]`
188+ ///
189+ /// ```text
190+ /// stack.pop()
191+ /// ```
192+ Pop = 7,
193+
194+ /// Immediates: `()`
195+ ///
196+ /// Stack: `[... Node Node] -> [... Node]`
197+ ///
198+ /// ```text
199+ /// child = stack.pop()
200+ /// stack.top().appendChild(child)
201+ /// ```
202+ AppendChild = 8,
203+
204+ /// Immediates: `(pointer, length)`
205+ ///
206+ /// Stack: `[...] -> [... Node]`
207+ ///
208+ /// ```text
209+ /// stack.push(createTextNode(String(pointer, length)))
210+ /// ```
211+ CreateTextNode = 9,
212+
213+ /// Immediates: `(pointer, length)`
214+ ///
215+ /// Stack: `[...] -> [... Node]`
216+ ///
217+ /// ```text
218+ /// stack.push(createElement(String(pointer, length))
219+ /// ```
220+ CreateElement = 10,
221+
222+ /// Immediate: `(pointer, length, A, B)`
223+ ///
224+ /// Stack: `[... Node] -> [... Node]`
225+ ///
226+ /// ```text
227+ /// event = String(pointer, length)
228+ /// callback = ProxyToRustCallback(A, B);
229+ /// stack.top().addEventListener(event, callback);
230+ /// ```
231+ NewEventListener = 11,
232+
233+ /// Immediate: `(pointer, length, A, B)`
234+ ///
235+ /// Stack: `[... Node] -> [... Node]`
236+ ///
237+ /// ```text
238+ /// event = String(pointer, length)
239+ /// new_callback = ProxyToRustCallback(A, B);
240+ /// stack.top().updateEventlistener(new_callback)
241+ /// ```
242+ UpdateEventListener = 12,
243+
244+ /// Immediates: `(pointer, length)`
245+ ///
246+ /// Stack: `[... Node] -> [... Node]`
247+ ///
248+ /// ```text
249+ /// stack.top().removeEventListener(String(pointer, length));
250+ /// ```
251+ RemoveEventListener = 13,
252+
253+ /// Immediates: `(pointer, length, id)`
254+ ///
255+ /// Stack: `[...] -> [...]`
256+ ///
257+ /// ```text
258+ /// addString(String(pointer, length), id);
259+ /// ```
260+ AddString = 14,
261+
262+ /// Immediates: `(id)`
263+ ///
264+ /// Stack: `[...] -> [...]`
265+ ///
266+ /// ```text
267+ /// dropString(id);
268+ /// ```
269+ DropString = 15,
270+}
271+
272+// Allocation utilities to ensure that we only allocation sequences of `u32`s
273+// into the change list's bump arena without any padding. This helps maintain
274+// the invariants required for `Bump::each_allocated_chunk`'s safety.
275+impl ChangeList {
276+ // Allocate an opcode with zero immediates.
277+ fn op0(&self, discriminant: ChangeDiscriminant) {
278+ self.bump.alloc(discriminant as u32);
279+ }
280+
281+ // Allocate an opcode with one immediate.
282+ fn op1(&self, discriminant: ChangeDiscriminant, a: u32) {
283+ self.bump.alloc([discriminant as u32, a]);
284+ }
285+
286+ // Allocate an opcode with two immediates.
287+ fn op2(&self, discriminant: ChangeDiscriminant, a: u32, b: u32) {
288+ self.bump.alloc([discriminant as u32, a, b]);
289+ }
290+
291+ // Allocate an opcode with three immediates.
292+ fn op3(&self, discriminant: ChangeDiscriminant, a: u32, b: u32, c: u32) {
293+ self.bump.alloc([discriminant as u32, a, b, c]);
294+ }
295+
296+ // Note: no 4-immediate opcodes at this time.
297+}
298+
299+impl ChangeList {
300+ fn ensure_string(&mut self, string: &str) -> u32 {
301+ if let Some(entry) = self.strings_cache.get_mut(string) {
302+ entry.used = true;
303+ entry.key
304+ } else {
305+ let key = self.next_string_key;
306+ self.next_string_key += 1;
307+ let entry = StringsCacheEntry { key, used: true };
308+ self.strings_cache.insert(string.to_string(), entry);
309+ self.op3(
310+ ChangeDiscriminant::AddString,
311+ string.as_ptr() as u32,
312+ string.len() as u32,
313+ key,
314+ );
315+ key
316+ }
317+ }
318+
319+ pub(crate) fn drop_unused_strings(&mut self) {
320+ debug!("drop_unused_strings()");
321+ let mut new_cache = FxHashMap::default();
322+ for (key, val) in self.strings_cache.iter() {
323+ if val.used {
324+ new_cache.insert(
325+ key.clone(),
326+ StringsCacheEntry {
327+ key: val.key,
328+ used: false,
329+ },
330+ );
331+ } else {
332+ self.op1(ChangeDiscriminant::DropString, val.key);
333+ }
334+ }
335+ self.strings_cache = new_cache;
336+ }
337+
338+ pub(crate) fn emit_set_text(&self, text: &str) {
339+ debug!("emit_set_text({:?})", text);
340+ self.op2(
341+ ChangeDiscriminant::SetText,
342+ text.as_ptr() as u32,
343+ text.len() as u32,
344+ );
345+ }
346+
347+ pub(crate) fn emit_remove_self_and_next_siblings(&self) {
348+ debug!("emit_remove_self_and_next_siblings()");
349+ self.op0(ChangeDiscriminant::RemoveSelfAndNextSiblings);
350+ }
351+
352+ pub(crate) fn emit_replace_with(&self) {
353+ debug!("emit_replace_with()");
354+ self.op0(ChangeDiscriminant::ReplaceWith);
355+ }
356+
357+ pub(crate) fn emit_set_attribute(&mut self, name: &str, value: &str) {
358+ debug!("emit_set_attribute({:?}, {:?})", name, value);
359+ let name_id = self.ensure_string(name);
360+ let value_id = self.ensure_string(value);
361+ self.op2(ChangeDiscriminant::SetAttribute, name_id, value_id);
362+ }
363+
364+ pub(crate) fn emit_remove_attribute(&mut self, name: &str) {
365+ debug!("emit_remove_attribute({:?})", name);
366+ let name_id = self.ensure_string(name);
367+ self.op1(ChangeDiscriminant::RemoveAttribute, name_id);
368+ }
369+
370+ pub(crate) fn emit_push_first_child(&self) {
371+ debug!("emit_push_first_child()");
372+ self.op0(ChangeDiscriminant::PushFirstChild);
373+ }
374+
375+ pub(crate) fn emit_pop_push_next_sibling(&self) {
376+ debug!("emit_pop_push_next_sibling()");
377+ self.op0(ChangeDiscriminant::PopPushNextSibling);
378+ }
379+
380+ pub(crate) fn emit_pop(&self) {
381+ debug!("emit_pop()");
382+ self.op0(ChangeDiscriminant::Pop);
383+ }
384+
385+ pub(crate) fn emit_append_child(&self) {
386+ debug!("emit_append_child()");
387+ self.op0(ChangeDiscriminant::AppendChild);
388+ }
389+
390+ pub(crate) fn emit_create_text_node(&self, text: &str) {
391+ debug!("emit_create_text_node({:?})", text);
392+ self.op2(
393+ ChangeDiscriminant::CreateTextNode,
394+ text.as_ptr() as u32,
395+ text.len() as u32,
396+ );
397+ }
398+
399+ pub(crate) fn emit_create_element(&mut self, tag_name: &str) {
400+ debug!("emit_create_element({:?})", tag_name);
401+ let tag_name_id = self.ensure_string(tag_name);
402+ self.op1(ChangeDiscriminant::CreateElement, tag_name_id);
403+ }
404+
405+ pub(crate) fn emit_new_event_listener(&mut self, listener: &Listener) {
406+ debug!("emit_new_event_listener({:?})", listener);
407+ let (a, b) = listener.get_callback_parts();
408+ debug_assert!(a != 0);
409+ let event_id = self.ensure_string(listener.event);
410+ self.op3(ChangeDiscriminant::NewEventListener, event_id, a, b);
411+ }
412+
413+ pub(crate) fn emit_update_event_listener(&mut self, listener: &Listener) {
414+ debug!("emit_update_event_listener({:?})", listener);
415+ let (a, b) = listener.get_callback_parts();
416+ debug_assert!(a != 0);
417+ let event_id = self.ensure_string(listener.event);
418+ self.op3(ChangeDiscriminant::UpdateEventListener, event_id, a, b);
419+ }
420+
421+ pub(crate) fn emit_remove_event_listener(&mut self, event: &str) {
422+ debug!("emit_remove_event_listener({:?})", event);
423+ let event_id = self.ensure_string(event);
424+ self.op1(ChangeDiscriminant::RemoveEventListener, event_id);
425+ }
426+}
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+81−144
@@ -1,111 +1,34 @@
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;
4use std::iter;
4use std::iter;
5use std::mem;
5use std::mem;
6−use std::u32;
7
6
8−/// A virtual DOM node.
7+/// A node is either a text node or an element.
9#[derive(Debug, Clone)]
8#[derive(Debug, Clone)]
10−pub struct Node<'a> {
9+pub enum Node<'a> {
11− #[cfg(feature = "xxx-unstable-internal-use-only")]
10+ /// A text node.
12− #[doc(hidden)]
11+ Text(TextNode<'a>),
13− pub kind: NodeKind<'a>,
14
12
15− #[cfg(not(feature = "xxx-unstable-internal-use-only"))]
13+ /// An element potentially with attributes and children.
16− pub(crate) kind: NodeKind<'a>,
14+ Element(ElementNode<'a>),
17}
15}
18
16
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
17+/// Text nodes are just a string of text. They cannot have attributes or
37− /// children.
18+/// children.
38− #[derive(Debug, Clone)]
19+#[derive(Debug, Clone)]
39− pub(crate) struct TextNode<'a> {
20+pub struct TextNode<'a> {
40− pub text: &'a str,
21+ 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}
22}
84
23
85−impl NodeKey {
24+/// Elements have a tag name, zero or more attributes, and zero or more
86− /// The default, lack of a key.
25+/// children.
87− pub const NONE: NodeKey = NodeKey(u32::MAX);
26+#[derive(Debug, Clone)]
88−
27+pub struct ElementNode<'a> {
89− /// Is this key `NodeKey::NONE`?
28+ pub(crate) tag_name: &'a str,
90− #[inline]
29+ pub(crate) listeners: &'a [Listener<'a>],
91− pub fn is_none(&self) -> bool {
30+ pub(crate) attributes: &'a [Attribute<'a>],
92− *self == Self::NONE
31+ 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}
32}
110
33
111/// An event listener callback function.
34/// An event listener callback function.
@@ -115,7 +38,7 @@ impl NodeKey {
115/// 1. The virtual DOM's root rendering component.
38/// 1. The virtual DOM's root rendering component.
116/// 2. A capability to scheduler virtual DOM re-rendering.
39/// 2. A capability to scheduler virtual DOM re-rendering.
117/// 3. The event that occurred.
40/// 3. The event that occurred.
118−pub(crate) type ListenerCallback<'a> =
41+pub type ListenerCallback<'a> =
119 &'a (dyn Fn(&mut dyn RootRender, VdomWeak, web_sys::Event) + 'static);
42 &'a (dyn Fn(&mut dyn RootRender, VdomWeak, web_sys::Event) + 'static);
120
43
121/// An event listener.
44/// An event listener.
@@ -134,15 +57,6 @@ pub struct Attribute<'a> {
134 pub(crate) value: &'a str,
57 pub(crate) value: &'a str,
135}
58}
136
59
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<'_> {
60impl fmt::Debug for Listener<'_> {
147 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
61 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
148 let (a, b) = self.get_callback_parts();
62 let (a, b) = self.get_callback_parts();
@@ -182,61 +96,60 @@ impl<'a> Attribute<'a> {
182}
96}
183
97
184impl<'a> Node<'a> {
98impl<'a> Node<'a> {
185− /// Low-level constructor for making a new `Node` of type element with given
99+ /// 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]
100 #[inline]
192− pub fn element(
101+ pub(crate) fn element<Listeners, Attributes, Children>(
193 bump: &'a Bump,
102 bump: &'a Bump,
194− key: NodeKey,
195 tag_name: &'a str,
103 tag_name: &'a str,
196− listeners: &'a [Listener<'a>],
104+ listeners: Listeners,
197− attributes: &'a [Attribute<'a>],
105+ attributes: Attributes,
198− children: &'a [Node<'a>],
106+ children: Children,
199− namespace: Option<&'a str>,
200− ) -> Node<'a> {
107+ ) -> Node<'a>
201− let element = bump.alloc_with(|| ElementNode {
108+ where
202− key,
109+ Listeners: 'a + AsRef<[Listener<'a>]>,
110+ Attributes: 'a + AsRef<[Attribute<'a>]>,
111+ Children: 'a + AsRef<[Node<'a>]>,
112+ {
113+ let children: &'a Children = bump.alloc(children);
114+ let children: &'a [Node<'a>] = children.as_ref();
115+
116+ let listeners: &'a Listeners = bump.alloc(listeners);
117+ let listeners: &'a [Listener<'a>] = listeners.as_ref();
118+
119+ let attributes: &'a Attributes = bump.alloc(attributes);
120+ let attributes: &'a [Attribute<'a>] = attributes.as_ref();
121+
122+ Node::Element(ElementNode {
203 tag_name,
123 tag_name,
204 listeners,
124 listeners,
205 attributes,
125 attributes,
206 children,
126 children,
207− namespace,
208− });
127+ })
128+ }
209
129
130+ /// Is this node a text node?
131+ pub fn is_text(&self) -> bool {
132+ match self {
210− Node {
133+ Node::Text(_) => true,
211− kind: NodeKind::Element(element),
134+ _ => false,
212 }
135 }
213 }
136 }
214
137
215− /// Construct a new text node with the given text.
138+ /// Is this node an element?
216− #[inline]
139+ pub fn is_element(&self) -> bool {
217− pub(crate) fn text(text: &'a str) -> Node<'a> {
140+ match self {
218− Node {
141+ Node::Element { .. } => true,
219− kind: NodeKind::Text(TextNode { text }),
142+ _ => false,
220 }
143 }
221 }
144 }
222
145
146+ /// Construct a new text node with the given text.
223 #[inline]
147 #[inline]
224− pub(crate) fn key(&self) -> NodeKey {
148+ pub(crate) fn text(text: &'a str) -> Node<'a> {
225− match &self.kind {
149+ Node::Text(TextNode { text })
226− NodeKind::Text(_) => NodeKey::NONE,
227− NodeKind::Element(e) => e.key,
228− NodeKind::Cached(c) => c.key,
229− }
230 }
150 }
231}
151}
232
152
233−/// A node can become an iterator that yields the node itself once.
234−///
235−/// This implementation of `IntoIterator` mostly exists to improve the
236−/// `typed-html` ergonomics, where the macro invokes `.into_iter()` on the child
237−/// contents of a tag. By implementing `IntoIterator` here, we avoid having to
238−/// do nasty shenanigans like `<p>vec![$contents]</p>` instead of plain old
239−/// `<p>$contents</p>`.
240impl<'a> IntoIterator for Node<'a> {
153impl<'a> IntoIterator for Node<'a> {
241 type Item = Node<'a>;
154 type Item = Node<'a>;
242 type IntoIter = iter::Once<Self::Item>;
155 type IntoIter = iter::Once<Self::Item>;
@@ -246,6 +159,30 @@ impl<'a> IntoIterator for Node<'a> {
246 }
159 }
247}
160}
248
161
162+impl<'a> TextNode<'a> {
163+ /// Get this text node's text content.
164+ pub fn text(&self) -> &'a str {
165+ self.text
166+ }
167+}
168+
169+impl<'a> ElementNode<'a> {
170+ /// Get this element's tag name.
171+ pub fn tag_name(&self) -> &'a str {
172+ self.tag_name
173+ }
174+
175+ /// Get this element's attributes.
176+ pub fn attributes(&self) -> &'a [Attribute<'a>] {
177+ self.attributes
178+ }
179+
180+ /// Get this element's attributes.
181+ pub fn children(&self) -> &'a [Node<'a>] {
182+ self.children
183+ }
184+}
185+
249union CallbackFatPtr<'a> {
186union CallbackFatPtr<'a> {
250 callback: ListenerCallback<'a>,
187 callback: ListenerCallback<'a>,
251 parts: (u32, u32),
188 parts: (u32, u32),
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+338−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 access 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,268 @@ 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+ // Find and drop cached strings that aren't in use anymore.
260+ self.change_list.drop_unused_strings();
261+
262+ // Tell JS to apply our diff-generated changes to the physical DOM!
263+ self.change_list.apply_changes();
313 }
264 }
314 }
265 }
315
266
316− fn swap_buffers(&mut self, mut dom_buffers: [Bump; 2]) {
267+ 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);
268+ let (first, second) = self.dom_buffers.as_mut().split_at_mut(1);
319 mem::swap(&mut first[0], &mut second[0]);
269 mem::swap(&mut first[0], &mut second[0]);
320− self.dom_buffers = Some(dom_buffers);
321 }
270 }
322
271
323 unsafe fn set_current_root(&mut self, current: Node<'static>) {
272 unsafe fn set_current_root(&mut self, current: Node<'static>) {
324 debug_assert!(self.current_root.is_none());
273 debug_assert!(self.current_root.is_none());
325 self.current_root = Some(current);
274 self.current_root = Some(current);
326 }
275 }
276+
277+ fn diff<'a>(&mut self, registry: &mut EventsRegistry, old: Node<'a>, new: Node<'a>) {
278+ // debug!("---------------------------------------------------------");
279+ // debug!("dodrio::Vdom::diff");
280+ // debug!(" old = {:#?}", old);
281+ // debug!(" new = {:#?}", new);
282+ match (&new, old) {
283+ (&Node::Text(TextNode { text: new_text }), Node::Text(TextNode { text: old_text })) => {
284+ debug!(" both are text nodes");
285+ if new_text != old_text {
286+ debug!(" text needs updating");
287+ self.change_list.emit_set_text(new_text);
288+ }
289+ }
290+ (&Node::Text(TextNode { .. }), Node::Element(ElementNode { .. })) => {
291+ debug!(" replacing a text node with an element");
292+ self.create(registry, new);
293+ self.change_list.emit_replace_with();
294+ }
295+ (&Node::Element(ElementNode { .. }), Node::Text(TextNode { .. })) => {
296+ debug!(" replacing an element with a text node");
297+ self.create(registry, new);
298+ self.change_list.emit_replace_with();
299+ }
300+ (
301+ &Node::Element(ElementNode {
302+ tag_name: new_tag_name,
303+ listeners: new_listeners,
304+ attributes: new_attributes,
305+ children: new_children,
306+ }),
307+ Node::Element(ElementNode {
308+ tag_name: old_tag_name,
309+ listeners: old_listeners,
310+ attributes: old_attributes,
311+ children: old_children,
312+ }),
313+ ) => {
314+ debug!(" updating an element");
315+ if new_tag_name != old_tag_name {
316+ debug!(" different tag names; creating new element and replacing old element");
317+ self.create(registry, new);
318+ self.change_list.emit_replace_with();
319+ return;
320+ }
321+ self.diff_listeners(registry, old_listeners, new_listeners);
322+ self.diff_attributes(old_attributes, new_attributes);
323+ self.diff_children(registry, old_children, new_children);
324+ }
325+ }
326+ }
327+
328+ fn diff_listeners<'a>(
329+ &mut self,
330+ registry: &mut EventsRegistry,
331+ old: &'a [Listener<'a>],
332+ new: &'a [Listener<'a>],
333+ ) {
334+ debug!(" updating event listeners");
335+
336+ 'outer1: for new_l in new {
337+ unsafe {
338+ // Safety relies on removing `new_l` from the registry manually
339+ // at the end of its lifetime. This happens when we invoke
340+ // `clear_active_listeners` at the start of a new rendering
341+ // phase.
342+ registry.add(new_l);
343+ }
344+ for old_l in old {
345+ if new_l.event == old_l.event {
346+ self.change_list.emit_update_event_listener(new_l);
347+ continue 'outer1;
348+ }
349+ }
350+ self.change_list.emit_new_event_listener(new_l);
351+ }
352+
353+ 'outer2: for old_l in old {
354+ for new_l in new {
355+ if new_l.event == old_l.event {
356+ continue 'outer2;
357+ }
358+ }
359+ self.change_list.emit_remove_event_listener(old_l.event);
360+ }
361+ }
362+
363+ fn diff_attributes(&mut self, old: &[Attribute], new: &[Attribute]) {
364+ debug!(" updating attributes");
365+
366+ // Do O(n^2) passes to add/update and remove attributes, since
367+ // there are almost always very few attributes.
368+ 'outer: for new_attr in new {
369+ if new_attr.is_volatile() {
370+ self.change_list
371+ .emit_set_attribute(new_attr.name, new_attr.value);
372+ } else {
373+ for old_attr in old {
374+ if old_attr.name == new_attr.name {
375+ if old_attr.value != new_attr.value {
376+ self.change_list
377+ .emit_set_attribute(new_attr.name, new_attr.value);
378+ }
379+ continue 'outer;
380+ }
381+ }
382+ self.change_list
383+ .emit_set_attribute(new_attr.name, new_attr.value);
384+ }
385+ }
386+
387+ 'outer2: for old_attr in old {
388+ for new_attr in new {
389+ if old_attr.name == new_attr.name {
390+ continue 'outer2;
391+ }
392+ }
393+ self.change_list.emit_remove_attribute(old_attr.name);
394+ }
395+ }
396+
397+ fn diff_children<'a>(
398+ &mut self,
399+ registry: &mut EventsRegistry,
400+ old: &'a [Node<'a>],
401+ new: &'a [Node<'a>],
402+ ) {
403+ debug!(" updating children shared by old and new");
404+
405+ let num_children_to_diff = cmp::min(new.len(), old.len());
406+ let mut new_children = new.iter();
407+ let mut old_children = old.iter();
408+ let mut pushed = false;
409+
410+ for (i, (new_child, old_child)) in new_children
411+ .by_ref()
412+ .zip(old_children.by_ref())
413+ .take(num_children_to_diff)
414+ .enumerate()
415+ {
416+ if i == 0 {
417+ self.change_list.emit_push_first_child();
418+ pushed = true;
419+ } else {
420+ debug_assert!(pushed);
421+ self.change_list.emit_pop_push_next_sibling();
422+ }
423+
424+ self.diff(registry, old_child.clone(), new_child.clone());
425+ }
426+
427+ if old_children.next().is_some() {
428+ debug!(" removing extra old children");
429+ debug_assert!(new_children.next().is_none());
430+ if !pushed {
431+ self.change_list.emit_push_first_child();
432+ } else {
433+ self.change_list.emit_pop_push_next_sibling();
434+ }
435+ self.change_list.emit_remove_self_and_next_siblings();
436+ pushed = false;
437+ } else {
438+ debug!(" creating new children");
439+ for (i, new_child) in new_children.enumerate() {
440+ if i == 0 && pushed {
441+ self.change_list.emit_pop();
442+ pushed = false;
443+ }
444+ self.create(registry, new_child.clone());
445+ self.change_list.emit_append_child();
446+ }
447+ }
448+
449+ debug!(" done updating children");
450+ if pushed {
451+ self.change_list.emit_pop();
452+ }
453+ }
454+
455+ fn create<'a>(&mut self, registry: &mut EventsRegistry, node: Node<'a>) {
456+ match node {
457+ Node::Text(TextNode { text }) => {
458+ self.change_list.emit_create_text_node(text);
459+ }
460+ Node::Element(ElementNode {
461+ tag_name,
462+ listeners,
463+ attributes,
464+ children,
465+ }) => {
466+ self.change_list.emit_create_element(tag_name);
467+ for l in listeners {
468+ unsafe {
469+ registry.add(l);
470+ }
471+ self.change_list.emit_new_event_listener(l);
472+ }
473+ for attr in attributes {
474+ self.change_list.emit_set_attribute(&attr.name, &attr.value);
475+ }
476+ for child in children {
477+ self.create(registry, child.clone());
478+ self.change_list.emit_append_child();
479+ }
480+ }
481+ }
482+ }
327}
483}
328
484
329−fn request_animation_frame(f: &Closure<dyn FnMut()>) {
485+fn request_animation_frame(f: &Closure<FnMut()>) {
330 web_sys::window()
486 web_sys::window()
331 .expect_throw("should have a window")
487 .expect_throw("should have a window")
332 .request_animation_frame(f.as_ref().unchecked_ref())
488 .request_animation_frame(f.as_ref().unchecked_ref())
@@ -343,7 +499,7 @@ where
343 let f = Closure::wrap(Box::new(move || {
499 let f = Closure::wrap(Box::new(move || {
344 *g.borrow_mut() = None;
500 *g.borrow_mut() = None;
345 f();
501 f();
346− }) as Box<dyn FnMut()>);
502+ }) as Box<FnMut()>);
347 request_animation_frame(&f);
503 request_animation_frame(&f);
348
504
349 *h.borrow_mut() = Some(f);
505 *h.borrow_mut() = Some(f);
@@ -374,23 +530,25 @@ impl VdomWeak {
374 /// Replace the root rendering component with the new `root`.
530 /// Replace the root rendering component with the new `root`.
375 ///
531 ///
376 /// Returns a future that resolves to the *old* root component.
532 /// Returns a future that resolves to the *old* root component.
377− pub async fn set_component(
533+ pub fn set_component(
378 self,
534 self,
379 root: Box<dyn RootRender>,
535 root: Box<dyn RootRender>,
380− ) -> Result<Box<dyn RootRender + 'static>, VdomDroppedError> {
536+ ) -> impl Future<Item = Box<dyn RootRender + 'static>, Error = VdomDroppedError> {
537+ futures::future::ok(self.inner.upgrade())
538+ .and_then(|inner| inner.ok_or(()))
381− let inner = self.inner.upgrade().ok_or(VdomDroppedError {})?;
539+ .map_err(|_| VdomDroppedError {})
382−
540+ .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;
541+ let promise = js_sys::Promise::resolve(&JsValue::null());
385−
542+ JsFuture::from(promise)
543+ .map_err(|_| VdomDroppedError {})
544+ .and_then(move |_| {
386− let old = {
545+ let old = {
387− let mut exclusive = inner.exclusive.borrow_mut();
546+ let mut exclusive = inner.exclusive.borrow_mut();
388− mem::replace(&mut *exclusive.component.as_mut().unwrap_throw(), root)
547+ mem::replace(&mut *exclusive.component.as_mut().unwrap_throw(), root)
389− };
548+ };
390−
391− VdomWeak::new(&inner).render().await?;
549+ VdomWeak::new(&inner).render().map(|_| old)
392−
550+ })
393− Ok(old)
551+ })
394 }
552 }
395
553
396 /// Execute `f` with a reference to this virtual DOM's root rendering
554 /// Execute `f` with a reference to this virtual DOM's root rendering
@@ -398,18 +556,24 @@ impl VdomWeak {
398 ///
556 ///
399 /// To ensure exclusive access to the root rendering component, the
557 /// To ensure exclusive access to the root rendering component, the
400 /// invocation takes place on a new tick of the micro-task queue.
558 /// 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>
559+ pub fn with_component<F, T>(&self, f: F) -> impl Future<Item = T, Error = VdomDroppedError>
402 where
560 where
403 F: 'static + FnOnce(&mut dyn RootRender) -> T,
561 F: 'static + FnOnce(&mut dyn RootRender) -> T,
404 {
562 {
563+ futures::future::ok(self.inner.upgrade())
564+ .and_then(|inner| inner.ok_or(()))
405− let inner = self.inner.upgrade().ok_or(VdomDroppedError {})?;
565+ .map_err(|_| VdomDroppedError {})
406−
566+ .and_then(|inner| {
407− // Wait for a new tick of the micro-task queue
567+ let mut f = Some(f);
408− let _ = JsFuture::from(js_sys::Promise::resolve(&JsValue::null())).await;
568+ let promise = js_sys::Promise::resolve(&JsValue::null());
409−
569+ JsFuture::from(promise)
570+ .map_err(|_| VdomDroppedError {})
571+ .map(move |_| {
572+ let f = f.take().unwrap_throw();
410− let mut exclusive = inner.exclusive.borrow_mut();
573+ let mut exclusive = inner.exclusive.borrow_mut();
411−
412− Ok(f(exclusive.component_raw_mut()))
574+ f(exclusive.component_raw_mut())
575+ })
576+ })
413 }
577 }
414
578
415 /// Schedule a render to occur during the next animation frame.
579 /// Schedule a render to occur during the next animation frame.
@@ -418,12 +582,7 @@ impl VdomWeak {
418 /// `render` instead.
582 /// `render` instead.
419 pub fn schedule_render(&self) {
583 pub fn schedule_render(&self) {
420 debug!("VdomWeak::schedule_render");
584 debug!("VdomWeak::schedule_render");
421−
422− let future = self.render();
423−
424− wasm_bindgen_futures::spawn_local(async move {
585+ wasm_bindgen_futures::spawn_local(self.render().map_err(|_| ()));
425− let _ = future.await;
426− });
427 }
586 }
428
587
429 /// Schedule a render to occur during the next animation frame and return a
588 /// Schedule a render to occur during the next animation frame and return a
@@ -431,46 +590,43 @@ impl VdomWeak {
431 ///
590 ///
432 /// If you don't want to do more things after the render completes, then use
591 /// If you don't want to do more things after the render completes, then use
433 /// `schedule_render` instead of `render`.
592 /// `schedule_render` instead of `render`.
434− pub fn render(&self) -> impl Future<Output = Result<(), VdomDroppedError>> {
593+ pub fn render(&self) -> impl Future<Item = (), Error = VdomDroppedError> {
594+ debug!("VdomWeak::render: initiating render in new animation frame");
435− let inner = self.inner.upgrade();
595+ futures::future::ok(self.inner.upgrade())
436−
596+ .and_then(|inner| inner.ok_or(()))
437− async move {
438− let inner = inner.ok_or(VdomDroppedError {})?;
597+ .map_err(|_| VdomDroppedError {})
439−
598+ .and_then(|inner| {
440− let promise = inner.shared.render_scheduled.take().unwrap_or_else(|| {
599+ let promise = inner.shared.render_scheduled.take().unwrap_or_else(|| {
441− js_sys::Promise::new(&mut |resolve, reject| {
600+ js_sys::Promise::new(&mut |resolve, reject| {
442− let vdom = VdomWeak {
601+ let vdom = VdomWeak {
443− inner: Rc::downgrade(&inner),
602+ inner: Rc::downgrade(&inner),
444− };
603+ };
445−
446− with_animation_frame(move || match vdom.inner.upgrade() {
604+ with_animation_frame(move || match vdom.inner.upgrade() {
447− None => {
605+ None => {
448− warn!("VdomWeak::render: vdom unmounted before we could render");
606+ warn!("VdomWeak::render: vdom unmounted before we could render");
449− let r = reject.call0(&JsValue::null());
607+ let r = reject.call0(&JsValue::null());
450− debug_assert!(r.is_ok());
608+ debug_assert!(r.is_ok());
451− }
609+ }
452− Some(inner) => {
610+ Some(inner) => {
453− let mut exclusive = inner.exclusive.borrow_mut();
611+ let mut exclusive = inner.exclusive.borrow_mut();
454− exclusive.render();
612+ exclusive.render();
455−
613+
456− // We did the render, so take the promise away
614+ // We did the render, so take the promise away
457− // and let future `render` calls request new
615+ // and let future `render` calls request new
458− // animation frames.
616+ // animation frames.
459− let _ = inner.shared.render_scheduled.take();
617+ let _ = inner.shared.render_scheduled.take();
460−
618+
619+ debug!("VdomWeak::render: finished rendering");
461− let r = resolve.call0(&JsValue::null());
620+ let r = resolve.call0(&JsValue::null());
462− debug_assert!(r.is_ok());
621+ debug_assert!(r.is_ok());
463− }
622+ }
464− });
623+ });
465− })
624+ })
466− });
625+ });
467−
468− inner.shared.render_scheduled.set(Some(promise.clone()));
626+ inner.shared.render_scheduled.set(Some(promise.clone()));
469−
470− JsFuture::from(promise)
627+ JsFuture::from(promise)
471− .await
472− .map(drop)
628+ .map(|_| ())
473− .map_err(|_| VdomDroppedError {})
629+ .map_err(|_| VdomDroppedError {})
474− }
630+ })
475 }
631 }
476}
632}
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+36−59
@@ -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,13 +168,12 @@ 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 declaring a bunch of `assert_before_after` tests.
@@ -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}