on master help sign in
52 files changed+3636−1247
since splitsnapshots
unifiedsplit
.travis.yml+18−12 Cargo.lock+505−499 Cargo.toml+25−15 README.md+9 benches/benches.rs+13−24 crates/js-api/Cargo.toml+6−7 crates/js-api/src/lib.rs+13−10 examples/counter/Cargo.toml+5−5 examples/counter/src/lib.rs+5−4 examples/game-of-life/Cargo.toml+5−6 examples/game-of-life/index.html−3 examples/game-of-life/src/lib.rs+18−13 examples/hello-world/Cargo.toml+3−3 examples/hello-world/src/lib.rs+2−2 examples/input-form/Cargo.toml+3−3 examples/input-form/src/lib.rs+2−2 examples/js-component/Cargo.toml+3−3 examples/js-component/src/lib.rs+3−3 examples/moire/Cargo.toml+8−9 examples/moire/src/lib.rs+6−7 examples/sierpinski-triangle/Cargo.toml+5−6 examples/sierpinski-triangle/README.md+1−1 examples/sierpinski-triangle/src/lib.rs+5−6 examples/todomvc/Cargo.toml+11−12 examples/todomvc/src/router.rs+23−21 examples/todomvc/src/todo.rs+77−21 examples/todomvc/src/todos.rs+5−6 js/change-list-interpreter.js+104−12 js/strace.js+80 src/builder.rs+64−5 src/cached.rs+91−22 src/cached_set.rs+79−52 src/change_list/emitter.rs+93−5 src/change_list/mod.rs+195−25 src/change_list/strings.rs+4−3 src/change_list/traversal.rs+344 src/diff.rs+894−76 src/events.rs+9−18 src/lib.rs+5−4 src/node.rs+83−39 src/render.rs+43−16 src/render_context.rs+43−2 src/strace.rs+22 src/vdom.rs+96−85 tests/web/cached.js+11 tests/web/cached.rs+103−48 tests/web/events.rs+56−73 tests/web/js_api.rs+30−37 tests/web/keyed.js+25 tests/web/keyed.rs+269 tests/web/main.rs+25−20 tests/web/render.rs+89−2
.travis.yml+18−12
@@ -25,24 +25,30 @@ matrix:
2525 - name: "build (beta)"
2626 rust: beta
2727 script: INSTALL=1 ./build.sh
28− - name: "build (nightly)"
29− rust: nightly
30− script: INSTALL=1 ./build.sh
3128 - name: "headless browser tests (firefox)"
3229 rust: stable
3330 addons:
3431 firefox: latest
3532 script: wasm-pack test --firefox --headless -- --features xxx-unstable-internal-use-only
36− - name: "headless browser tests (chrome)"
37− rust: stable
38− addons:
39− chrome: stable
40− script: wasm-pack test --chrome --headless -- --features xxx-unstable-internal-use-only
4133 - name: "native tests"
4234 rust: stable
4335 install: echo "no install"
4436 script: cargo test --all
45− - name: "check native benches"
46− rust: nightly
47− install: echo "no install"
48− script: cargo check --benches --features xxx-unstable-internal-use-only
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+505−499
@@ -1,104 +1,71 @@
11# This file is automatically @generated by Cargo.
22# It is not intended for manual editing.
33[[package]]
4−name = "aho-corasick"
5−version = "0.7.3"
4+name = "anyhow"
5+version = "1.0.25"
66source = "registry+https://github.com/rust-lang/crates.io-index"
7−dependencies = [
8− "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
9−]
10−
11−[[package]]
12−name = "arrayvec"
13−version = "0.4.10"
14−source = "registry+https://github.com/rust-lang/crates.io-index"
15−dependencies = [
16− "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
17−]
187
198[[package]]
209name = "atty"
21−version = "0.2.11"
10+version = "0.2.13"
2211source = "registry+https://github.com/rust-lang/crates.io-index"
2312dependencies = [
24− "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
25− "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
26− "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
13+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
14+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
2715]
2816
2917[[package]]
3018name = "autocfg"
31−version = "0.1.2"
19+version = "0.1.7"
3220source = "registry+https://github.com/rust-lang/crates.io-index"
3321
3422[[package]]
35−name = "backtrace"
36−version = "0.3.15"
23+name = "bitflags"
24+version = "1.2.1"
3725source = "registry+https://github.com/rust-lang/crates.io-index"
38−dependencies = [
39− "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
40− "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
41− "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
42− "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
43− "rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
44− "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
45−]
4626
4727[[package]]
48−name = "backtrace-sys"
49−version = "0.1.28"
28+name = "bstr"
29+version = "0.2.8"
5030source = "registry+https://github.com/rust-lang/crates.io-index"
5131dependencies = [
52− "cc 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)",
53− "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
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)",
5436]
5537
56−[[package]]
57−name = "bitflags"
58−version = "1.0.4"
59−source = "registry+https://github.com/rust-lang/crates.io-index"
60−
6138[[package]]
6239name = "bumpalo"
63−version = "2.3.0"
40+version = "2.6.0"
6441source = "registry+https://github.com/rust-lang/crates.io-index"
6542
6643[[package]]
6744name = "byteorder"
68−version = "1.3.1"
45+version = "1.3.2"
6946source = "registry+https://github.com/rust-lang/crates.io-index"
7047
7148[[package]]
7249name = "cast"
73−version = "0.2.2"
74−source = "registry+https://github.com/rust-lang/crates.io-index"
75−
76−[[package]]
77−name = "cc"
78−version = "1.0.34"
50+version = "0.2.3"
7951source = "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+]
8055
8156[[package]]
8257name = "cfg-if"
83−version = "0.1.7"
58+version = "0.1.10"
8459source = "registry+https://github.com/rust-lang/crates.io-index"
8560
8661[[package]]
8762name = "clap"
88−version = "2.32.0"
63+version = "2.33.0"
8964source = "registry+https://github.com/rust-lang/crates.io-index"
9065dependencies = [
91− "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
92− "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
93− "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
94−]
95−
96−[[package]]
97−name = "cloudabi"
98−version = "0.0.3"
99−source = "registry+https://github.com/rust-lang/crates.io-index"
100−dependencies = [
101− "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
10269]
10370
10471[[package]]
@@ -106,118 +73,132 @@ name = "console_error_panic_hook"
10673version = "0.1.6"
10774source = "registry+https://github.com/rust-lang/crates.io-index"
10875dependencies = [
109− "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
110− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
76+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
77+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
11178]
11279
11380[[package]]
11481name = "console_log"
115−version = "0.1.2"
82+version = "0.2.0"
11683source = "registry+https://github.com/rust-lang/crates.io-index"
11784dependencies = [
118− "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
119− "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
12087]
12188
12289[[package]]
12390name = "criterion"
124−version = "0.2.10"
91+version = "0.3.3"
12592source = "registry+https://github.com/rust-lang/crates.io-index"
12693dependencies = [
127− "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
128− "cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
129− "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
130− "criterion-plot 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
131− "csv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
132− "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
133− "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
134− "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
135− "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
136− "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
137− "rand_xoshiro 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
138− "rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
139− "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
140− "serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
141− "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
142− "tinytemplate 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
143− "walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
144112]
145113
146114[[package]]
147115name = "criterion-plot"
148−version = "0.3.0"
116+version = "0.4.3"
149117source = "registry+https://github.com/rust-lang/crates.io-index"
150118dependencies = [
151− "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
152− "cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
153− "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
154121]
155122
156123[[package]]
157124name = "crossbeam-deque"
158−version = "0.2.0"
125+version = "0.7.2"
159126source = "registry+https://github.com/rust-lang/crates.io-index"
160127dependencies = [
161− "crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
162− "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
163130]
164131
165132[[package]]
166133name = "crossbeam-epoch"
167−version = "0.3.1"
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"
168148source = "registry+https://github.com/rust-lang/crates.io-index"
169149dependencies = [
170− "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
171− "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
172− "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
173− "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
174− "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
175− "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
176− "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
150+ "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
177151]
178152
179153[[package]]
180154name = "crossbeam-utils"
181−version = "0.2.2"
155+version = "0.7.0"
182156source = "registry+https://github.com/rust-lang/crates.io-index"
183157dependencies = [
184− "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
185161]
186162
187163[[package]]
188164name = "csv"
189−version = "1.0.5"
165+version = "1.1.1"
190166source = "registry+https://github.com/rust-lang/crates.io-index"
191167dependencies = [
192− "csv-core 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
193− "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
194173]
195174
196175[[package]]
197176name = "csv-core"
198−version = "0.1.5"
177+version = "0.1.6"
199178source = "registry+https://github.com/rust-lang/crates.io-index"
200179dependencies = [
201− "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
180+ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
202181]
203182
204183[[package]]
205184name = "dodrio"
206−version = "0.1.0"
185+version = "0.2.0"
207186dependencies = [
208− "bumpalo 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
209− "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
210− "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
211− "criterion 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
212− "dodrio-js-api 0.1.0",
213− "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
187+ "bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
188+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
189+ "console_log 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
190+ "criterion 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
191+ "dodrio-js-api 0.2.0",
192+ "futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
214193 "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
215− "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
216− "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
217− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
218− "wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
219− "wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
220− "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
194+ "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
195+ "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
196+ "longest-increasing-subsequence 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
197+ "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
198+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
199+ "wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
200+ "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
201+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
221202]
222203
223204[[package]]
@@ -225,12 +206,12 @@ name = "dodrio-counter"
225206version = "0.1.0"
226207dependencies = [
227208 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
228− "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
229− "dodrio 0.1.0",
230− "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
231− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
232− "wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
233− "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
209+ "console_log 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
210+ "dodrio 0.2.0",
211+ "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
212+ "wasm-bindgen 0.2.55 (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)",
214+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
234215]
235216
236217[[package]]
@@ -238,13 +219,12 @@ name = "dodrio-game-of-life"
238219version = "0.1.0"
239220dependencies = [
240221 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
241− "dodrio 0.1.0",
242− "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
243− "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
244− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
245− "wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
246− "wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
247− "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
222+ "dodrio 0.2.0",
223+ "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
224+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
225+ "wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
226+ "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
227+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
248228]
249229
250230[[package]]
@@ -252,10 +232,10 @@ name = "dodrio-hello-world"
252232version = "0.1.0"
253233dependencies = [
254234 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
255− "dodrio 0.1.0",
256− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
257− "wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
258− "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
235+ "dodrio 0.2.0",
236+ "wasm-bindgen 0.2.55 (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)",
238+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
259239]
260240
261241[[package]]
@@ -263,22 +243,21 @@ name = "dodrio-input-form"
263243version = "0.1.0"
264244dependencies = [
265245 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
266− "dodrio 0.1.0",
267− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
268− "wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
269− "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
246+ "dodrio 0.2.0",
247+ "wasm-bindgen 0.2.55 (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)",
249+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
270250]
271251
272252[[package]]
273253name = "dodrio-js-api"
274−version = "0.1.0"
254+version = "0.2.0"
275255dependencies = [
276− "dodrio 0.1.0",
277− "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
278− "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
279− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
280− "wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
281− "wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
256+ "dodrio 0.2.0",
257+ "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
258+ "wasm-bindgen 0.2.55 (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)",
260+ "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
282261]
283262
284263[[package]]
@@ -286,28 +265,27 @@ name = "dodrio-js-component"
286265version = "0.1.0"
287266dependencies = [
288267 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
289− "dodrio 0.1.0",
290− "dodrio-js-api 0.1.0",
291− "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
292− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
293− "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
268+ "dodrio 0.2.0",
269+ "dodrio-js-api 0.2.0",
270+ "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
271+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
272+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
294273]
295274
296275[[package]]
297276name = "dodrio-moire"
298277version = "0.1.0"
299278dependencies = [
300− "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
279+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
301280 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
302− "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
303− "dodrio 0.1.0",
304− "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
305− "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
306− "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
307− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
308− "wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
309− "wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
310− "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
281+ "console_log 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
282+ "dodrio 0.2.0",
283+ "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
284+ "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
285+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
286+ "wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
287+ "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
288+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
311289]
312290
313291[[package]]
@@ -315,315 +293,372 @@ name = "dodrio-sierpinski"
315293version = "0.1.0"
316294dependencies = [
317295 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
318− "dodrio 0.1.0",
319− "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
320− "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
321− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
322− "wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
323− "wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
324− "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
296+ "dodrio 0.2.0",
297+ "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
298+ "wasm-bindgen 0.2.55 (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)",
300+ "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
301+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
325302]
326303
327304[[package]]
328305name = "dodrio-todomvc"
329306version = "0.1.0"
330307dependencies = [
331− "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
308+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
332309 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
333− "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
334− "dodrio 0.1.0",
335− "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
336− "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
337− "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
338− "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
339− "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
340− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
341− "wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
342− "wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
343− "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
310+ "console_log 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
311+ "dodrio 0.2.0",
312+ "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
313+ "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
314+ "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
315+ "serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)",
316+ "wasm-bindgen 0.2.55 (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)",
318+ "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
319+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
344320]
345321
346322[[package]]
347323name = "either"
348−version = "1.5.2"
324+version = "1.5.3"
349325source = "registry+https://github.com/rust-lang/crates.io-index"
350326
351327[[package]]
352−name = "env_logger"
353−version = "0.6.1"
328+name = "futures"
329+version = "0.3.5"
354330source = "registry+https://github.com/rust-lang/crates.io-index"
355331dependencies = [
356− "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
357− "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
358− "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
359− "regex 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
360− "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
361339]
362340
363341[[package]]
364−name = "failure"
365−version = "0.1.5"
342+name = "futures-channel"
343+version = "0.3.5"
366344source = "registry+https://github.com/rust-lang/crates.io-index"
367345dependencies = [
368− "backtrace 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
369− "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
370348]
371349
372350[[package]]
373−name = "failure_derive"
374−version = "0.1.5"
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"
375358source = "registry+https://github.com/rust-lang/crates.io-index"
376359dependencies = [
377− "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
378− "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
379− "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)",
380− "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
360+ "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
361+ "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
362+ "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
381363]
382364
383365[[package]]
384−name = "fuchsia-cprng"
385−version = "0.1.1"
366+name = "futures-io"
367+version = "0.3.5"
386368source = "registry+https://github.com/rust-lang/crates.io-index"
387369
388370[[package]]
389−name = "futures"
390−version = "0.1.26"
371+name = "futures-macro"
372+version = "0.3.5"
391373source = "registry+https://github.com/rust-lang/crates.io-index"
374+dependencies = [
375+ "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
376+ "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
377+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
378+ "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
379+]
380+
381+[[package]]
382+name = "futures-sink"
383+version = "0.3.5"
384+source = "registry+https://github.com/rust-lang/crates.io-index"
385+
386+[[package]]
387+name = "futures-task"
388+version = "0.3.5"
389+source = "registry+https://github.com/rust-lang/crates.io-index"
390+dependencies = [
391+ "once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
392+]
393+
394+[[package]]
395+name = "futures-util"
396+version = "0.3.5"
397+source = "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+]
392412
393413[[package]]
394414name = "fxhash"
395415version = "0.2.1"
396416source = "registry+https://github.com/rust-lang/crates.io-index"
397417dependencies = [
398− "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
418+ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
399419]
400420
421+[[package]]
422+name = "half"
423+version = "1.6.0"
424+source = "registry+https://github.com/rust-lang/crates.io-index"
425+
401426[[package]]
402427name = "heck"
403428version = "0.3.1"
404429source = "registry+https://github.com/rust-lang/crates.io-index"
405430dependencies = [
406− "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
431+ "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
407432]
408433
409434[[package]]
410−name = "humantime"
411−version = "1.2.0"
435+name = "hermit-abi"
436+version = "0.1.3"
412437source = "registry+https://github.com/rust-lang/crates.io-index"
413438dependencies = [
414− "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
439+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
415440]
416441
417442[[package]]
418443name = "itertools"
419−version = "0.8.0"
444+version = "0.9.0"
420445source = "registry+https://github.com/rust-lang/crates.io-index"
421446dependencies = [
422− "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
447+ "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
423448]
424449
425450[[package]]
426451name = "itoa"
427−version = "0.4.3"
452+version = "0.4.4"
428453source = "registry+https://github.com/rust-lang/crates.io-index"
429454
430455[[package]]
431456name = "js-sys"
432−version = "0.3.17"
457+version = "0.3.32"
433458source = "registry+https://github.com/rust-lang/crates.io-index"
434459dependencies = [
435− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
460+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
436461]
437462
438463[[package]]
439464name = "lazy_static"
440−version = "1.3.0"
465+version = "1.4.0"
441466source = "registry+https://github.com/rust-lang/crates.io-index"
442467
443468[[package]]
444469name = "libc"
445−version = "0.2.51"
470+version = "0.2.66"
446471source = "registry+https://github.com/rust-lang/crates.io-index"
447472
448473[[package]]
449474name = "log"
450−version = "0.4.6"
475+version = "0.4.11"
451476source = "registry+https://github.com/rust-lang/crates.io-index"
452477dependencies = [
453− "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
478+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
454479]
455480
481+[[package]]
482+name = "longest-increasing-subsequence"
483+version = "0.1.0"
484+source = "registry+https://github.com/rust-lang/crates.io-index"
485+
456486[[package]]
457487name = "memchr"
458−version = "2.2.0"
488+version = "2.2.1"
459489source = "registry+https://github.com/rust-lang/crates.io-index"
460490dependencies = [
461− "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
491+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
462492]
463493
464494[[package]]
465495name = "memoffset"
466−version = "0.2.1"
467−source = "registry+https://github.com/rust-lang/crates.io-index"
468−
469−[[package]]
470−name = "nodrop"
471−version = "0.1.13"
496+version = "0.5.3"
472497source = "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+]
473501
474502[[package]]
475503name = "nom"
476504version = "4.2.3"
477505source = "registry+https://github.com/rust-lang/crates.io-index"
478506dependencies = [
479− "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
507+ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
480508 "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
481509]
482510
483511[[package]]
484512name = "num-traits"
485−version = "0.2.6"
513+version = "0.2.10"
486514source = "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+]
487518
488519[[package]]
489520name = "num_cpus"
490−version = "1.10.0"
521+version = "1.11.1"
491522source = "registry+https://github.com/rust-lang/crates.io-index"
492523dependencies = [
493− "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
494526]
495527
496528[[package]]
497−name = "proc-macro2"
498−version = "0.4.27"
529+name = "once_cell"
530+version = "1.3.1"
499531source = "registry+https://github.com/rust-lang/crates.io-index"
500−dependencies = [
501− "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
502−]
503532
504533[[package]]
505−name = "quick-error"
506−version = "1.2.2"
534+name = "oorandom"
535+version = "11.1.0"
507536source = "registry+https://github.com/rust-lang/crates.io-index"
508537
509538[[package]]
510−name = "quote"
511−version = "0.6.11"
539+name = "pin-project"
540+version = "0.4.16"
512541source = "registry+https://github.com/rust-lang/crates.io-index"
513542dependencies = [
514− "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
543+ "pin-project-internal 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)",
515544]
516545
517546[[package]]
518−name = "rand_core"
519−version = "0.3.1"
547+name = "pin-project-internal"
548+version = "0.4.16"
520549source = "registry+https://github.com/rust-lang/crates.io-index"
521550dependencies = [
522− "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
523554]
524555
525556[[package]]
526−name = "rand_core"
527−version = "0.4.0"
557+name = "pin-utils"
558+version = "0.1.0"
528559source = "registry+https://github.com/rust-lang/crates.io-index"
529560
530561[[package]]
531−name = "rand_os"
532−version = "0.1.3"
562+name = "plotters"
563+version = "0.2.12"
533564source = "registry+https://github.com/rust-lang/crates.io-index"
534565dependencies = [
535− "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
536− "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
537− "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
538− "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
539− "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
540− "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
541570]
542571
543572[[package]]
544−name = "rand_xoshiro"
545−version = "0.1.0"
573+name = "proc-macro-hack"
574+version = "0.5.11"
546575source = "registry+https://github.com/rust-lang/crates.io-index"
547576dependencies = [
548− "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
549− "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
550580]
551581
552582[[package]]
553−name = "rayon"
554−version = "1.0.3"
583+name = "proc-macro-nested"
584+version = "0.1.3"
555585source = "registry+https://github.com/rust-lang/crates.io-index"
556−dependencies = [
557− "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
558− "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
559− "rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
560−]
561586
562587[[package]]
563−name = "rayon-core"
564−version = "1.4.1"
588+name = "proc-macro2"
589+version = "1.0.18"
565590source = "registry+https://github.com/rust-lang/crates.io-index"
566591dependencies = [
567− "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
568− "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
569− "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
570− "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
592+ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
571593]
572594
573595[[package]]
574−name = "rdrand"
575−version = "0.4.0"
596+name = "quote"
597+version = "1.0.2"
576598source = "registry+https://github.com/rust-lang/crates.io-index"
577599dependencies = [
578− "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
600+ "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
579601]
580602
581603[[package]]
582−name = "redox_syscall"
583−version = "0.1.52"
604+name = "rayon"
605+version = "1.3.0"
584606source = "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+]
585612
586613[[package]]
587−name = "redox_termios"
588−version = "0.1.1"
614+name = "rayon-core"
615+version = "1.7.0"
589616source = "registry+https://github.com/rust-lang/crates.io-index"
590617dependencies = [
591− "redox_syscall 0.1.52 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
592623]
593624
594625[[package]]
595626name = "regex"
596−version = "1.1.5"
627+version = "1.3.3"
597628source = "registry+https://github.com/rust-lang/crates.io-index"
598629dependencies = [
599− "aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
600− "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
601− "regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
602− "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
603− "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
630+ "regex-syntax 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
604631]
605632
606633[[package]]
607−name = "regex-syntax"
608−version = "0.6.6"
634+name = "regex-automata"
635+version = "0.1.8"
609636source = "registry+https://github.com/rust-lang/crates.io-index"
610637dependencies = [
611− "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
638+ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
612639]
613640
614641[[package]]
615−name = "rustc-demangle"
616−version = "0.1.13"
642+name = "regex-syntax"
643+version = "0.6.13"
617644source = "registry+https://github.com/rust-lang/crates.io-index"
618645
646+[[package]]
647+name = "rustc_version"
648+version = "0.2.3"
649+source = "registry+https://github.com/rust-lang/crates.io-index"
650+dependencies = [
651+ "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
652+]
653+
619654[[package]]
620655name = "ryu"
621−version = "0.2.7"
656+version = "1.0.2"
622657source = "registry+https://github.com/rust-lang/crates.io-index"
623658
624659[[package]]
625660name = "same-file"
626−version = "1.0.4"
661+version = "1.0.5"
627662source = "registry+https://github.com/rust-lang/crates.io-index"
628663dependencies = [
629664 "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -636,129 +671,109 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
636671
637672[[package]]
638673name = "scopeguard"
639−version = "0.3.3"
674+version = "1.0.0"
640675source = "registry+https://github.com/rust-lang/crates.io-index"
641676
642677[[package]]
643−name = "serde"
644−version = "1.0.90"
678+name = "semver"
679+version = "0.9.0"
645680source = "registry+https://github.com/rust-lang/crates.io-index"
646681dependencies = [
647− "serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
682+ "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
648683]
649684
650685[[package]]
651−name = "serde_derive"
652−version = "1.0.90"
686+name = "semver-parser"
687+version = "0.7.0"
653688source = "registry+https://github.com/rust-lang/crates.io-index"
654−dependencies = [
655− "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
656− "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
657− "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)",
658−]
659689
660690[[package]]
661−name = "serde_json"
662−version = "1.0.39"
691+name = "serde"
692+version = "1.0.114"
663693source = "registry+https://github.com/rust-lang/crates.io-index"
664694dependencies = [
665− "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
666− "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
667− "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
695+ "serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
668696]
669697
670698[[package]]
671−name = "sourcefile"
672−version = "0.1.4"
699+name = "serde_cbor"
700+version = "0.11.1"
673701source = "registry+https://github.com/rust-lang/crates.io-index"
702+dependencies = [
703+ "half 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
704+ "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
705+]
674706
675707[[package]]
676−name = "syn"
677−version = "0.15.30"
708+name = "serde_derive"
709+version = "1.0.114"
678710source = "registry+https://github.com/rust-lang/crates.io-index"
679711dependencies = [
680− "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
681− "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
682− "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
683715]
684716
685717[[package]]
686−name = "synstructure"
687−version = "0.10.1"
718+name = "serde_json"
719+version = "1.0.57"
688720source = "registry+https://github.com/rust-lang/crates.io-index"
689721dependencies = [
690− "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
691− "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
692− "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)",
693− "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
722+ "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
723+ "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
724+ "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
694725]
695726
696727[[package]]
697−name = "termcolor"
698−version = "1.0.4"
728+name = "slab"
729+version = "0.4.2"
699730source = "registry+https://github.com/rust-lang/crates.io-index"
700−dependencies = [
701− "wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
702−]
703731
704732[[package]]
705−name = "termion"
706−version = "1.5.1"
733+name = "sourcefile"
734+version = "0.1.4"
707735source = "registry+https://github.com/rust-lang/crates.io-index"
708−dependencies = [
709− "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
710− "redox_syscall 0.1.52 (registry+https://github.com/rust-lang/crates.io-index)",
711− "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
712−]
713736
714737[[package]]
715−name = "textwrap"
716−version = "0.10.0"
738+name = "syn"
739+version = "1.0.33"
717740source = "registry+https://github.com/rust-lang/crates.io-index"
718741dependencies = [
719− "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
720745]
721746
722747[[package]]
723−name = "thread_local"
724−version = "0.3.6"
748+name = "textwrap"
749+version = "0.11.0"
725750source = "registry+https://github.com/rust-lang/crates.io-index"
726751dependencies = [
727− "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
752+ "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
728753]
729754
730755[[package]]
731756name = "tinytemplate"
732−version = "1.0.1"
757+version = "1.1.0"
733758source = "registry+https://github.com/rust-lang/crates.io-index"
734759dependencies = [
735− "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
736− "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
737762]
738763
739−[[package]]
740−name = "ucd-util"
741−version = "0.1.3"
742−source = "registry+https://github.com/rust-lang/crates.io-index"
743−
744764[[package]]
745765name = "unicode-segmentation"
746−version = "1.2.1"
766+version = "1.6.0"
747767source = "registry+https://github.com/rust-lang/crates.io-index"
748768
749769[[package]]
750770name = "unicode-width"
751−version = "0.1.5"
771+version = "0.1.6"
752772source = "registry+https://github.com/rust-lang/crates.io-index"
753773
754774[[package]]
755775name = "unicode-xid"
756−version = "0.1.0"
757−source = "registry+https://github.com/rust-lang/crates.io-index"
758−
759−[[package]]
760−name = "utf8-ranges"
761−version = "1.0.2"
776+version = "0.2.0"
762777source = "registry+https://github.com/rust-lang/crates.io-index"
763778
764779[[package]]
@@ -768,126 +783,126 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
768783
769784[[package]]
770785name = "walkdir"
771−version = "2.2.7"
786+version = "2.3.1"
772787source = "registry+https://github.com/rust-lang/crates.io-index"
773788dependencies = [
774− "same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
775− "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
776791 "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
777792]
778793
779794[[package]]
780795name = "wasm-bindgen"
781−version = "0.2.40"
796+version = "0.2.55"
782797source = "registry+https://github.com/rust-lang/crates.io-index"
783798dependencies = [
784− "wasm-bindgen-macro 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
785801]
786802
787803[[package]]
788804name = "wasm-bindgen-backend"
789−version = "0.2.40"
805+version = "0.2.55"
790806source = "registry+https://github.com/rust-lang/crates.io-index"
791807dependencies = [
792− "bumpalo 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
793− "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
794− "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
795− "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
796− "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
797− "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)",
798− "wasm-bindgen-shared 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
808+ "bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
809+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
810+ "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
811+ "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
812+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
813+ "syn 1.0.33 (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)",
799815]
800816
801817[[package]]
802818name = "wasm-bindgen-futures"
803−version = "0.3.17"
819+version = "0.4.5"
804820source = "registry+https://github.com/rust-lang/crates.io-index"
805821dependencies = [
806− "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
807− "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
808− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
822+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
823+ "js-sys 0.3.32 (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)",
809826]
810827
811828[[package]]
812829name = "wasm-bindgen-macro"
813−version = "0.2.40"
830+version = "0.2.55"
814831source = "registry+https://github.com/rust-lang/crates.io-index"
815832dependencies = [
816− "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
817− "wasm-bindgen-macro-support 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
833+ "quote 1.0.2 (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)",
818835]
819836
820837[[package]]
821838name = "wasm-bindgen-macro-support"
822−version = "0.2.40"
839+version = "0.2.55"
823840source = "registry+https://github.com/rust-lang/crates.io-index"
824841dependencies = [
825− "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
826− "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
827− "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)",
828− "wasm-bindgen-backend 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
829− "wasm-bindgen-shared 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
842+ "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
843+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
844+ "syn 1.0.33 (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)",
846+ "wasm-bindgen-shared 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
830847]
831848
832849[[package]]
833850name = "wasm-bindgen-shared"
834−version = "0.2.40"
851+version = "0.2.55"
835852source = "registry+https://github.com/rust-lang/crates.io-index"
836853
837854[[package]]
838855name = "wasm-bindgen-test"
839−version = "0.2.40"
856+version = "0.3.5"
840857source = "registry+https://github.com/rust-lang/crates.io-index"
841858dependencies = [
842859 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
843− "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
844− "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
860+ "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
845861 "scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
846− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
847− "wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
848− "wasm-bindgen-test-macro 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
862+ "wasm-bindgen 0.2.55 (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)",
864+ "wasm-bindgen-test-macro 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
849865]
850866
851867[[package]]
852868name = "wasm-bindgen-test-macro"
853−version = "0.2.40"
869+version = "0.3.5"
854870source = "registry+https://github.com/rust-lang/crates.io-index"
855871dependencies = [
856− "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
857− "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
872+ "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
873+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
858874]
859875
860876[[package]]
861877name = "wasm-bindgen-webidl"
862−version = "0.2.40"
878+version = "0.2.55"
863879source = "registry+https://github.com/rust-lang/crates.io-index"
864880dependencies = [
865− "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
881+ "anyhow 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
866882 "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
867− "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
868− "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
869− "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
870− "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)",
871− "wasm-bindgen-backend 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
872− "weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
883+ "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
884+ "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
885+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
886+ "syn 1.0.33 (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)",
888+ "weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
873889]
874890
875891[[package]]
876892name = "web-sys"
877−version = "0.3.17"
893+version = "0.3.32"
878894source = "registry+https://github.com/rust-lang/crates.io-index"
879895dependencies = [
880− "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
881− "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
882− "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
896+ "anyhow 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
897+ "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
883898 "sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
884− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
885− "wasm-bindgen-webidl 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
899+ "wasm-bindgen 0.2.55 (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)",
886901]
887902
888903[[package]]
889904name = "weedle"
890−version = "0.8.0"
905+version = "0.10.0"
891906source = "registry+https://github.com/rust-lang/crates.io-index"
892907dependencies = [
893908 "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -895,7 +910,7 @@ dependencies = [
895910
896911[[package]]
897912name = "winapi"
898−version = "0.3.7"
913+version = "0.3.8"
899914source = "registry+https://github.com/rust-lang/crates.io-index"
900915dependencies = [
901916 "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -912,7 +927,7 @@ name = "winapi-util"
912927version = "0.1.2"
913928source = "registry+https://github.com/rust-lang/crates.io-index"
914929dependencies = [
915− "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
930+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
916931]
917932
918933[[package]]
@@ -920,110 +935,101 @@ name = "winapi-x86_64-pc-windows-gnu"
920935version = "0.4.0"
921936source = "registry+https://github.com/rust-lang/crates.io-index"
922937
923−[[package]]
924−name = "wincolor"
925−version = "1.0.1"
926−source = "registry+https://github.com/rust-lang/crates.io-index"
927−dependencies = [
928− "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
929− "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
930−]
931−
932938[metadata]
933−"checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c"
934−"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71"
935−"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
936−"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799"
937−"checksum backtrace 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f106c02a3604afcdc0df5d36cc47b44b55917dbaf3d808f71c163a0ddba64637"
938−"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6"
939−"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
940−"checksum bumpalo 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f6a6bc2ba935b1e2f810787ceba8dfe86cbc164cee55925cae715541186673c4"
941−"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb"
942−"checksum cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "926013f2860c46252efceabb19f4a6b308197505082c609025aa6706c011d427"
943−"checksum cc 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)" = "30f813bf45048a18eda9190fd3c6b78644146056740c43172a5a3699118588fd"
944−"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4"
945−"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
946−"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
939+"checksum anyhow 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9267dff192e68f3399525901e709a48c1d3982c9c072fa32f2127a0cb0babf14"
940+"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
941+"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
942+"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
943+"checksum bstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8d6c2c5b58ab920a4f5aeaaca34b4488074e8cc7596af94e6f8c6ff247c60245"
944+"checksum bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad807f2fc2bf185eeb98ff3a901bd46dc5ad58163d0fa4577ba0d25674d71708"
945+"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
946+"checksum cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0"
947+"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
948+"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
947949"checksum console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211"
948−"checksum console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e7871d2947441b0fdd8e2bd1ce2a2f75304f896582c0d572162d48290683c48"
949−"checksum criterion 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "1c6e5ee5b9652d4f851418c448af105642e1f99e9a2741a8ff45c0d2c911b1e0"
950−"checksum criterion-plot 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4107e4a5abb94267e0149922b8ff49dc70a87cc202820fdbfc0d3e1edbdc4b16"
951−"checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3"
952−"checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150"
953−"checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9"
954−"checksum csv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9fd1c44c58078cfbeaf11fbb3eac9ae5534c23004ed770cc4bfb48e658ae4f04"
955−"checksum csv-core 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa5cdef62f37e6ffe7d1f07a381bc0db32b7a3ff1cac0de56cb0d81e71f53d65"
956−"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b"
957−"checksum env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b61fa891024a945da30a9581546e8cfaf5602c7b3f4c137a2805cf388f92075a"
958−"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2"
959−"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1"
960−"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
961−"checksum futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "62941eff9507c8177d448bd83a44d9b9760856e184081d8cd79ba9f03dd24981"
950+"checksum console_log 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "501a375961cef1a0d44767200e66e4a559283097e91d0730b1d75dfb2f8a1494"
951+"checksum criterion 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "70daa7ceec6cf143990669a04c7df13391d55fb27bd4079d252fca774ba244d8"
952+"checksum criterion-plot 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d"
953+"checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca"
954+"checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac"
955+"checksum crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dfd6515864a82d2f877b42813d4553292c6659498c9a2aa31bab5a15243c2700"
956+"checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4"
957+"checksum csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37519ccdfd73a75821cac9319d4fce15a81b9fcf75f951df5b9988aa3a0af87d"
958+"checksum csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9b5cadb6b25c77aeff80ba701712494213f4a8418fcda2ee11b6560c3ad0bf4c"
959+"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
960+"checksum futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613"
961+"checksum futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5"
962+"checksum futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399"
963+"checksum futures-executor 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314"
964+"checksum futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789"
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"
962969"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"
963971"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
964−"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114"
965−"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358"
966−"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b"
967−"checksum js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "d4bda84b98977341bb6ba1086ecbd9eab8bc929de0f2923c118baf76c21dd0c8"
968−"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
969−"checksum libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "bedcc7a809076656486ffe045abeeac163da1b558e963a31e29fbfbeba916917"
970−"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
971−"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39"
972−"checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3"
973−"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
972+"checksum hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120"
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"
975+"checksum js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)" = "1c840fdb2167497b0bd0db43d6dfe61e91637fa72f9d061f8bd17ddc44ba6414"
976+"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
977+"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
978+"checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
979+"checksum longest-increasing-subsequence 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3bd0dd2cd90571056fdb71f6275fada10131182f84899f4b2a916e565d81d86"
980+"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
981+"checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9"
974982"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
975−"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1"
976−"checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba"
977−"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915"
978−"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
979−"checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1"
980−"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
981−"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0"
982−"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
983−"checksum rand_xoshiro 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "03b418169fb9c46533f326efd6eed2576699c44ca92d3052a066214a8d828929"
984−"checksum rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "373814f27745b2686b350dd261bfd24576a6fb0e2c5919b3a2b6005f820b0473"
985−"checksum rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b055d1e92aba6877574d8fe604a63c8b5df60f60e5982bf7ccbb1338ea527356"
986−"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
987−"checksum redox_syscall 0.1.52 (registry+https://github.com/rust-lang/crates.io-index)" = "d32b3053e5ced86e4bc0411fec997389532bf56b000e66cb4884eeeb41413d69"
988−"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
989−"checksum regex 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "559008764a17de49a3146b234641644ed37d118d1ef641a0bb573d146edc6ce0"
990−"checksum regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dcfd8681eebe297b81d98498869d4aae052137651ad7b96822f09ceb690d0a96"
991−"checksum rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "adacaae16d02b6ec37fdc7acfcddf365978de76d1983d3ee22afc260e1ca9619"
992−"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7"
993−"checksum same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8f20c4be53a8a1ff4c1f1b2bd14570d2f634628709752f0702ecdd2b3f9a5267"
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"
995+"checksum rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098"
996+"checksum rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9"
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"
999+"checksum regex-syntax 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e734e891f5b408a29efbf8309e656876276f49ab6a6ac208600b4419bd893d90"
1000+"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
1001+"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
1002+"checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421"
9941003"checksum scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
995−"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27"
996−"checksum serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "aa5f7c20820475babd2c077c3ab5f8c77a31c15e16ea38687b4c02d3e48680f4"
997−"checksum serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "58fc82bec244f168b23d1963b45c8bf5726e9a15a9d146a067f9081aeed2de79"
998−"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d"
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"
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"
1010+"checksum serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)" = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c"
1011+"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
9991012"checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3"
1000−"checksum syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)" = "66c8865bf5a7cbb662d8b011950060b3c8743dca141b054bf7195b20d314d8e2"
1001−"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
1002−"checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f"
1003−"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
1004−"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6"
1005−"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
1006−"checksum tinytemplate 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7655088894274afb52b807bd3c87072daa1fedd155068b8705cabfd628956115"
1007−"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86"
1008−"checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1"
1009−"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
1010−"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
1011−"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737"
1013+"checksum syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)" = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd"
1014+"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
1015+"checksum tinytemplate 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d3dc76004a03cec1c5932bca4cdc2e39aaa798e3f82363dd94f9adf6098c12f"
1016+"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
1017+"checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20"
1018+"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
10121019"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
1013−"checksum walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d9d7ed3431229a144296213105a390676cc49c9b6a72bd19f3176c98e129fa1"
1014−"checksum wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "9742fc4860f47bede1090a5e4b0cfc33afcd70cfdf45dd28f2cfb02d4662b0dd"
1015−"checksum wasm-bindgen-backend 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c6d7f35ecbb4180513cdb9b298543321bcb278670730415cbb3205ff2c66a477"
1016−"checksum wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "67bfbec85e48e8915b6acfa1c4d90b770ce033c96dc2067688f3bccd00d3b9e1"
1017−"checksum wasm-bindgen-macro 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "e3c86b06bcd28e92e87d2c2ad208889b2f69ea33f79810b91ef660cc3de65a4c"
1018−"checksum wasm-bindgen-macro-support 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "81d7338dd8c67e193d8ef18e5802dc03d8710456baa792c1c2e66847e57fd389"
1019−"checksum wasm-bindgen-shared 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "0d57c3b66f2f3e4d96b50f49b7b7e2f4cfcddc88b15744433c98c5c105b26672"
1020−"checksum wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "85bbc3ad78a8d59a63a02d59345e8da9a3e0c1617f79e77fd1c3da6e047db63e"
1021−"checksum wasm-bindgen-test-macro 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "114dfdf01bd3659dd79db05480fbd2751759d1c79cd9edc22f2fe611c866e932"
1022−"checksum wasm-bindgen-webidl 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "fc7052a577eefce17a439edfce5c2eedc8432fe7457bbe949624cdf946233fa7"
1023−"checksum web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "72d02003924c77af871ad74216f828b6e32af776cbb3f5dab3dcde2f2713c012"
1024−"checksum weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "26a4c67f132386d965390b8a734d5d10adbcd30eb5cc74bd9229af8b83f10044"
1025−"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770"
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"
1022+"checksum wasm-bindgen-backend 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "1845584bd3593442dc0de6e6d9f84454a59a057722f36f005e44665d6ab19d85"
1023+"checksum wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1458706aa1b8fe6898d19433c9f110d93a05d1f22ae6adf55810409a94df34b4"
1024+"checksum wasm-bindgen-macro 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "87fcc747e6b73c93d22c947a6334644d22cfec5abd8b66238484dc2b0aeb9fe4"
1025+"checksum wasm-bindgen-macro-support 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "3dc4b3f2c4078c8c4a5f363b92fcf62604c5913cbd16c6ff5aaf0f74ec03f570"
1026+"checksum wasm-bindgen-shared 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "ca0b78d6d3be8589b95d1d49cdc0794728ca734adf36d7c9f07e6459508bb53d"
1027+"checksum wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ed5833682054e941db0b7e2b4877b61bbaafd680608e40d268e070d34558b305"
1028+"checksum wasm-bindgen-test-macro 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9c5dcf70d21d0ff0ea5cdb97dba9a9b56ddad4a9bd10c8912eedc55618be022f"
1029+"checksum wasm-bindgen-webidl 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "3126356474ceb717c8fb5549ae387c9fbf4872818454f4d87708bee997214bb5"
1030+"checksum web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)" = "98405c0a2e722ed3db341b4c5b70eb9fe0021621f7350bab76df93b09b649bbf"
1031+"checksum weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bb43f70885151e629e2a19ce9e50bd730fd436cfd4b666894c9ce4de9141164"
1032+"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
10261033"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
10271034"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
10281035"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1029−"checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba"
Cargo.toml+25−15
@@ -8,7 +8,7 @@ license = "MPL-2.0"
88name = "dodrio"
99readme = "./README.md"
1010repository = "https://github.com/fitzgen/dodrio"
11−version = "0.1.0"
11+version = "0.2.0"
1212
1313[badges]
1414travis-ci = { repository = "fitzgen/dodrio" }
@@ -29,22 +29,31 @@ harness = false
2929required-features = ["xxx-unstable-internal-use-only"]
3030
3131[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+
3240# Don't use this feature! It only exists for exposing otherwise-internal APIs
3341# for testing and benchmarks.
3442xxx-unstable-internal-use-only = []
3543
3644[dependencies]
37−bumpalo = "2.3.0"
38−cfg-if = "0.1.7"
39−futures = "0.1.26"
40−wasm-bindgen = "0.2.40"
41−wasm-bindgen-futures = "0.3.17"
42−js-sys = "0.3.17"
45+bumpalo = "2.6.0"
46+cfg-if = "0.1.10"
4347fxhash = "0.2.1"
44−log = { version = "0.4.6", optional = true }
48+js-sys = "0.3.32"
49+log = { version = "0.4.11", 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"
4554
4655[dependencies.web-sys]
47−version = "0.3.17"
56+version = "0.3.32"
4857features = [
4958 "console",
5059 "Document",
@@ -55,13 +64,14 @@ features = [
5564]
5665
5766[dev-dependencies]
58−console_log = "0.1.2"
59−dodrio-js-api = { version = "=0.1.0", path = "./crates/js-api" }
60−log = "0.4.6"
61−wasm-bindgen-test = "0.2.40"
67+console_log = "0.2.0"
68+dodrio-js-api = { version = "=0.2.0", path = "./crates/js-api" }
69+futures = "0.3.5"
70+log = "0.4.11"
71+wasm-bindgen-test = "0.3.5"
6272
6373[dev-dependencies.web-sys]
64−version = "0.3.17"
74+version = "0.3.32"
6575features = [
6676 "Attr",
6777 "EventTarget",
@@ -71,7 +81,7 @@ features = [
7181]
7282
7383[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
74−criterion = "0.2.10"
84+criterion = "0.3.3"
7585
7686[profile.bench]
7787debug = true
README.md+9
@@ -57,6 +57,15 @@ directory](https://github.com/fitzgen/dodrio/tree/master/examples), including:
5757* [`js-component`](https://github.com/fitzgen/dodrio/tree/master/examples/js-component):
5858 Defines a rendering component in JavaScript with the `dodrio-js-api` crate.
5959
60+## Cargo Features
61+
62+* `log` — 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` — enable `serde::{Serialize, Deserialize}` implementations for
67+ `Cached<R>` where `R` is serializable and deserializable.
68+
6069## Design
6170
6271### Bump Allocation
benches/benches.rs+13−24
@@ -13,19 +13,20 @@ use dodrio::{
1313 CachedSet, Node, Render, RenderContext, Vdom,
1414};
1515use std::cell::RefCell;
16+use std::convert::TryInto;
1617
1718/// The simplest thing we can render: `<div/>`.
1819struct Empty;
19−impl Render for Empty {
20− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
20+impl<'a> Render<'a> for Empty {
21+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
2122 div(&cx).finish()
2223 }
2324}
2425
2526/// Render a list that is `self.0` items long, has attributes and listeners.
2627struct SimpleList(usize);
27−impl Render for SimpleList {
28− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
28+impl<'a> Render<'a> for SimpleList {
29+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
2930 let mut children = bumpalo::collections::Vec::with_capacity_in(self.0, cx.bump);
3031 children.extend((0..self.0).map(|_| {
3132 li(&cx)
@@ -46,9 +47,10 @@ fn criterion_benchmark(c: &mut Criterion) {
4647 Benchmark::new("empty", {
4748 let mut bump = Bump::new();
4849 let cached_set = RefCell::new(CachedSet::default());
50+ let mut templates = Default::default();
4951 move |b| {
5052 bump.reset();
51− let mut cx = RenderContext::new(&bump, &cached_set);
53+ let mut cx = RenderContext::new(&bump, &cached_set, &mut templates);
5254 b.iter(|| {
5355 black_box(Empty.render(&mut cx));
5456 })
@@ -63,24 +65,18 @@ fn criterion_benchmark(c: &mut Criterion) {
6365 {
6466 let mut bump = Bump::new();
6567 let cached_set = RefCell::new(CachedSet::default());
68+ let mut templates = Default::default();
6669 move |b, n| {
6770 bump.reset();
68− let mut cx = RenderContext::new(&bump, &cached_set);
71+ let mut cx = RenderContext::new(&bump, &cached_set, &mut templates);
6972 b.iter(|| {
7073 black_box(SimpleList(*n).render(&mut cx));
7174 })
7275 }
7376 },
74− vec![
75− // TODO: Only test one `n` value until
76− // https://github.com/bheisler/criterion.rs/issues/269 is fixed.
77− //
78− // 100,
79− // 1_000,
80− 10_000,
81− ],
77+ vec![100, 1_000, 10_000],
8278 )
83− .throughput(|n| Throughput::Elements(*n as u32)),
79+ .throughput(|n| Throughput::Elements((*n).try_into().unwrap())),
8480 );
8581
8682 c.bench(
@@ -94,14 +90,7 @@ fn criterion_benchmark(c: &mut Criterion) {
9490 black_box(&vdom);
9591 })
9692 },
97− vec![
98− // TODO: Only test one `n` value until
99− // https://github.com/bheisler/criterion.rs/issues/269 is fixed.
100− //
101− // 100,
102− // 1_000,
103− 10_000,
104− ],
93+ vec![100, 1_000, 10_000],
10594 )
10695 .with_function("empty-to-full-list-to-empty", |b, &n| {
10796 b.iter(|| {
@@ -121,7 +110,7 @@ fn criterion_benchmark(c: &mut Criterion) {
121110 black_box(&vdom);
122111 })
123112 })
124− .throughput(|n| Throughput::Elements(*n as u32)),
113+ .throughput(|n| Throughput::Elements((*n).try_into().unwrap())),
125114 );
126115}
127116
crates/js-api/Cargo.toml+6−7
@@ -7,17 +7,16 @@ keywords = ["javascript", "js", "vdom", "virtual-dom"]
77license = "MPL-2.0"
88name = "dodrio-js-api"
99repository = "https://github.com/fitzgen/dodrio"
10−version = "0.1.0"
10+version = "0.2.0"
1111
1212[badges]
1313travis-ci = { repository = "fitzgen/dodrio" }
1414
1515[dependencies]
16−dodrio = { version = "=0.1.0", path = "../.." }
17−wasm-bindgen = "0.2.40"
18−js-sys = "0.3.17"
19−wasm-bindgen-futures = "0.3.17"
20−futures = "0.1.26"
16+dodrio = { version = "=0.2.0", path = "../.." }
17+wasm-bindgen = "0.2.55"
18+js-sys = "0.3.32"
19+wasm-bindgen-futures = "0.4.5"
2120
2221[dev-dependencies]
23−wasm-bindgen-test = "0.2.40"
22+wasm-bindgen-test = "0.3.5"
crates/js-api/src/lib.rs+13−10
@@ -99,8 +99,8 @@ impl GreetingViaJs {
9999
100100/// And finally the `Render` implementation! This adds a `<p>` element and some
101101/// text around whatever the inner JS `Greeting` component renders.
102−impl Render for GreetingViaJs {
103− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
102+impl<'a> Render<'a> for GreetingViaJs {
103+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
104104 use dodrio::builder::*;
105105 p(&cx)
106106 .children([
@@ -116,7 +116,6 @@ impl Render for GreetingViaJs {
116116#![deny(missing_docs, missing_debug_implementations)]
117117
118118use dodrio::{builder, bumpalo, Node, Render, RenderContext};
119−use futures::prelude::*;
120119use js_sys::{Object, Promise, Reflect};
121120use wasm_bindgen::prelude::*;
122121use wasm_bindgen::JsCast;
@@ -185,12 +184,16 @@ impl VdomWeak {
185184 /// Schedule re-rendering of the virtual DOM. A promise is returned that is
186185 /// resolved after the rendering has happened.
187186 pub fn render(&self) -> Promise {
188− wasm_bindgen_futures::future_to_promise(
189− self.inner.render().map(|_| JsValue::null()).map_err(|e| {
187+ let future = self.inner.render();
188+
189+ wasm_bindgen_futures::future_to_promise(async move {
190+ if let Err(e) = future.await {
190191 let msg = e.to_string();
191− js_sys::Error::new(&msg).into()
192− }),
193− )
192+ Err(js_sys::Error::new(&msg).into())
193+ } else {
194+ Ok(JsValue::null())
195+ }
196+ })
194197 }
195198}
196199
@@ -213,8 +216,8 @@ impl JsRender {
213216 }
214217}
215218
216−impl Render for JsRender {
217− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
219+impl<'a> Render<'a> for JsRender {
220+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
218221 create(cx, self.render())
219222 }
220223}
examples/counter/Cargo.toml+5−5
@@ -11,13 +11,13 @@ crate-type = ["cdylib"]
1111
1212[dependencies]
1313console_error_panic_hook = "0.1.6"
14−console_log = "0.1.2"
14+console_log = "0.2.0"
1515dodrio = { path = "../.." }
16−log = "0.4.6"
17−wasm-bindgen = "0.2.40"
16+log = "0.4.11"
17+wasm-bindgen = "0.2.55"
1818
1919[dependencies.web-sys]
20−version = "0.3.17"
20+version = "0.3.32"
2121features = [
2222 "console",
2323 "Document",
@@ -30,4 +30,4 @@ features = [
3030]
3131
3232[dev-dependencies]
33−wasm-bindgen-test = "0.2.40"
33+wasm-bindgen-test = "0.3.5"
examples/counter/src/lib.rsmode 100644 → 100755+5−4
@@ -1,4 +1,4 @@
1−use dodrio::{bumpalo, Render, RenderContext};
1+use dodrio::{bumpalo, Node, Render, RenderContext};
22use log::*;
33use wasm_bindgen::prelude::*;
44
@@ -26,8 +26,8 @@ impl Counter {
2626
2727// The `Render` implementation for `Counter`s displays the current count and has
2828// buttons to increment and decrement the count.
29−impl Render for Counter {
30− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> dodrio::Node<'a> {
29+impl<'a> Render<'a> for Counter {
30+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
3131 use dodrio::builder::*;
3232
3333 // Stringify the count as a bump-allocated string.
@@ -54,7 +54,8 @@ impl Render for Counter {
5454 button(&cx)
5555 .on("click", |root, vdom, _event| {
5656 // Same as above, but decrementing instead of incrementing.
57− root.unwrap_mut::<Counter>().decrement();
57+ let counter = root.unwrap_mut::<Counter>();
58+ counter.decrement();
5859 vdom.schedule_render();
5960 })
6061 .children([text("-")])
examples/game-of-life/Cargo.toml+5−6
@@ -11,14 +11,13 @@ crate-type = ["cdylib"]
1111
1212[dependencies]
1313dodrio = { path = "../.." }
14−wasm-bindgen = "0.2.40"
14+wasm-bindgen = "0.2.55"
1515console_error_panic_hook = "0.1.6"
16−js-sys = "0.3.17"
17−wasm-bindgen-futures = "0.3.17"
18−futures = "0.1.26"
16+js-sys = "0.3.32"
17+wasm-bindgen-futures = "0.4.5"
1918
2019[dependencies.web-sys]
21−version = "0.3.17"
20+version = "0.3.32"
2221features = [
2322 "Document",
2423 "HtmlElement",
@@ -27,4 +26,4 @@ features = [
2726]
2827
2928[dev-dependencies]
30−wasm-bindgen-test = "0.2.40"
29+wasm-bindgen-test = "0.3.5"
examples/game-of-life/index.htmlmode 100644 → 100755−3
@@ -19,9 +19,6 @@
1919 padding: 0;
2020 }
2121
22− .universe {
23− }
24−
2522 body {
2623 width: 100vw;
2724 height: 100vh;
examples/game-of-life/src/lib.rs+18−13
@@ -1,5 +1,4 @@
11use dodrio::{bumpalo, Node, Render, RenderContext, Vdom};
2−use futures::prelude::*;
32use std::cell::RefCell;
43use std::rc::Rc;
54use wasm_bindgen::{prelude::*, JsCast};
@@ -127,8 +126,8 @@ impl Universe {
127126}
128127
129128/// The rendering implementation for our Game of Life.
130−impl Render for Universe {
131− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
129+impl<'a> Render<'a> for Universe {
130+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
132131 use dodrio::builder::*;
133132
134133 let mut rows = bumpalo::collections::Vec::with_capacity_in(self.height as usize, cx.bump);
@@ -172,26 +171,32 @@ pub fn run() {
172171 // Create a new Game of Life `Universe` render component.
173172 let universe = Universe::new();
174173
175− // Create a virtual DOM and mount it and the `Hello` render component to the
174+ // Create a virtual DOM and mount it and the `Universe` render component to the
176175 // `<body>`.
177176 let vdom = Vdom::new(body.as_ref(), universe);
178177
179178 // Kick off a loop that keeps computing a tick of the universe and then
180179 // re-rendering on every animation frame.
181− let rc: Rc<RefCell<Option<Closure<FnMut()>>>> = Rc::new(RefCell::new(None));
180+ let rc = <Rc<RefCell<Option<Closure<dyn FnMut()>>>>>::default();
182181 let rc2 = rc.clone();
183182 let window2 = window.clone();
184183 let weak = vdom.weak();
185184 let f = Closure::wrap(Box::new(move || {
186185 weak.schedule_render();
187186
188− wasm_bindgen_futures::spawn_local(
189− weak.with_component(|root| {
190− let universe = root.unwrap_mut::<Universe>();
191− universe.tick();
192− })
193− .map_err(|_| wasm_bindgen::throw_str("impossible, we always keep the vdom alive")),
194− );
187+ wasm_bindgen_futures::spawn_local({
188+ let weak = weak.clone();
189+ async move {
190+ let fut = weak.with_component(|root| {
191+ let universe = root.unwrap_mut::<Universe>();
192+ universe.tick();
193+ });
194+
195+ if fut.await.is_err() {
196+ wasm_bindgen::throw_str("impossible, we always keep the vdom alive");
197+ }
198+ }
199+ });
195200
196201 window
197202 .request_animation_frame(
@@ -202,7 +207,7 @@ pub fn run() {
202207 .unchecked_ref::<js_sys::Function>(),
203208 )
204209 .unwrap_throw();
205− }) as Box<FnMut()>);
210+ }) as Box<dyn FnMut()>);
206211 window2
207212 .request_animation_frame(f.as_ref().unchecked_ref::<js_sys::Function>())
208213 .unwrap_throw();
examples/hello-world/Cargo.toml+3−3
@@ -11,11 +11,11 @@ crate-type = ["cdylib"]
1111
1212[dependencies]
1313dodrio = { path = "../.." }
14−wasm-bindgen = "0.2.40"
14+wasm-bindgen = "0.2.55"
1515console_error_panic_hook = "0.1.6"
1616
1717[dependencies.web-sys]
18−version = "0.3.17"
18+version = "0.3.32"
1919features = [
2020 "Document",
2121 "HtmlElement",
@@ -24,4 +24,4 @@ features = [
2424]
2525
2626[dev-dependencies]
27−wasm-bindgen-test = "0.2.40"
27+wasm-bindgen-test = "0.3.5"
examples/hello-world/src/lib.rs+2−2
@@ -10,8 +10,8 @@ struct Hello {
1010
1111// The `Render` implementation describes how to render a `Hello` component into
1212// HTML.
13−impl Render for Hello {
14− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
13+impl<'a> Render<'a> for Hello {
14+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
1515 let msg = bumpalo::format!(in cx.bump, "Hello, {}!", self.who);
1616 let msg = msg.into_bump_str();
1717 p(&cx).children([text(msg)]).finish()
examples/input-form/Cargo.toml+3−3
@@ -11,11 +11,11 @@ crate-type = ["cdylib"]
1111
1212[dependencies]
1313dodrio = { path = "../.." }
14−wasm-bindgen = "0.2.40"
14+wasm-bindgen = "0.2.55"
1515console_error_panic_hook = "0.1.6"
1616
1717[dependencies.web-sys]
18−version = "0.3.17"
18+version = "0.3.32"
1919features = [
2020 "console",
2121 "Document",
@@ -29,4 +29,4 @@ features = [
2929]
3030
3131[dev-dependencies]
32−wasm-bindgen-test = "0.2.40"
32+wasm-bindgen-test = "0.3.5"
examples/input-form/src/lib.rs+2−2
@@ -23,8 +23,8 @@ impl SayHelloTo {
2323
2424// The `Render` implementation has a text `<input>` and a `<div>` that shows a
2525// greeting to the `<input>`'s value.
26−impl Render for SayHelloTo {
27− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
26+impl<'a> Render<'a> for SayHelloTo {
27+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
2828 use dodrio::builder::*;
2929
3030 div(&cx)
examples/js-component/Cargo.toml+3−3
@@ -12,12 +12,12 @@ crate-type = ["cdylib"]
1212[dependencies]
1313dodrio = { path = "../.." }
1414dodrio-js-api = { path = "../../crates/js-api" }
15−wasm-bindgen = "0.2.40"
15+wasm-bindgen = "0.2.55"
1616console_error_panic_hook = "0.1.6"
17−js-sys = "0.3.17"
17+js-sys = "0.3.32"
1818
1919[dependencies.web-sys]
20−version = "0.3.17"
20+version = "0.3.32"
2121features = [
2222 "Document",
2323 "HtmlElement",
examples/js-component/src/lib.rs+3−3
@@ -31,8 +31,8 @@ impl GreetingViaJs {
3131
3232/// Here's the `Render` implementation! This adds a `<p>` element and some text
3333/// around whatever the inner JS `Greeting` component renders.
34−impl Render for GreetingViaJs {
35− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
34+impl<'a> Render<'a> for GreetingViaJs {
35+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
3636 use dodrio::builder::*;
3737 p(&cx)
3838 .children([text("JavaScript says: "), self.js.render(cx)])
@@ -54,7 +54,7 @@ pub fn run() {
5454 // Create a new `GreetingViaJs` render component.
5555 let component = GreetingViaJs::new("World");
5656
57− // Create a virtual DOM and mount it and the `Hello` render component to the
57+ // Create a virtual DOM and mount it and the `GreetingViaJs` render component to the
5858 // `<body>`.
5959 let vdom = Vdom::new(body.as_ref(), component);
6060
examples/moire/Cargo.toml+8−9
@@ -8,20 +8,19 @@ edition = "2018"
88crate-type = ["cdylib"]
99
1010[dependencies]
11−cfg-if = "0.1.7"
11+cfg-if = "0.1.10"
1212dodrio = { path = "../.." }
13−futures = "0.1.26"
14−log = "0.4.6"
15−wasm-bindgen = "0.2.40"
16−wasm-bindgen-futures = "0.3.17"
13+log = "0.4.11"
14+wasm-bindgen = "0.2.55"
15+wasm-bindgen-futures = "0.4.5"
1716
1817# Optional dependencies for logging.
1918console_error_panic_hook = { optional = true, version = "0.1.6" }
20−console_log = { optional = true, version = "0.1.2" }
21−js-sys = "0.3.17"
19+console_log = { optional = true, version = "0.2.0" }
20+js-sys = "0.3.32"
2221
2322[dependencies.web-sys]
24−version = "0.3.17"
23+version = "0.3.32"
2524features = [
2625 "Document",
2726 "Element",
@@ -35,7 +34,7 @@ features = [
3534]
3635
3736[dev-dependencies]
38−wasm-bindgen-test = "0.2.40"
37+wasm-bindgen-test = "0.3.5"
3938
4039[features]
4140logging = [
examples/moire/src/lib.rs+6−7
@@ -5,8 +5,7 @@ use dodrio::{bumpalo, Node, Render, RenderContext, Vdom};
55use std::cell::RefCell;
66use std::rc::Rc;
77use std::str;
8−use wasm_bindgen::prelude::*;
9−use wasm_bindgen::JsCast;
8+use wasm_bindgen::{prelude::*, JsCast};
109
1110/// This is the main function that is automatically invoked when the wasm module
1211/// is loaded.
@@ -23,12 +22,12 @@ pub fn run() {
2322 // Create a new `Moire` render component.
2423 let component = Moire::new();
2524
26− // Create a virtual DOM and mount it and the `Hello` render component to the
25+ // Create a virtual DOM and mount it and the `Moire` render component to the
2726 // scene.
2827 let vdom = Vdom::new(&scene, component);
2928
3029 // Kick off a loop that keeps re-rendering on every animation frame.
31− let rc: Rc<RefCell<Option<Closure<FnMut()>>>> = Rc::new(RefCell::new(None));
30+ let rc = <Rc<RefCell<Option<Closure<dyn FnMut()>>>>>::default();
3231 let rc2 = rc.clone();
3332 let window2 = window.clone();
3433 let weak = vdom.weak();
@@ -43,7 +42,7 @@ pub fn run() {
4342 .unchecked_ref::<js_sys::Function>(),
4443 )
4544 .unwrap_throw();
46− }) as Box<FnMut()>);
45+ }) as Box<dyn FnMut()>);
4746 window2
4847 .request_animation_frame(f.as_ref().unchecked_ref::<js_sys::Function>())
4948 .unwrap_throw();
@@ -105,8 +104,8 @@ impl Moire {
105104 }
106105}
107106
108−impl Render for Moire {
109− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
107+impl<'a> Render<'a> for Moire {
108+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
110109 use dodrio::builder::*;
111110
112111 let elapsed = web_sys::window()
examples/sierpinski-triangle/Cargo.toml+5−6
@@ -9,14 +9,13 @@ crate-type = ["cdylib"]
99
1010[dependencies]
1111dodrio = { path = "../.." }
12−wasm-bindgen = "0.2.40"
12+wasm-bindgen = "0.2.55"
1313console_error_panic_hook = "0.1.6"
14−js-sys = "0.3.17"
15−wasm-bindgen-futures = "0.3.17"
16−futures = "0.1.26"
14+js-sys = "0.3.32"
15+wasm-bindgen-futures = "0.4.5"
1716
1817[dependencies.web-sys]
19−version = "0.3.17"
18+version = "0.3.32"
2019features = [
2120 "Document",
2221 "HtmlElement",
@@ -26,4 +25,4 @@ features = [
2625]
2726
2827[dev-dependencies]
29−wasm-bindgen-test = "0.2.40"
28+wasm-bindgen-test = "0.3.5"
examples/sierpinski-triangle/README.mdmode 100644 → 100755+1−1
@@ -20,5 +20,5 @@ wasm-pack build --target web
2020Use any HTTP server, for example:
2121
2222```
23−python -m http.server
23+python -m SimpleHTTPServer
2424```
examples/sierpinski-triangle/src/lib.rs+5−6
@@ -1,8 +1,7 @@
11use dodrio::{bumpalo, Node, Render, RenderContext, Vdom};
22use std::cell::RefCell;
33use std::rc::Rc;
4−use wasm_bindgen::prelude::*;
5−use wasm_bindgen::JsCast;
4+use wasm_bindgen::{prelude::*, JsCast};
65use web_sys::Window;
76
87// Triangle container defining target size.
@@ -86,8 +85,8 @@ impl Container {
8685 }
8786}
8887
89−impl Render for Container {
90− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
88+impl<'a> Render<'a> for Container {
89+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
9190 use dodrio::builder::div;
9291
9392 let elapsed = web_sys::window()
@@ -112,7 +111,7 @@ impl Render for Container {
112111
113112// Kick off a loop that keeps re-rendering on every animation frame.
114113fn animate(window: Window, vdom: Vdom) {
115− let rc: Rc<RefCell<Option<Closure<FnMut()>>>> = Rc::new(RefCell::new(None));
114+ let rc = <Rc<RefCell<Option<Closure<dyn FnMut()>>>>>::default();
116115 let rc2 = rc.clone();
117116 let window2 = window.clone();
118117 let weak = vdom.weak();
@@ -127,7 +126,7 @@ fn animate(window: Window, vdom: Vdom) {
127126 .unchecked_ref::<js_sys::Function>(),
128127 )
129128 .unwrap_throw();
130− }) as Box<FnMut()>);
129+ }) as Box<dyn FnMut()>);
131130 window2
132131 .request_animation_frame(f.as_ref().unchecked_ref::<js_sys::Function>())
133132 .unwrap_throw();
examples/todomvc/Cargo.toml+11−12
@@ -5,22 +5,21 @@ name = "dodrio-todomvc"
55version = "0.1.0"
66
77[dependencies]
8−cfg-if = "0.1.7"
9−dodrio = { path = "../.." }
10−futures = "0.1.26"
11−js-sys = "0.3.17"
12−serde = { features = ["derive"], version = "1.0.90" }
13−serde_json = "1.0.39"
14−wasm-bindgen = "0.2.40"
15−wasm-bindgen-futures = "0.3.17"
8+cfg-if = "0.1.10"
9+dodrio = { path = "../..", features = ["serde"] }
10+js-sys = "0.3.32"
11+serde = { features = ["derive"], version = "1.0.114" }
12+serde_json = "1.0.57"
13+wasm-bindgen = "0.2.55"
14+wasm-bindgen-futures = "0.4.5"
1615
1716# Optional dependencies for logging.
1817console_error_panic_hook = { optional = true, version = "0.1.6" }
19−console_log = { optional = true, version = "0.1.2" }
20−log = { optional = true, version = "0.4.6" }
18+console_log = { optional = true, version = "0.2.0" }
19+log = { optional = true, version = "0.4.11" }
2120
2221[dependencies.web-sys]
23−version = "0.3.17"
22+version = "0.3.32"
2423features = [
2524 "Document",
2625 "Event",
@@ -35,7 +34,7 @@ features = [
3534]
3635
3736[dev-dependencies]
38−wasm-bindgen-test = "0.2.40"
37+wasm-bindgen-test = "0.3.5"
3938
4039[features]
4140logging = [
examples/todomvc/src/router.rsmode 100644 → 100755+23−21
@@ -4,10 +4,8 @@ use crate::todos::Todos;
44use crate::utils;
55use crate::visibility::Visibility;
66use dodrio::VdomWeak;
7−use futures::prelude::*;
87use std::str::FromStr;
9−use wasm_bindgen::prelude::*;
10−use wasm_bindgen::JsCast;
8+use wasm_bindgen::{prelude::*, JsCast};
119
1210/// Start the router.
1311pub fn start(vdom: VdomWeak) {
@@ -30,23 +28,27 @@ pub fn start(vdom: VdomWeak) {
3028 v
3129 });
3230
33− wasm_bindgen_futures::spawn_local(
34− vdom.with_component({
35− let vdom = vdom.clone();
36− move |root| {
37− let todos = root.unwrap_mut::<Todos>();
38− // If the todos' visibility already matches the event's
39− // visibility, then there is nothing to do (ha). If they
40− // don't match, then we need to update the todos' visibility
41− // and re-render.
42− if todos.visibility() != new_vis {
43− todos.set_visibility(new_vis);
44− vdom.schedule_render();
45− }
46− }
47− })
48− .map_err(|_| ()),
49− );
31+ wasm_bindgen_futures::spawn_local({
32+ let vdom = vdom.clone();
33+ async move {
34+ let _ = vdom
35+ .with_component({
36+ let vdom = vdom.clone();
37+ move |root| {
38+ let todos = root.unwrap_mut::<Todos>();
39+ // If the todos' visibility already matches the event's
40+ // visibility, then there is nothing to do (ha). If they
41+ // don't match, then we need to update the todos' visibility
42+ // and re-render.
43+ if todos.visibility() != new_vis {
44+ todos.set_visibility(new_vis);
45+ vdom.schedule_render();
46+ }
47+ }
48+ })
49+ .await;
50+ }
51+ });
5052 };
5153
5254 // Call it once to handle the initial `#` fragment.
@@ -57,7 +59,7 @@ pub fn start(vdom: VdomWeak) {
5759 // Note that if we ever intended to unmount our todos app, we would want to
5860 // provide a method for removing this router's event listener and cleaning
5961 // up after ourselves.
60− let on_hash_change = Closure::wrap(Box::new(on_hash_change) as Box<FnMut()>);
62+ let on_hash_change = Closure::wrap(Box::new(on_hash_change) as Box<dyn FnMut()>);
6163 let window = utils::window();
6264 window
6365 .add_event_listener_with_callback("hashchange", on_hash_change.as_ref().unchecked_ref())
examples/todomvc/src/todo.rs+77−21
@@ -1,14 +1,19 @@
11//! Type definition and `dodrio::Render` implementation for a single todo item.
22
33use crate::keys;
4−use dodrio::{Node, Render, RenderContext, RootRender, VdomWeak};
4+use dodrio::{Cached, Node, Render, RenderContext, RootRender, VdomWeak};
55use serde::{Deserialize, Serialize};
66use std::marker::PhantomData;
77use wasm_bindgen::{prelude::*, JsCast};
88
99/// A single todo item.
10−#[derive(Serialize, Deserialize)]
10+#[derive(Default)]
1111pub struct Todo<C> {
12+ inner: Cached<TodoInner<C>>,
13+}
14+
15+#[derive(Serialize, Deserialize)]
16+struct TodoInner<C> {
1217 id: usize,
1318 title: String,
1419 completed: bool,
@@ -48,68 +53,87 @@ impl<C> Todo<C> {
4853 let completed = false;
4954 let edits = None;
5055 Todo {
51− id,
52− title,
53− completed,
54− edits,
55− _controller: PhantomData,
56+ inner: Cached::new(TodoInner {
57+ id,
58+ title,
59+ completed,
60+ edits,
61+ _controller: PhantomData,
62+ }),
5663 }
5764 }
5865
5966 /// Set this todo item's id.
6067 pub fn set_id(&mut self, id: usize) {
61− self.id = id;
68+ if id != self.inner.id {
69+ Cached::invalidate(&self.inner);
70+ }
71+ self.inner.id = id;
6272 }
6373
6474 /// Is this `Todo` complete?
6575 pub fn is_complete(&self) -> bool {
66− self.completed
76+ self.inner.completed
6777 }
6878
6979 /// Mark the `Todo` as complete or not.
7080 pub fn set_complete(&mut self, to: bool) {
71− self.completed = to;
81+ if to != self.inner.completed {
82+ Cached::invalidate(&self.inner);
83+ }
84+ self.inner.completed = to;
7285 }
7386
7487 /// Get this todo's title.
7588 pub fn title(&self) -> &str {
76− &self.title
89+ &self.inner.title
7790 }
7891
7992 /// Set this todo item's title.
8093 pub fn set_title<S: Into<String>>(&mut self, title: S) {
81− self.title = title.into();
94+ let title = title.into();
95+ if title != self.inner.title {
96+ Cached::invalidate(&self.inner);
97+ }
98+ self.inner.title = title;
8299 }
83100
84101 /// Set the edits for this todo.
85102 pub fn set_edits<S: Into<String>>(&mut self, edits: Option<S>) {
86− self.edits = edits.map(Into::into);
103+ let edits = edits.map(Into::into);
104+ if edits != self.inner.edits {
105+ Cached::invalidate(&self.inner);
106+ }
107+ self.inner.edits = edits;
87108 }
88109
89110 /// Take this todo's edits, leaving `None` in their place.
90111 pub fn take_edits(&mut self) -> Option<String> {
91− self.edits.take()
112+ if self.inner.edits.is_some() {
113+ Cached::invalidate(&self.inner);
114+ }
115+ self.inner.edits.take()
92116 }
93117}
94118
95−impl<C: TodoActions> Render for Todo<C> {
96− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
119+impl<'a, C: TodoActions> Render<'a> for Todo<C> {
120+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
97121 use dodrio::{
98122 builder::*,
99123 bumpalo::{self, collections::String},
100124 };
101125
102− let id = self.id;
103− let title = self.edits.as_ref().unwrap_or(&self.title);
126+ let id = self.inner.id;
127+ let title = self.inner.edits.as_ref().unwrap_or(&self.inner.title);
104128 let title = bumpalo::format!(in cx.bump, "{}", title).into_bump_str();
105129
106130 li(&cx)
107131 .attr("class", {
108132 let mut class = String::new_in(cx.bump);
109− if self.completed {
133+ if self.inner.completed {
110134 class.push_str("completed ");
111135 }
112− if self.edits.is_some() {
136+ if self.inner.edits.is_some() {
113137 class.push_str("editing");
114138 }
115139 class.into_bump_str()
@@ -121,7 +145,7 @@ impl<C: TodoActions> Render for Todo<C> {
121145 input(&cx)
122146 .attr("class", "toggle")
123147 .attr("type", "checkbox")
124− .bool_attr("checked", self.completed)
148+ .bool_attr("checked", self.inner.completed)
125149 .on("click", move |root, vdom, _event| {
126150 C::toggle_completed(root, vdom, id);
127151 })
@@ -171,3 +195,35 @@ impl<C: TodoActions> Render for Todo<C> {
171195 .finish()
172196 }
173197}
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 100644 → 100755+5−6
@@ -9,8 +9,7 @@ use dodrio::{bumpalo, Node, Render, RenderContext, RootRender, VdomWeak};
99use serde::{Deserialize, Serialize};
1010use std::marker::PhantomData;
1111use std::mem;
12−use wasm_bindgen::prelude::*;
13−use wasm_bindgen::JsCast;
12+use wasm_bindgen::{prelude::*, JsCast};
1413
1514/// A collection of todos.
1615#[derive(Default, Serialize, Deserialize)]
@@ -29,7 +28,7 @@ pub struct Todos<C = Controller> {
2928}
3029
3130/// Actions for `Todos` that can be triggered by UI interactions.
32−pub trait TodosActions: TodoActions {
31+pub trait TodosActions: 'static + TodoActions {
3332 /// Toggle the completion state of all todo items.
3433 fn toggle_all(root: &mut dyn RootRender, vdom: VdomWeak);
3534
@@ -50,7 +49,7 @@ pub trait TodosActions: TodoActions {
5049impl<C> Todos<C> {
5150 /// Construct a new todos set.
5251 ///
53− /// If an existing set is available in local storage, then us that,
52+ /// If an existing set is available in local storage, then use that,
5453 /// otherwise create a new set.
5554 pub fn new() -> Self
5655 where
@@ -290,8 +289,8 @@ impl<C: TodosActions> Todos<C> {
290289 }
291290}
292291
293−impl<C: TodosActions> Render for Todos<C> {
294− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
292+impl<'a, C: TodosActions> Render<'a> for Todos<C> {
293+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
295294 use dodrio::builder::*;
296295
297296 div(&cx)
js/change-list-interpreter.js+104−12
@@ -86,15 +86,23 @@ const OP_TABLE = [
8686 },
8787
8888 // 5
89− function pushFirstChild(interpreter, mem8, mem32, i) {
90− interpreter.stack.push(top(interpreter.stack).firstChild);
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);
9195 return i;
9296 },
9397
9498 // 6
95− function popPushNextSibling(interpreter, mem8, mem32, i) {
96− const node = interpreter.stack.pop();
97− interpreter.stack.push(node.nextSibling);
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);
98106 return i;
99107 },
100108
@@ -188,13 +196,84 @@ const OP_TABLE = [
188196 },
189197
190198 // 17
191− function setAttributeNS(interpreter, mem8, mem32, i) {
192− const nameId = mem32[i++];
193− const valueId = mem32[i++];
194− const name = interpreter.getCachedString(nameId);
195− const value = interpreter.getCachedString(valueId);
196− const node = top(interpreter.stack);
197− node.setAttributeNS(null, name, value);
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));
198277 return i;
199278 }
200279];
@@ -206,6 +285,8 @@ export class ChangeListInterpreter {
206285 this.ranges = [];
207286 this.stack = [];
208287 this.strings = new Map();
288+ this.temporaries = [];
289+ this.templates = new Map();
209290 }
210291
211292 unmount() {
@@ -218,6 +299,8 @@ export class ChangeListInterpreter {
218299 this.ranges = null;
219300 this.stack = null;
220301 this.strings = null;
302+ this.temporaries = null;
303+ this.templates = null;
221304 }
222305
223306 addChangeListRange(start, len) {
@@ -242,6 +325,7 @@ export class ChangeListInterpreter {
242325
243326 this.ranges.length = 0;
244327 this.stack.length = 0;
328+ this.temporaries.length = 0;
245329 }
246330
247331 applyChangeRange(mem8, mem32, start, len) {
@@ -264,6 +348,14 @@ export class ChangeListInterpreter {
264348 return this.strings.get(id);
265349 }
266350
351+ saveTemplate(id, template) {
352+ this.templates.set(id, template);
353+ }
354+
355+ getTemplate(id) {
356+ return this.templates.get(id);
357+ }
358+
267359 initEventsTrampoline(trampoline) {
268360 this.trampoline = trampoline;
269361 trampoline.mounted = true;
js/strace.jsnew file+80
@@ -0,0 +1,80 @@
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+64−5
@@ -1,6 +1,6 @@
11//! Helpers for building virtual DOM nodes.
22
3−use crate::{Attribute, Listener, Node, RootRender, VdomWeak};
3+use crate::{node::NodeKey, Attribute, Listener, Node, RootRender, VdomWeak};
44use bumpalo::Bump;
55
66/// A virtual DOM element builder.
@@ -16,6 +16,7 @@ where
1616 Children: 'a + AsRef<[Node<'a>]>,
1717{
1818 bump: &'a Bump,
19+ key: NodeKey,
1920 tag_name: &'a str,
2021 listeners: Listeners,
2122 attributes: Attributes,
@@ -61,6 +62,7 @@ impl<'a>
6162 let bump = bump.into();
6263 ElementBuilder {
6364 bump,
65+ key: NodeKey::NONE,
6466 tag_name,
6567 listeners: bumpalo::collections::Vec::new_in(bump),
6668 attributes: bumpalo::collections::Vec::new_in(bump),
@@ -110,6 +112,7 @@ where
110112 {
111113 ElementBuilder {
112114 bump: self.bump,
115+ key: self.key,
113116 tag_name: self.tag_name,
114117 listeners,
115118 attributes: self.attributes,
@@ -148,6 +151,7 @@ where
148151 {
149152 ElementBuilder {
150153 bump: self.bump,
154+ key: self.key,
151155 tag_name: self.tag_name,
152156 listeners: self.listeners,
153157 attributes,
@@ -186,6 +190,7 @@ where
186190 {
187191 ElementBuilder {
188192 bump: self.bump,
193+ key: self.key,
189194 tag_name: self.tag_name,
190195 listeners: self.listeners,
191196 attributes: self.attributes,
@@ -204,7 +209,7 @@ where
204209 /// let b = Bump::new();
205210 ///
206211 /// // Create a `<td>` tag with an xhtml namespace
207− /// let my_div = td(&b)
212+ /// let my_td = td(&b)
208213 /// .namespace(Some("http://www.w3.org/1999/xhtml"))
209214 /// .finish();
210215 /// ```
@@ -212,6 +217,7 @@ where
212217 pub fn namespace(self, namespace: Option<&'a str>) -> Self {
213218 ElementBuilder {
214219 bump: self.bump,
220+ key: self.key,
215221 tag_name: self.tag_name,
216222 listeners: self.listeners,
217223 attributes: self.attributes,
@@ -220,6 +226,45 @@ where
220226 }
221227 }
222228
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+
223268 /// Create the virtual DOM node described by this builder.
224269 ///
225270 /// # Example
@@ -237,12 +282,22 @@ where
237282 /// ```
238283 #[inline]
239284 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+
240294 Node::element(
241295 self.bump,
296+ self.key,
242297 self.tag_name,
243− self.listeners,
244− self.attributes,
245− self.children,
298+ listeners,
299+ attributes,
300+ children,
246301 self.namespace,
247302 )
248303 }
@@ -955,6 +1010,10 @@ builder_constructors! {
9551010 /// [`<rect>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect)
9561011 /// element.
9571012 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";
9581017}
9591018
9601019/// Construct a text node.
src/cached.rs+91−22
@@ -1,19 +1,38 @@
11use crate::{
2− cached_set::{CacheId, CachedSet},
2+ cached_set::CachedSet,
3+ node::{CachedNode, NodeKey},
34 Node, Render, RenderContext,
45};
6+use std::any::TypeId;
57use std::cell::Cell;
68use std::ops::{Deref, DerefMut};
79
810/// A renderable that supports caching for when rendering is expensive but can
911/// generate the same DOM tree.
10−#[derive(Clone, Debug)]
11−pub struct Cached<R> {
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)]
19+pub struct Cached<R>
20+where
21+ R: Default,
22+{
1223 inner: R,
13− cached: Cell<Option<CacheId>>,
24+ cached: Cell<Option<CachedNode>>,
25+}
26+
27+pub_unstable_internal! {
28+ #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
29+ pub(crate) struct TemplateId(TypeId);
1430}
1531
16−impl<R> Cached<R> {
32+impl<R> Cached<R>
33+where
34+ R: Default,
35+{
1736 /// Construct a new `Cached<R>` of an inner `R`.
1837 ///
1938 /// # Example
@@ -21,12 +40,13 @@ impl<R> Cached<R> {
2140 /// ```
2241 /// use dodrio::{Cached, Node, Render, RenderContext};
2342 ///
43+ /// #[derive(Default)]
2444 /// pub struct Counter {
2545 /// count: u32,
2646 /// }
2747 ///
28− /// impl Render for Counter {
29− /// fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
48+ /// impl<'a> Render<'a> for Counter {
49+ /// fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
3050 /// // ...
3151 /// # unimplemented!()
3252 /// }
@@ -60,12 +80,13 @@ impl<R> Cached<R> {
6080 /// use dodrio::{bumpalo, Cached, Node, Render, RenderContext};
6181 ///
6282 /// /// A component that renders to "<p>Hello, {who}!</p>"
83+ /// #[derive(Default)]
6384 /// pub struct Hello {
6485 /// who: String
6586 /// }
6687 ///
67− /// impl Render for Hello {
68− /// fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
88+ /// impl<'a> Render<'a> for Hello {
89+ /// fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
6990 /// use dodrio::builder::*;
7091 /// let greeting = bumpalo::format!(in cx.bump, "Hello, {}!", self.who);
7192 /// p(&cx)
@@ -93,7 +114,19 @@ impl<R> Cached<R> {
93114 }
94115}
95116
96−impl<R> Deref for Cached<R> {
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>
127+where
128+ R: Default,
129+{
97130 type Target = R;
98131
99132 fn deref(&self) -> &R {
@@ -101,18 +134,22 @@ impl<R> Deref for Cached<R> {
101134 }
102135}
103136
104−impl<R> DerefMut for Cached<R> {
137+impl<R> DerefMut for Cached<R>
138+where
139+ R: Default,
140+{
105141 fn deref_mut(&mut self) -> &mut R {
106142 &mut self.inner
107143 }
108144}
109145
110−impl<R> Render for Cached<R>
146+impl<'a, R> Render<'a> for Cached<R>
111147where
112− R: Render,
148+ R: 'static + Default + for<'b> Render<'b>,
113149{
114− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
115− let id = match self.cached.get() {
150+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
151+ let template = cx.template::<R>();
152+ let cached = match self.cached.get() {
116153 // This does-the-cache-contain-this-id check is necessary because
117154 // the same `Cached<R>` instance can be rendered into vdom A, which
118155 // will save the results into A's cached set and yield id X. Then,
@@ -140,21 +177,53 @@ where
140177 //
141178 // This is all do-able but is a bit more book keeping than we really
142179 // want to do unless it is well motivated.
143− Some(id)
180+ Some(cached)
144181 if {
145182 let cached_set = cx.cached_set.borrow();
146− cached_set.contains(id)
183+ cached_set.contains(cached.id)
147184 } =>
148185 {
149− id
186+ cached
150187 }
151188 _ => {
152− let id = CachedSet::insert(cx, |nested_cx| self.inner.render(nested_cx));
153− self.cached.set(Some(id));
154− id
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
155198 }
156199 };
157200
158− Node::cached(id)
201+ cached.into()
202+ }
203+}
204+
205+#[cfg(feature = "serde")]
206+impl<R> serde::Serialize for Cached<R>
207+where
208+ R: Default + serde::Serialize,
209+{
210+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
211+ where
212+ S: serde::Serializer,
213+ {
214+ self.inner.serialize(serializer)
215+ }
216+}
217+
218+#[cfg(feature = "serde")]
219+impl<'de, R> serde::Deserialize<'de> for Cached<R>
220+where
221+ R: Default + serde::Deserialize<'de>,
222+{
223+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
224+ where
225+ D: serde::Deserializer<'de>,
226+ {
227+ R::deserialize(deserializer).map(Cached::new)
159228 }
160229}
src/cached_set.rs+79−52
@@ -35,6 +35,28 @@ pub(crate) struct CacheEntry {
3535 // once, we don't have to repeat the tracing every time we want to garbage
3636 // collect cache entries.
3737 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+ }
3860}
3961
4062impl CachedSet {
@@ -62,9 +84,8 @@ impl CachedSet {
6284 }
6385
6486 self.items.retain(|id, entry| {
65− let keep = marked.contains(id);
87+ let keep = entry.pinned || marked.contains(id);
6688 if !keep {
67− debug!("CachedSet::gc: removing {:?}", id);
6889 let node: &Node = unsafe { &*entry.node };
6990 registry.remove_subtree(node);
7091 }
@@ -76,33 +97,31 @@ impl CachedSet {
7697 // node has.
7798 fn trace(&self, node: &Node) -> FxHashSet<CacheId> {
7899 let mut edges = FxHashSet::default();
100+ self.trace_recursive(&mut edges, node);
101+ edges
102+ }
79103
80− let bump = Bump::new();
81− let mut stack = bumpalo::collections::Vec::with_capacity_in(64, &bump);
82− stack.push(node);
83−
84− while let Some(node) = stack.pop() {
85− match &node.kind {
86− NodeKind::Text(_) => continue,
87− NodeKind::Cached(c) => {
88− debug_assert!(self.items.contains_key(&c.id));
89− edges.insert(c.id);
90− edges.extend(
91− self.items
92− .get(&c.id)
93− .expect_throw("CachedSet::trace: should have c.id in cached set")
94− .edges
95− .iter()
96− .cloned(),
97− );
98− }
99− NodeKind::Element(el) => {
100− stack.extend(el.children);
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);
101122 }
102123 }
103124 }
104−
105− edges
106125 }
107126
108127 fn next_id(&mut self) -> CacheId {
@@ -111,25 +130,41 @@ impl CachedSet {
111130 CacheId(next.expect_throw("ID_COUNTER overflowed"))
112131 }
113132
114− pub(crate) fn insert<F>(cx: &mut RenderContext, f: F) -> CacheId
133+ pub(crate) fn insert<F>(
134+ cx: &mut RenderContext,
135+ pinned: bool,
136+ template: Option<CacheId>,
137+ f: F,
138+ ) -> CacheId
115139 where
116140 F: for<'a> FnOnce(&mut RenderContext<'a>) -> Node<'a>,
117141 {
118− let bump = Bump::new();
119142 let set = cx.cached_set;
120− let mut nested_cx = RenderContext::new(&bump, set);
121− let node = f(&mut nested_cx);
122− let node = bump.alloc(node);
123− let edges = {
124− let set = set.borrow();
125− set.trace(node)
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,
126164 };
127− let node = node as *mut Node<'_> as usize as *const Node<'static>;
128− let entry = CacheEntry { bump, node, edges };
129165
130166 let mut set = set.borrow_mut();
131167 let id = set.next_id();
132− debug!("CachedSet::insert: id = {:?}; entry = {:?}", id, entry);
133168 set.items.insert(id, entry);
134169 id
135170 }
@@ -139,21 +174,13 @@ impl CachedSet {
139174 self.items.contains_key(&id)
140175 }
141176
142− /// Get the node for the given cache id.
143− pub fn get(&self, mut id: CacheId) -> Node {
144− debug!("CachedSet::get: id = {:?}", id);
145− loop {
146− let entry = self
147− .items
148− .get(&id)
149− .expect_throw("CachedSet::get: should have id in set");
150− let node: &Node = unsafe { &*entry.node };
151− if let NodeKind::Cached(ref c) = node.kind {
152− id = c.id;
153− continue;
154− } else {
155− return node.clone();
156− }
157− }
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)
158185 }
159186}
src/change_list/emitter.rsmode 100644 → 100755+93−5
@@ -30,6 +30,7 @@ impl InstructionEmitter {
3030
3131 /// Invoke the given function with each of the allocated instruction
3232 /// sequences that this emitter has built up.
33+ #[cfg_attr(feature = "xxx-unstable-internal-use-only", allow(dead_code))]
3334 pub fn each_instruction_sequence<F>(&mut self, f: F)
3435 where
3536 F: FnMut(&[u8]),
@@ -114,16 +115,21 @@ define_change_list_instructions! {
114115 /// Stack: `[... Node] -> [... Node Node]`
115116 ///
116117 /// ```text
117− /// stack.push(stack.top().firstChild)
118+ /// parent = stack.top()
119+ /// child = parent.childNodes[parent.childNodes.length - n - 1]
120+ /// stack.push(child)
118121 /// ```
119− push_first_child() = 5,
122+ push_reverse_child(n) = 5,
120123
121− /// Stack: `[... Node] -> [... Node]`
124+ /// Stack: `[... Node Node] -> [... Node Node]`
122125 ///
123126 /// ```text
124− /// stack.push(stack.pop().nextSibling)
127+ /// stack.pop();
128+ /// parent = stack.top();
129+ /// child = parent.childNodes[n]
130+ /// stack.push(child)
125131 /// ```
126− pop_push_next_sibling() = 6,
132+ pop_push_child(n) = 6,
127133
128134 /// Stack: `[... T] -> [...]`
129135 ///
@@ -201,4 +207,86 @@ define_change_list_instructions! {
201207 /// stack.push(document.createElementNS(tag_name, namespace))
202208 /// ```
203209 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,
204292}
src/change_list/mod.rs+195−25
@@ -1,27 +1,33 @@
11pub(crate) mod emitter;
22pub(crate) mod strings;
3+pub(crate) mod traversal;
34
45// Note: has to be `pub` because of `wasm-bindgen` visibility restrictions.
56pub mod js;
67
78use self::emitter::InstructionEmitter;
89use self::strings::{StringKey, StringsCache};
9−use crate::Listener;
10+use self::traversal::{MoveTo, Traversal};
11+use crate::{cached_set::CacheId, Listener};
12+use fxhash::FxHashSet;
1013
1114#[derive(Debug)]
1215pub(crate) struct ChangeListPersistentState {
1316 strings: StringsCache,
1417 emitter: InstructionEmitter,
18+ traversal: Traversal,
1519 interpreter: js::ChangeListInterpreter,
20+ templates: FxHashSet<CacheId>,
1621}
1722
1823pub(crate) struct ChangeListBuilder<'a> {
1924 state: &'a mut ChangeListPersistentState,
25+ next_temporary: u32,
26+ forcing_new_listeners: bool,
2027}
2128
2229impl Drop for ChangeListPersistentState {
2330 fn drop(&mut self) {
24− debug!("Dropping ChangeListPersistentState");
2531 self.interpreter.unmount();
2632 }
2733}
@@ -30,11 +36,15 @@ impl ChangeListPersistentState {
3036 pub(crate) fn new(container: &crate::Element) -> ChangeListPersistentState {
3137 let strings = StringsCache::new();
3238 let emitter = InstructionEmitter::new();
39+ let traversal = Traversal::new();
3340 let interpreter = js::ChangeListInterpreter::new(container);
41+ let templates = Default::default();
3442 ChangeListPersistentState {
3543 strings,
3644 emitter,
45+ traversal,
3746 interpreter,
47+ templates,
3848 }
3949 }
4050
@@ -43,7 +53,11 @@ impl ChangeListPersistentState {
4353 }
4454
4555 pub(crate) fn builder<'a>(&'a mut self) -> ChangeListBuilder<'a> {
46− ChangeListBuilder { state: self }
56+ ChangeListBuilder {
57+ state: self,
58+ next_temporary: 0,
59+ forcing_new_listeners: false,
60+ }
4761 }
4862}
4963
@@ -56,6 +70,7 @@ cfg_if::cfg_if! {
5670 // Nothing to actually apply the changes to.
5771
5872 self.state.emitter.reset();
73+ self.state.traversal.reset();
5974 }
6075 }
6176 } else {
@@ -71,12 +86,127 @@ cfg_if::cfg_if! {
7186 interpreter.apply_changes(wasm_bindgen::memory());
7287
7388 self.state.emitter.reset();
89+ self.state.traversal.reset();
7490 }
7591 }
7692 }
7793}
7894
95+/// Traversal methods.
7996impl 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+
80210 pub fn ensure_string(&mut self, string: &str) -> StringKey {
81211 self.state
82212 .strings
@@ -84,6 +214,7 @@ impl ChangeListBuilder<'_> {
84214 }
85215
86216 pub fn set_text(&self, text: &str) {
217+ debug_assert!(self.traversal_is_committed());
87218 debug!("emit: set_text({:?})", text);
88219 self.state
89220 .emitter
@@ -91,51 +222,48 @@ impl ChangeListBuilder<'_> {
91222 }
92223
93224 pub fn remove_self_and_next_siblings(&self) {
225+ debug_assert!(self.traversal_is_committed());
94226 debug!("emit: remove_self_and_next_siblings()");
95227 self.state.emitter.remove_self_and_next_siblings();
96228 }
97229
98230 pub fn replace_with(&self) {
231+ debug_assert!(self.traversal_is_committed());
99232 debug!("emit: replace_with()");
100233 self.state.emitter.replace_with();
101234 }
102235
103− pub fn set_attribute(&mut self, name: &str, value: &str) {
104− debug!("emit: set_attribute({:?}, {:?})", name, value);
105− let name_id = self.ensure_string(name);
106− let value_id = self.ensure_string(value);
107− self.state
108− .emitter
109− .set_attribute(name_id.into(), value_id.into());
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+ }
110250 }
111251
112252 pub fn remove_attribute(&mut self, name: &str) {
253+ debug_assert!(self.traversal_is_committed());
113254 debug!("emit: remove_attribute({:?})", name);
114255 let name_id = self.ensure_string(name);
115256 self.state.emitter.remove_attribute(name_id.into());
116257 }
117258
118− pub fn push_first_child(&self) {
119− debug!("emit: push_first_child");
120− self.state.emitter.push_first_child();
121− }
122−
123− pub fn pop_push_next_sibling(&self) {
124− debug!("emit: pop_push_next_sibling");
125− self.state.emitter.pop_push_next_sibling();
126− }
127−
128− pub fn pop(&self) {
129− debug!("emit: pop");
130− self.state.emitter.pop();
131− }
132−
133259 pub fn append_child(&self) {
260+ debug_assert!(self.traversal_is_committed());
134261 debug!("emit: append_child()");
135262 self.state.emitter.append_child();
136263 }
137264
138265 pub fn create_text_node(&self, text: &str) {
266+ debug_assert!(self.traversal_is_committed());
139267 debug!("emit: create_text_node({:?})", text);
140268 self.state
141269 .emitter
@@ -143,12 +271,14 @@ impl ChangeListBuilder<'_> {
143271 }
144272
145273 pub fn create_element(&mut self, tag_name: &str) {
274+ debug_assert!(self.traversal_is_committed());
146275 debug!("emit: create_element({:?})", tag_name);
147276 let tag_name_id = self.ensure_string(tag_name);
148277 self.state.emitter.create_element(tag_name_id.into());
149278 }
150279
151280 pub fn create_element_ns(&mut self, tag_name: &str, ns: &str) {
281+ debug_assert!(self.traversal_is_committed());
152282 debug!("emit: create_element_ns({:?}, {:?})", tag_name, ns);
153283 let tag_name_id = self.ensure_string(tag_name);
154284 let ns_id = self.ensure_string(ns);
@@ -157,7 +287,19 @@ impl ChangeListBuilder<'_> {
157287 .create_element_ns(tag_name_id.into(), ns_id.into());
158288 }
159289
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+
160301 pub fn new_event_listener(&mut self, listener: &Listener) {
302+ debug_assert!(self.traversal_is_committed());
161303 debug!("emit: new_event_listener({:?})", listener);
162304 let (a, b) = listener.get_callback_parts();
163305 debug_assert!(a != 0);
@@ -166,6 +308,13 @@ impl ChangeListBuilder<'_> {
166308 }
167309
168310 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+
169318 debug!("emit: update_event_listener({:?})", listener);
170319 let (a, b) = listener.get_callback_parts();
171320 debug_assert!(a != 0);
@@ -176,8 +325,29 @@ impl ChangeListBuilder<'_> {
176325 }
177326
178327 pub fn remove_event_listener(&mut self, event: &str) {
328+ debug_assert!(self.traversal_is_committed());
179329 debug!("emit: remove_event_listener({:?})", event);
180330 let event_id = self.ensure_string(event);
181331 self.state.emitter.remove_event_listener(event_id.into());
182332 }
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+ }
183353}
src/change_list/strings.rs+4−3
@@ -45,8 +45,7 @@ impl StringsCache {
4545 }
4646
4747 pub fn drop_unused_strings(&mut self, emitter: &InstructionEmitter) {
48− debug!("drop_unused_strings()");
49− self.entries.retain(|_string, entry| {
48+ self.entries.retain(|string, entry| {
5049 if entry.used {
5150 // Since this entry was used during while rendering this frame,
5251 // it is likely going to be used for the next frame as well. So
@@ -57,7 +56,9 @@ impl StringsCache {
5756 entry.used = false;
5857 true
5958 } else {
60− emitter.drop_cached_string(entry.key.into());
59+ let key = entry.key.into();
60+ debug!("emit: drop_cached_string({}) = {:?}", key, string);
61+ emitter.drop_cached_string(key);
6162 false
6263 }
6364 });
src/change_list/traversal.rsnew file+344
@@ -0,0 +1,344 @@
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.rs+894−76
@@ -4,15 +4,26 @@ use crate::{
44 events::EventsRegistry,
55 node::{Attribute, ElementNode, Listener, Node, NodeKind, TextNode},
66};
7−use fxhash::FxHashSet;
8−use std::cmp;
7+use fxhash::{FxHashMap, FxHashSet};
8+use std::cmp::Ordering;
9+use std::u32;
10+use wasm_bindgen::UnwrapThrowExt;
911
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.
1021pub(crate) fn diff(
1122 cached_set: &CachedSet,
1223 change_list: &mut ChangeListBuilder,
1324 registry: &mut EventsRegistry,
14− old: Node,
15− new: Node,
25+ old: &Node,
26+ new: &Node,
1627 cached_roots: &mut FxHashSet<CacheId>,
1728) {
1829 match (&new.kind, &old.kind) {
@@ -20,22 +31,21 @@ pub(crate) fn diff(
2031 &NodeKind::Text(TextNode { text: new_text }),
2132 &NodeKind::Text(TextNode { text: old_text }),
2233 ) => {
23− debug!(" both are text nodes");
2434 if new_text != old_text {
25− debug!(" text needs updating");
35+ change_list.commit_traversal();
2636 change_list.set_text(new_text);
2737 }
2838 }
2939
3040 (&NodeKind::Text(_), &NodeKind::Element(_)) => {
31− debug!(" replacing a text node with an element");
41+ change_list.commit_traversal();
3242 create(cached_set, change_list, registry, new, cached_roots);
3343 registry.remove_subtree(&old);
3444 change_list.replace_with();
3545 }
3646
3747 (&NodeKind::Element(_), &NodeKind::Text(_)) => {
38− debug!(" replacing an element with a text node");
48+ change_list.commit_traversal();
3949 create(cached_set, change_list, registry, new, cached_roots);
4050 // Note: text nodes cannot have event listeners, so we don't need to
4151 // remove the old node's listeners from our registry her.
@@ -44,6 +54,7 @@ pub(crate) fn diff(
4454
4555 (
4656 &NodeKind::Element(ElementNode {
57+ key: _,
4758 tag_name: new_tag_name,
4859 listeners: new_listeners,
4960 attributes: new_attributes,
@@ -51,6 +62,7 @@ pub(crate) fn diff(
5162 namespace: new_namespace,
5263 }),
5364 &NodeKind::Element(ElementNode {
65+ key: _,
5466 tag_name: old_tag_name,
5567 listeners: old_listeners,
5668 attributes: old_attributes,
@@ -58,16 +70,15 @@ pub(crate) fn diff(
5870 namespace: old_namespace,
5971 }),
6072 ) => {
61− debug!(" updating an element");
6273 if new_tag_name != old_tag_name || new_namespace != old_namespace {
63− debug!(" different tag names or namespaces; creating new element and replacing old element");
74+ change_list.commit_traversal();
6475 create(cached_set, change_list, registry, new, cached_roots);
6576 registry.remove_subtree(&old);
6677 change_list.replace_with();
6778 return;
6879 }
6980 diff_listeners(change_list, registry, old_listeners, new_listeners);
70− diff_attributes(change_list, old_attributes, new_attributes);
81+ diff_attributes(change_list, old_attributes, new_attributes, new_namespace.is_some());
7182 diff_children(
7283 cached_set,
7384 change_list,
@@ -87,25 +98,51 @@ pub(crate) fn diff(
8798 return;
8899 }
89100
90− let new = cached_set.get(new.id);
91− let old = cached_set.get(old.id);
92− diff(cached_set, change_list, registry, old, new, cached_roots);
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+ }
93122 }
94123
95124 // New cached node when the old node was not cached. In this scenario,
96125 // we assume that they are pretty different, and it isn't worth diffing
97126 // the subtrees, so we just create the new cached node afresh.
98127 (&NodeKind::Cached(ref c), _) => {
128+ change_list.commit_traversal();
99129 cached_roots.insert(c.id);
100− let new = cached_set.get(c.id);
101− create(cached_set, change_list, registry, new, cached_roots);
102− registry.remove_subtree(&old);
103− change_list.replace_with();
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+ );
104140 }
105141
106142 // Old cached node and new non-cached node. Again, assume that they are
107143 // probably pretty different and create the new non-cached node afresh.
108144 (_, &NodeKind::Cached(_)) => {
145+ change_list.commit_traversal();
109146 create(cached_set, change_list, registry, new, cached_roots);
110147 registry.remove_subtree(&old);
111148 change_list.replace_with();
@@ -113,13 +150,22 @@ pub(crate) fn diff(
113150 }
114151}
115152
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.
116160fn diff_listeners(
117161 change_list: &mut ChangeListBuilder,
118162 registry: &mut EventsRegistry,
119163 old: &[Listener],
120164 new: &[Listener],
121165) {
122− debug!(" updating event listeners");
166+ if !old.is_empty() || !new.is_empty() {
167+ change_list.commit_traversal();
168+ }
123169
124170 'outer1: for new_l in new {
125171 unsafe {
@@ -151,24 +197,33 @@ fn diff_listeners(
151197 }
152198}
153199
154−fn diff_attributes(change_list: &mut ChangeListBuilder, old: &[Attribute], new: &[Attribute]) {
155− debug!(" updating attributes");
156−
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) {
157208 // Do O(n^2) passes to add/update and remove attributes, since
158209 // there are almost always very few attributes.
159210 'outer: for new_attr in new {
160211 if new_attr.is_volatile() {
161− change_list.set_attribute(new_attr.name, new_attr.value);
212+ change_list.commit_traversal();
213+ change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
162214 } else {
163215 for old_attr in old {
164216 if old_attr.name == new_attr.name {
165217 if old_attr.value != new_attr.value {
166− change_list.set_attribute(new_attr.name, new_attr.value);
218+ change_list.commit_traversal();
219+ change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
167220 }
168221 continue 'outer;
169222 }
170223 }
171− change_list.set_attribute(new_attr.name, new_attr.value);
224+
225+ change_list.commit_traversal();
226+ change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
172227 }
173228 }
174229
@@ -178,10 +233,20 @@ fn diff_attributes(change_list: &mut ChangeListBuilder, old: &[Attribute], new:
178233 continue 'outer2;
179234 }
180235 }
236+
237+ change_list.commit_traversal();
181238 change_list.remove_attribute(old_attr.name);
182239 }
183240}
184241
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.
185250fn diff_children(
186251 cached_set: &CachedSet,
187252 change_list: &mut ChangeListBuilder,
@@ -190,83 +255,684 @@ fn diff_children(
190255 new: &[Node],
191256 cached_roots: &mut FxHashSet<CacheId>,
192257) {
193− debug!(" updating children shared by old and new");
194−
195− let num_children_to_diff = cmp::min(new.len(), old.len());
196− let mut new_children = new.iter();
197− let mut old_children = old.iter();
198− let mut pushed = false;
199−
200− for (i, (new_child, old_child)) in new_children
201− .by_ref()
202− .zip(old_children.by_ref())
203− .take(num_children_to_diff)
204− .enumerate()
205− {
206− if i == 0 {
207− change_list.push_first_child();
208− pushed = true;
209− } else {
210− debug_assert!(pushed);
211− change_list.pop_push_next_sibling();
258+ if new.is_empty() {
259+ if !old.is_empty() {
260+ change_list.commit_traversal();
261+ remove_all_children(change_list, registry, old);
212262 }
263+ return;
264+ }
213265
214− diff(
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(
215424 cached_set,
216425 change_list,
217426 registry,
218− old_child.clone(),
219− new_child.clone(),
427+ old_suffix,
428+ new_suffix,
220429 cached_roots,
430+ new_shared_suffix_start,
221431 );
222432 }
433+}
223434
224− if old_children.next().is_some() {
225− debug!(" removing extra old children");
226− debug_assert!(new_children.next().is_none());
227− if pushed {
228− change_list.pop_push_next_sibling();
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
229551 } else {
230− change_list.push_first_child();
552+ u32::MAX as usize
231553 }
232− change_list.remove_self_and_next_siblings();
233− pushed = false;
234− } else {
235− debug!(" creating new children");
236− for (i, new_child) in new_children.enumerate() {
237− if i == 0 && pushed {
238− change_list.pop();
239− pushed = false;
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;
240593 }
241− create(
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(
242670 cached_set,
243671 change_list,
244672 registry,
245− new_child.clone(),
673+ &old[old_index],
674+ last,
246675 cached_roots,
247676 );
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]
248692 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+ );
249735 }
250736 }
251737
252− debug!(" done updating children");
253− if pushed {
254− change_list.pop();
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);
255886 }
887+ // Fast way to remove all children: set the node's textContent to an empty
888+ // string.
889+ change_list.set_text("");
256890}
257891
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]
258922fn create(
259923 cached_set: &CachedSet,
260924 change_list: &mut ChangeListBuilder,
261925 registry: &mut EventsRegistry,
262− node: Node,
926+ node: &Node,
263927 cached_roots: &mut FxHashSet<CacheId>,
264928) {
929+ debug_assert!(change_list.traversal_is_committed());
265930 match node.kind {
266931 NodeKind::Text(TextNode { text }) => {
267932 change_list.create_text_node(text);
268933 }
269− NodeKind::Element(ElementNode {
934+ NodeKind::Element(&ElementNode {
935+ key: _,
270936 tag_name,
271937 listeners,
272938 attributes,
@@ -278,30 +944,182 @@ fn create(
278944 } else {
279945 change_list.create_element(tag_name);
280946 }
947+
281948 for l in listeners {
282949 unsafe {
283950 registry.add(l);
284951 }
285952 change_list.new_event_listener(l);
286953 }
954+
287955 for attr in attributes {
288− change_list.set_attribute(&attr.name, &attr.value);
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+ }
289973 }
974+
290975 for child in children {
291− create(
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(
292985 cached_set,
293986 change_list,
294987 registry,
295− child.clone(),
988+ template,
989+ node,
296990 cached_roots,
297991 );
298− change_list.append_child();
992+ } else {
993+ create(cached_set, change_list, registry, node, cached_roots);
299994 }
300995 }
301− NodeKind::Cached(c) => {
302− cached_roots.insert(c.id);
303− let node = cached_set.get(c.id);
304− create(cached_set, change_list, registry, node, cached_roots)
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);
3051076 }
1077+
1078+ change_list.commit_traversal();
1079+ } else {
1080+ create(cached_set, change_list, registry, new, cached_roots);
1081+ change_list.replace_with();
3061082 }
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();
3071125}
src/events.rs+9−18
@@ -19,7 +19,6 @@ cfg_if::cfg_if! {
1919 pub(crate) fn clear_active_listeners(&mut self) {}
2020 }
2121 } else {
22− use bumpalo::Bump;
2322 use crate::{
2423 node::{ElementNode, ListenerCallback, NodeKind},
2524 vdom::VdomWeak,
@@ -64,7 +63,6 @@ cfg_if::cfg_if! {
6463
6564 let weak_registry = Rc::downgrade(&registry);
6665 let closure = Closure::wrap(Box::new(move |event, a, b| {
67− debug!("Events trampoline invoked with (0x{:x}, 0x{:x})", a, b);
6866 debug_assert!(a != 0);
6967
7068 // if the VdomInnerExclusive is keeping this closure alive, then the
@@ -88,31 +86,26 @@ cfg_if::cfg_if! {
8886 callback(component, vdom_weak, event);
8987 }
9088 }
91− }) as Box<Fn(web_sys::Event, u32, u32)>);
89+ }) as Box<dyn Fn(web_sys::Event, u32, u32)>);
9290
9391 (registry, closure)
9492 }
9593
9694 pub(crate) fn remove(&mut self, listener: &Listener) {
9795 let id = listener.get_callback_parts();
98− debug!("EventsRegistry::remove(0x{:x}, 0x{:x})", id.0, id.1);
9996 debug_assert!(id.0 != 0);
10097 self.active.remove(&id);
10198 }
10299
103100 pub(crate) fn remove_subtree(&mut self, node: &Node) {
104− let bump = Bump::new();
105− let mut stack = bumpalo::collections::Vec::with_capacity_in(64, &bump);
106− stack.push(node);
107−
108− while let Some(node) = stack.pop() {
109− match node.kind {
110− NodeKind::Cached(_) | NodeKind::Text(_) => continue,
111− NodeKind::Element(ElementNode {listeners, children, ..}) => {
112− for l in listeners {
113− self.remove(l);
114− }
115− stack.extend(children);
101+ match node.kind {
102+ NodeKind::Cached(_) | NodeKind::Text(_) => {},
103+ NodeKind::Element(&ElementNode {listeners, children, ..}) => {
104+ for l in listeners {
105+ self.remove(l);
106+ }
107+ for child in children {
108+ self.remove_subtree(child)
116109 }
117110 }
118111 }
@@ -128,7 +121,6 @@ cfg_if::cfg_if! {
128121 /// diffing.
129122 pub(crate) unsafe fn add<'a>(&mut self, listener: &'a Listener<'a>) {
130123 let id = listener.get_callback_parts();
131− debug!("EventsRegistry::add(0x{:x}, 0x{:x})", id.0, id.1);
132124 debug_assert!(id.0 != 0);
133125
134126 let callback =
@@ -139,7 +131,6 @@ cfg_if::cfg_if! {
139131
140132 /// Clear all event listeners from the registry.
141133 pub(crate) fn clear_active_listeners(&mut self) {
142− debug!("EventsRegistry::clear_active_listeners()");
143134 self.active.clear();
144135 }
145136 }
src/lib.rs+5−4
@@ -18,8 +18,8 @@
1818//! }
1919//! }
2020//!
21−//! impl<'who> Render for Hello<'who> {
22−//! fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
21+//! impl<'a, 'who> Render<'a> for Hello<'who> {
22+//! fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
2323//! use dodrio::builder::*;
2424//!
2525//! let id = bumpalo::format!(in cx.bump, "hello-{}", self.who);
@@ -92,13 +92,14 @@ mod events;
9292mod node;
9393mod render;
9494mod render_context;
95+mod strace;
9596mod vdom;
9697
9798pub mod builder;
9899
99100// Re-export items at the top level.
100101pub use self::cached::Cached;
101−pub use self::node::{Attribute, Listener, Node};
102+pub use self::node::{Attribute, Listener, Node, NodeKey};
102103pub use self::render::{Render, RootRender};
103104pub use self::render_context::RenderContext;
104105pub use self::vdom::{Vdom, VdomWeak};
@@ -122,7 +123,7 @@ cfg_if::cfg_if! {
122123 /// An element node in the physical DOM.
123124 pub type Element = web_sys::Element;
124125
125− pub(crate) type EventsTrampoline = wasm_bindgen::closure::Closure<Fn(web_sys::Event, u32, u32)>;
126+ pub(crate) type EventsTrampoline = wasm_bindgen::closure::Closure<dyn Fn(web_sys::Event, u32, u32)>;
126127 }
127128}
128129
src/node.rs+83−39
@@ -2,8 +2,8 @@ use crate::{cached_set::CacheId, RootRender, VdomWeak};
22use bumpalo::Bump;
33use std::fmt;
44use std::iter;
5−use std::marker::PhantomData;
65use std::mem;
6+use std::u32;
77
88/// A virtual DOM node.
99#[derive(Debug, Clone)]
@@ -24,11 +24,11 @@ pub_unstable_internal! {
2424 Text(TextNode<'a>),
2525
2626 /// An element potentially with attributes and children.
27− Element(ElementNode<'a>),
27+ Element(&'a ElementNode<'a>),
2828
2929 /// A node in the vdom's `CachedSet`. This allows us to avoid
3030 /// re-rendering and re-diffing subtrees.
31− Cached(CachedNode<'a>),
31+ Cached(CachedNode),
3232 }
3333}
3434
@@ -46,6 +46,7 @@ pub_unstable_internal! {
4646 /// children.
4747 #[derive(Debug, Clone)]
4848 pub(crate) struct ElementNode<'a> {
49+ pub key: NodeKey,
4950 pub tag_name: &'a str,
5051 pub listeners: &'a [Listener<'a>],
5152 pub attributes: &'a [Attribute<'a>],
@@ -59,10 +60,51 @@ pub_unstable_internal! {
5960 /// allows us to avoid both re-rendering a sub-tree and re-diffing
6061 /// it. `CachedNode`s cannot contain cycles, since they can only contain
6162 /// other `CachedNode`s that already exist when they are being constructed.
62− #[derive(Debug, Clone)]
63− pub(crate) struct CachedNode<'a> {
63+ #[derive(Debug, Clone, Copy)]
64+ pub(crate) struct CachedNode {
6465 pub id: CacheId,
65− pub _phantom: PhantomData<&'a Node<'a>>,
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+}
84+
85+impl NodeKey {
86+ /// The default, lack of a key.
87+ pub const NONE: NodeKey = NodeKey(u32::MAX);
88+
89+ /// Is this key `NodeKey::NONE`?
90+ #[inline]
91+ pub fn is_none(&self) -> bool {
92+ *self == Self::NONE
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)
66108 }
67109}
68110
@@ -92,6 +134,15 @@ pub struct Attribute<'a> {
92134 pub(crate) value: &'a str,
93135}
94136
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+
95146impl fmt::Debug for Listener<'_> {
96147 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
97148 let (a, b) = self.get_callback_parts();
@@ -131,38 +182,33 @@ impl<'a> Attribute<'a> {
131182}
132183
133184impl<'a> Node<'a> {
134− /// Construct a new Node of type element with given tag name and children
185+ /// Low-level constructor for making a new `Node` of type element with given
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.
135191 #[inline]
136− pub(crate) fn element<Listeners, Attributes, Children>(
192+ pub fn element(
137193 bump: &'a Bump,
194+ key: NodeKey,
138195 tag_name: &'a str,
139− listeners: Listeners,
140− attributes: Attributes,
141− children: Children,
196+ listeners: &'a [Listener<'a>],
197+ attributes: &'a [Attribute<'a>],
198+ children: &'a [Node<'a>],
142199 namespace: Option<&'a str>,
143− ) -> Node<'a>
144− where
145− Listeners: 'a + AsRef<[Listener<'a>]>,
146− Attributes: 'a + AsRef<[Attribute<'a>]>,
147− Children: 'a + AsRef<[Node<'a>]>,
148− {
149− let children: &'a Children = bump.alloc(children);
150− let children: &'a [Node<'a>] = children.as_ref();
151−
152− let listeners: &'a Listeners = bump.alloc(listeners);
153− let listeners: &'a [Listener<'a>] = listeners.as_ref();
154−
155− let attributes: &'a Attributes = bump.alloc(attributes);
156− let attributes: &'a [Attribute<'a>] = attributes.as_ref();
200+ ) -> Node<'a> {
201+ let element = bump.alloc_with(|| ElementNode {
202+ key,
203+ tag_name,
204+ listeners,
205+ attributes,
206+ children,
207+ namespace,
208+ });
157209
158210 Node {
159− kind: NodeKind::Element(ElementNode {
160− tag_name,
161− listeners,
162− attributes,
163− children,
164− namespace,
165− }),
211+ kind: NodeKind::Element(element),
166212 }
167213 }
168214
@@ -174,14 +220,12 @@ impl<'a> Node<'a> {
174220 }
175221 }
176222
177− /// Construct a new cached node with the given id.
178223 #[inline]
179− pub(crate) fn cached(id: CacheId) -> Node<'a> {
180− Node {
181− kind: NodeKind::Cached(CachedNode {
182− id,
183− _phantom: PhantomData,
184− }),
224+ pub(crate) fn key(&self) -> NodeKey {
225+ match &self.kind {
226+ NodeKind::Text(_) => NodeKey::NONE,
227+ NodeKind::Element(e) => e.key,
228+ NodeKind::Cached(c) => c.key,
185229 }
186230 }
187231}
src/render.rs+43−16
@@ -21,8 +21,8 @@ use wasm_bindgen::UnwrapThrowExt;
2121///
2222/// pub struct MyComponent;
2323///
24−/// impl Render for MyComponent {
25−/// fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
24+/// impl<'a> Render<'a> for MyComponent {
25+/// fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
2626/// use dodrio::builder::*;
2727///
2828/// p(&cx)
@@ -35,26 +35,26 @@ use wasm_bindgen::UnwrapThrowExt;
3535/// }
3636/// }
3737/// ```
38−pub trait Render {
38+pub trait Render<'a> {
3939 /// Render `self` as a virtual DOM. Use the given context's `Bump` for
4040 /// temporary allocations.
41− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a>;
41+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a>;
4242}
4343
44−impl<'r, R> Render for &'r R
44+impl<'a, 'r, R> Render<'a> for &'r R
4545where
46− R: Render,
46+ R: Render<'a>,
4747{
48− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
48+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
4949 (**self).render(cx)
5050 }
5151}
5252
53−impl<R> Render for Rc<R>
53+impl<'a, R> Render<'a> for Rc<R>
5454where
55− R: Render,
55+ R: Render<'a>,
5656{
57− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
57+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
5858 (**self).render(cx)
5959 }
6060}
@@ -69,24 +69,24 @@ where
6969/// You do not need to implement this trait by hand: there is a blanket
7070/// implementation for all `Render` types that fulfill the `RootRender`
7171/// requirements.
72−pub trait RootRender: Any + Render {
72+pub trait RootRender: Any + for<'a> Render<'a> {
7373 /// Get this `&RootRender` trait object as an `&Any` trait object reference.
74− fn as_any(&self) -> &Any;
74+ fn as_any(&self) -> &dyn Any;
7575
7676 /// Get this `&mut RootRender` trait object as an `&mut Any` trait object
7777 /// reference.
78− fn as_any_mut(&mut self) -> &mut Any;
78+ fn as_any_mut(&mut self) -> &mut dyn Any;
7979}
8080
8181impl<T> RootRender for T
8282where
83− T: Any + Render,
83+ T: Any + for<'a> Render<'a>,
8484{
85− fn as_any(&self) -> &Any {
85+ fn as_any(&self) -> &dyn Any {
8686 self
8787 }
8888
89− fn as_any_mut(&mut self) -> &mut Any {
89+ fn as_any_mut(&mut self) -> &mut dyn Any {
9090 self
9191 }
9292}
@@ -138,4 +138,31 @@ mod tests {
138138 #[allow(dead_code)]
139139 fn takes_dyn_render(_: &dyn super::RootRender) {}
140140 }
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+ }
141168}
src/render_context.rs+43−2
@@ -1,5 +1,10 @@
1−use crate::cached_set::CachedSet;
1+use crate::{
2+ cached::{Cached, TemplateId},
3+ cached_set::{CacheId, CachedSet},
4+ Node, Render,
5+};
26use bumpalo::Bump;
7+use fxhash::FxHashMap;
38use std::fmt;
49
510/// Common context available to all `Render` implementations.
@@ -23,6 +28,8 @@ pub struct RenderContext<'a> {
2328
2429 pub(crate) cached_set: &'a crate::RefCell<CachedSet>,
2530
31+ pub(crate) templates: &'a mut FxHashMap<TemplateId, Option<CacheId>>,
32+
2633 // Prevent exhaustive matching on the rendering context, so we can always
2734 // add more members in a semver-compatible way.
2835 _non_exhaustive: (),
@@ -38,14 +45,48 @@ impl fmt::Debug for RenderContext<'_> {
3845
3946impl<'a> RenderContext<'a> {
4047 pub_unstable_internal! {
41− pub(crate) fn new(bump: &'a Bump, cached_set: &'a crate::RefCell<CachedSet>) -> Self {
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 {
4253 RenderContext {
4354 bump,
4455 cached_set,
56+ templates,
4557 _non_exhaustive: (),
4658 }
4759 }
4860 }
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+ }
4990}
5091
5192impl<'a, 'b> From<&'b RenderContext<'a>> for &'a Bump {
src/strace.rsnew file+22
@@ -0,0 +1,22 @@
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+96−85
@@ -1,14 +1,16 @@
11use super::change_list::ChangeListPersistentState;
22use super::RootRender;
3−use crate::cached_set::CachedSet;
3+use crate::cached::TemplateId;
4+use crate::cached_set::{CacheId, CachedSet};
45use crate::events::EventsRegistry;
5−use crate::node::Node;
6+use crate::node::{Node, NodeKey};
67use crate::RenderContext;
78use bumpalo::Bump;
8−use futures::future::Future;
9+use fxhash::FxHashMap;
910use std::cell::Cell;
1011use std::cell::RefCell;
1112use std::fmt;
13+use std::future::Future;
1214use std::mem;
1315use std::mem::ManuallyDrop;
1416use std::rc::{Rc, Weak};
@@ -53,7 +55,7 @@ pub(crate) struct VdomInnerExclusive {
5355 // Always `Some` except just before we drop. Just an option so that
5456 // `unmount` can take the component out but we can still have a Drop
5557 // implementation.
56− component: Option<Box<RootRender>>,
58+ component: Option<Box<dyn RootRender>>,
5759
5860 dom_buffers: Option<[Bump; 2]>,
5961 change_list: ManuallyDrop<ChangeListPersistentState>,
@@ -61,6 +63,7 @@ pub(crate) struct VdomInnerExclusive {
6163 events_registry: Option<Rc<RefCell<EventsRegistry>>>,
6264 events_trampoline: Option<crate::EventsTrampoline>,
6365 cached_set: crate::RefCell<CachedSet>,
66+ templates: FxHashMap<TemplateId, Option<CacheId>>,
6467
6568 // Actually a reference into `self.dom_buffers[0]` or if `self.component` is
6669 // caching renders, into `self.component`'s bump.
@@ -155,18 +158,24 @@ impl Vdom {
155158 where
156159 R: RootRender,
157160 {
158− Self::with_boxed_root_render(container, Box::new(component) as Box<RootRender>)
161+ Self::with_boxed_root_render(container, Box::new(component) as Box<dyn RootRender>)
159162 }
160163
161164 /// Construct a `Vdom` with the already-boxed-as-a-trait-object root
162165 /// rendering component.
163− pub fn with_boxed_root_render(container: &crate::Element, component: Box<RootRender>) -> Vdom {
166+ pub fn with_boxed_root_render(
167+ container: &crate::Element,
168+ component: Box<dyn RootRender>,
169+ ) -> Vdom {
170+ crate::strace::init_strace();
171+
164172 let dom_buffers = [Bump::new(), Bump::new()];
165173 let change_list = ManuallyDrop::new(ChangeListPersistentState::new(container));
166174
167175 // Create a dummy `<div/>` in our container.
168176 initialize_container(container);
169− let current_root = Node::element(&dom_buffers[0], "div", [], [], [], None);
177+ let current_root =
178+ Node::element(&dom_buffers[0], NodeKey::NONE, "div", &[], &[], &[], None);
170179 let current_root = Some(unsafe { extend_node_lifetime(current_root) });
171180
172181 let container = container.clone();
@@ -183,6 +192,7 @@ impl Vdom {
183192 events_registry: None,
184193 events_trampoline: None,
185194 cached_set: crate::RefCell::new(Default::default()),
195+ templates: Default::default(),
186196 }),
187197 });
188198
@@ -210,7 +220,7 @@ impl Vdom {
210220 R: RootRender,
211221 {
212222 let mut exclusive = self.inner.exclusive.borrow_mut();
213− let component = Box::new(component) as Box<RootRender>;
223+ let component = Box::new(component) as Box<dyn RootRender>;
214224 exclusive.component = Some(component);
215225 exclusive.render();
216226 }
@@ -230,7 +240,7 @@ impl Vdom {
230240 /// Unmount this virtual DOM, unregister its event listeners, and return its
231241 /// root render component.
232242 #[inline]
233− pub fn unmount(self) -> Box<RootRender> {
243+ pub fn unmount(self) -> Box<dyn RootRender> {
234244 Rc::try_unwrap(self.inner.clone())
235245 .map_err(|_| ())
236246 .unwrap_throw()
@@ -262,7 +272,8 @@ impl VdomInnerExclusive {
262272 dom_buffers[1].reset();
263273
264274 // Render the new current contents into the inactive bump arena.
265− let mut cx = RenderContext::new(&dom_buffers[1], &self.cached_set);
275+ let mut cx =
276+ RenderContext::new(&dom_buffers[1], &self.cached_set, &mut self.templates);
266277 let new_contents = self.component.as_ref().unwrap_throw().render(&mut cx);
267278 let new_contents = extend_node_lifetime(new_contents);
268279
@@ -277,8 +288,8 @@ impl VdomInnerExclusive {
277288 &cached_set,
278289 &mut change_list,
279290 &mut registry,
280− old_contents,
281− new_contents.clone(),
291+ &old_contents,
292+ &new_contents,
282293 &mut cache_roots,
283294 );
284295
@@ -315,7 +326,7 @@ impl VdomInnerExclusive {
315326 }
316327}
317328
318−fn request_animation_frame(f: &Closure<FnMut()>) {
329+fn request_animation_frame(f: &Closure<dyn FnMut()>) {
319330 web_sys::window()
320331 .expect_throw("should have a window")
321332 .request_animation_frame(f.as_ref().unchecked_ref())
@@ -332,7 +343,7 @@ where
332343 let f = Closure::wrap(Box::new(move || {
333344 *g.borrow_mut() = None;
334345 f();
335− }) as Box<FnMut()>);
346+ }) as Box<dyn FnMut()>);
336347 request_animation_frame(&f);
337348
338349 *h.borrow_mut() = Some(f);
@@ -363,25 +374,23 @@ impl VdomWeak {
363374 /// Replace the root rendering component with the new `root`.
364375 ///
365376 /// Returns a future that resolves to the *old* root component.
366− pub fn set_component(
377+ pub async fn set_component(
367378 self,
368379 root: Box<dyn RootRender>,
369− ) -> impl Future<Item = Box<dyn RootRender + 'static>, Error = VdomDroppedError> {
370− futures::future::ok(self.inner.upgrade())
371− .and_then(|inner| inner.ok_or(()))
372− .map_err(|_| VdomDroppedError {})
373− .and_then(|inner| {
374− let promise = js_sys::Promise::resolve(&JsValue::null());
375− JsFuture::from(promise)
376− .map_err(|_| VdomDroppedError {})
377− .and_then(move |_| {
378− let old = {
379− let mut exclusive = inner.exclusive.borrow_mut();
380− mem::replace(&mut *exclusive.component.as_mut().unwrap_throw(), root)
381− };
382− VdomWeak::new(&inner).render().map(|_| old)
383− })
384− })
380+ ) -> Result<Box<dyn RootRender + 'static>, VdomDroppedError> {
381+ let inner = self.inner.upgrade().ok_or(VdomDroppedError {})?;
382+
383+ // Wait for a new tick of the micro-task queue
384+ let _ = JsFuture::from(js_sys::Promise::resolve(&JsValue::null())).await;
385+
386+ let old = {
387+ let mut exclusive = inner.exclusive.borrow_mut();
388+ mem::replace(&mut *exclusive.component.as_mut().unwrap_throw(), root)
389+ };
390+
391+ VdomWeak::new(&inner).render().await?;
392+
393+ Ok(old)
385394 }
386395
387396 /// Execute `f` with a reference to this virtual DOM's root rendering
@@ -389,24 +398,18 @@ impl VdomWeak {
389398 ///
390399 /// To ensure exclusive access to the root rendering component, the
391400 /// invocation takes place on a new tick of the micro-task queue.
392− pub fn with_component<F, T>(&self, f: F) -> impl Future<Item = T, Error = VdomDroppedError>
401+ pub async fn with_component<F, T>(&self, f: F) -> Result<T, VdomDroppedError>
393402 where
394403 F: 'static + FnOnce(&mut dyn RootRender) -> T,
395404 {
396− futures::future::ok(self.inner.upgrade())
397− .and_then(|inner| inner.ok_or(()))
398− .map_err(|_| VdomDroppedError {})
399− .and_then(|inner| {
400− let mut f = Some(f);
401− let promise = js_sys::Promise::resolve(&JsValue::null());
402− JsFuture::from(promise)
403− .map_err(|_| VdomDroppedError {})
404− .map(move |_| {
405− let f = f.take().unwrap_throw();
406− let mut exclusive = inner.exclusive.borrow_mut();
407− f(exclusive.component_raw_mut())
408− })
409− })
405+ let inner = self.inner.upgrade().ok_or(VdomDroppedError {})?;
406+
407+ // Wait for a new tick of the micro-task queue
408+ let _ = JsFuture::from(js_sys::Promise::resolve(&JsValue::null())).await;
409+
410+ let mut exclusive = inner.exclusive.borrow_mut();
411+
412+ Ok(f(exclusive.component_raw_mut()))
410413 }
411414
412415 /// Schedule a render to occur during the next animation frame.
@@ -415,7 +418,12 @@ impl VdomWeak {
415418 /// `render` instead.
416419 pub fn schedule_render(&self) {
417420 debug!("VdomWeak::schedule_render");
418− wasm_bindgen_futures::spawn_local(self.render().map_err(|_| ()));
421+
422+ let future = self.render();
423+
424+ wasm_bindgen_futures::spawn_local(async move {
425+ let _ = future.await;
426+ });
419427 }
420428
421429 /// Schedule a render to occur during the next animation frame and return a
@@ -423,43 +431,46 @@ impl VdomWeak {
423431 ///
424432 /// If you don't want to do more things after the render completes, then use
425433 /// `schedule_render` instead of `render`.
426− pub fn render(&self) -> impl Future<Item = (), Error = VdomDroppedError> {
427− debug!("VdomWeak::render: initiating render in new animation frame");
428− futures::future::ok(self.inner.upgrade())
429− .and_then(|inner| inner.ok_or(()))
430− .map_err(|_| VdomDroppedError {})
431− .and_then(|inner| {
432− let promise = inner.shared.render_scheduled.take().unwrap_or_else(|| {
433− js_sys::Promise::new(&mut |resolve, reject| {
434− let vdom = VdomWeak {
435− inner: Rc::downgrade(&inner),
436− };
437− with_animation_frame(move || match vdom.inner.upgrade() {
438− None => {
439− warn!("VdomWeak::render: vdom unmounted before we could render");
440− let r = reject.call0(&JsValue::null());
441− debug_assert!(r.is_ok());
442− }
443− Some(inner) => {
444− let mut exclusive = inner.exclusive.borrow_mut();
445− exclusive.render();
446−
447− // We did the render, so take the promise away
448− // and let future `render` calls request new
449− // animation frames.
450− let _ = inner.shared.render_scheduled.take();
451−
452− debug!("VdomWeak::render: finished rendering");
453− let r = resolve.call0(&JsValue::null());
454− debug_assert!(r.is_ok());
455− }
456− });
457− })
458− });
459− inner.shared.render_scheduled.set(Some(promise.clone()));
460− JsFuture::from(promise)
461− .map(|_| ())
462− .map_err(|_| VdomDroppedError {})
463− })
434+ pub fn render(&self) -> impl Future<Output = Result<(), VdomDroppedError>> {
435+ let inner = self.inner.upgrade();
436+
437+ async move {
438+ let inner = inner.ok_or(VdomDroppedError {})?;
439+
440+ let promise = inner.shared.render_scheduled.take().unwrap_or_else(|| {
441+ js_sys::Promise::new(&mut |resolve, reject| {
442+ let vdom = VdomWeak {
443+ inner: Rc::downgrade(&inner),
444+ };
445+
446+ with_animation_frame(move || match vdom.inner.upgrade() {
447+ None => {
448+ warn!("VdomWeak::render: vdom unmounted before we could render");
449+ let r = reject.call0(&JsValue::null());
450+ debug_assert!(r.is_ok());
451+ }
452+ Some(inner) => {
453+ let mut exclusive = inner.exclusive.borrow_mut();
454+ exclusive.render();
455+
456+ // We did the render, so take the promise away
457+ // and let future `render` calls request new
458+ // animation frames.
459+ let _ = inner.shared.render_scheduled.take();
460+
461+ let r = resolve.call0(&JsValue::null());
462+ debug_assert!(r.is_ok());
463+ }
464+ });
465+ })
466+ });
467+
468+ inner.shared.render_scheduled.set(Some(promise.clone()));
469+
470+ JsFuture::from(promise)
471+ .await
472+ .map(drop)
473+ .map_err(|_| VdomDroppedError {})
474+ }
464475 }
465476}
tests/web/cached.jsnew file+11
@@ -0,0 +1,11 @@
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+103−48
@@ -1,11 +1,11 @@
11use super::{assert_rendered, before_after, create_element, RenderFn};
2−use dodrio::{builder::*, bumpalo, Cached, Node, Render, RenderContext, Vdom};
3−use futures::prelude::*;
2+use dodrio::{builder::*, bumpalo, Cached, Node, Render, RenderContext, RootRender, Vdom};
43use std::cell::Cell;
54use std::rc::Rc;
6−use wasm_bindgen::*;
5+use wasm_bindgen::prelude::*;
76use wasm_bindgen_test::*;
87
8+#[derive(Default)]
99pub struct CountRenders {
1010 render_count: Cell<usize>,
1111}
@@ -18,8 +18,8 @@ impl CountRenders {
1818 }
1919}
2020
21−impl Render for CountRenders {
22− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
21+impl<'a> Render<'a> for CountRenders {
22+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
2323 let count = self.render_count.get() + 1;
2424 self.render_count.set(count);
2525
@@ -28,8 +28,8 @@ impl Render for CountRenders {
2828 }
2929}
3030
31−#[wasm_bindgen_test(async)]
32−fn uses_cached_render() -> impl Future<Item = (), Error = JsValue> {
31+#[wasm_bindgen_test]
32+async fn uses_cached_render() {
3333 use dodrio::builder::*;
3434
3535 let cached = Cached::new(CountRenders::new());
@@ -49,57 +49,112 @@ fn uses_cached_render() -> impl Future<Item = (), Error = JsValue> {
4949 let vdom6 = vdom0.clone();
5050 let vdom7 = vdom0.clone();
5151
52+ // We render, populate the cache, and get "1".
53+ vdom0.weak().render().await.unwrap();
5254 vdom0
5355 .weak()
54− // We render, populate the cache, and get "1".
55− .render()
56− .and_then(move |_| {
57− vdom0.weak().with_component(move |comp| {
58− let comp = comp.unwrap_mut::<Cached<CountRenders>>();
59− assert_eq!(comp.render_count.get(), 1);
60− assert_rendered(&container1, &RenderFn(|_| text("1")));
61− })
56+ .with_component(move |comp| {
57+ let comp = comp.unwrap_mut::<Cached<CountRenders>>();
58+ assert_eq!(comp.render_count.get(), 1);
59+ assert_rendered(&container1, &RenderFn(|_| text("1")));
6260 })
63− // We re-render, re-use the cached node, and get "1" again.
64− .and_then(move |_| vdom1.weak().render())
65− .and_then(move |_| {
66− vdom2.weak().with_component(move |comp| {
67− let comp = comp.unwrap_mut::<Cached<CountRenders>>();
68− assert_eq!(comp.render_count.get(), 1);
69− assert_rendered(&container2, &RenderFn(|_| text("1")));
70− })
61+ .await
62+ .unwrap();
63+
64+ // We re-render, re-use the cached node, and get "1" again.
65+ vdom1.weak().render().await.unwrap();
66+ vdom2
67+ .weak()
68+ .with_component(move |comp| {
69+ let comp = comp.unwrap_mut::<Cached<CountRenders>>();
70+ assert_eq!(comp.render_count.get(), 1);
71+ assert_rendered(&container2, &RenderFn(|_| text("1")));
7172 })
72− // We invalidate the cache, re-render, re-populate the cache, and should
73− // now have "2".
74− .and_then(move |_| {
75− vdom3.weak().with_component(move |comp| {
76− let comp = comp.unwrap_mut::<Cached<CountRenders>>();
77− Cached::invalidate(comp);
78− })
73+ .await
74+ .unwrap();
75+
76+ // We invalidate the cache, re-render, re-populate the cache, and should now have "2".
77+ vdom3
78+ .weak()
79+ .with_component(move |comp| {
80+ let comp = comp.unwrap_mut::<Cached<CountRenders>>();
81+ Cached::invalidate(comp);
7982 })
80− .and_then(move |_| vdom4.weak().render())
81− .and_then(move |_| {
82− vdom5.weak().with_component(move |comp| {
83− let comp = comp.unwrap_mut::<Cached<CountRenders>>();
84− assert_eq!(comp.render_count.get(), 2);
85− assert_rendered(&container3, &RenderFn(|_| text("2")));
86− })
83+ .await
84+ .unwrap();
85+ vdom4.weak().render().await.unwrap();
86+ vdom5
87+ .weak()
88+ .with_component(move |comp| {
89+ let comp = comp.unwrap_mut::<Cached<CountRenders>>();
90+ assert_eq!(comp.render_count.get(), 2);
91+ assert_rendered(&container3, &RenderFn(|_| text("2")));
8792 })
88− // We re-render, re-use the cached node, and get "2" again.
89− .and_then(move |_| vdom6.weak().render())
90− .and_then(move |_| {
91− vdom7.weak().with_component(move |comp| {
92− let comp = comp.unwrap_mut::<Cached<CountRenders>>();
93− assert_eq!(comp.render_count.get(), 2);
94− assert_rendered(&container4, &RenderFn(|_| text("2")));
95− })
93+ .await
94+ .unwrap();
95+
96+ // We re-render, re-use the cached node, and get "2" again.
97+ vdom6.weak().render().await.unwrap();
98+ vdom7
99+ .weak()
100+ .with_component(move |comp| {
101+ let comp = comp.unwrap_mut::<Cached<CountRenders>>();
102+ assert_eq!(comp.render_count.get(), 2);
103+ assert_rendered(&container4, &RenderFn(|_| text("2")));
96104 })
97− .map_err(|e| JsValue::from(e.to_string()))
105+ .await
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);
98146}
99147
100148struct Id(&'static str);
101−impl Render for Id {
102− fn render<'a>(&self, _cx: &mut RenderContext<'a>) -> Node<'a> {
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> {
103158 text(self.0)
104159 }
105160}
tests/web/events.rs+56−73
@@ -1,6 +1,6 @@
11use super::create_element;
22use dodrio::{Node, Render, RenderContext, Vdom};
3−use futures::Future;
3+use futures::future::{select, Either};
44use std::cell::{Cell, RefCell};
55use std::rc::Rc;
66use wasm_bindgen::prelude::*;
@@ -9,7 +9,7 @@ use wasm_bindgen_test::*;
99
1010struct EventContainer {
1111 event: &'static str,
12− on_event: Box<FnMut()>,
12+ on_event: Box<dyn FnMut()>,
1313}
1414
1515impl EventContainer {
@@ -24,8 +24,8 @@ impl EventContainer {
2424 }
2525}
2626
27−impl Render for EventContainer {
28− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
27+impl<'a> Render<'a> for EventContainer {
28+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
2929 use dodrio::builder::*;
3030 div(&cx)
3131 .attr("id", "target")
@@ -44,14 +44,14 @@ fn target(container: &web_sys::Element) -> web_sys::HtmlElement {
4444 .unchecked_into()
4545}
4646
47−#[wasm_bindgen_test(async)]
48−fn click() -> impl Future<Item = (), Error = JsValue> {
47+#[wasm_bindgen_test]
48+async fn click() {
4949 let container = create_element("div");
5050
51− let (sender, receiver) = futures::sync::oneshot::channel();
51+ let (sender, receiver) = futures::channel::oneshot::channel();
5252 let mut sender = Some(sender);
5353
54− let vdom = Vdom::new(
54+ let _vdom = Vdom::new(
5555 &container,
5656 EventContainer::new("click", move || {
5757 sender
@@ -64,18 +64,14 @@ fn click() -> impl Future<Item = (), Error = JsValue> {
6464
6565 target(&container).click();
6666
67− receiver
68− .map(|_| {
69− drop(vdom);
70− })
71− .map_err(|e| JsValue::from(e.to_string()))
67+ receiver.await.unwrap();
7268}
7369
74−#[wasm_bindgen_test(async)]
75−fn updated_listener_is_called() -> impl Future<Item = (), Error = JsValue> {
70+#[wasm_bindgen_test]
71+async fn updated_listener_is_called() {
7672 let container = create_element("div");
7773
78− let (first_sender, first_receiver) = futures::sync::oneshot::channel();
74+ let (first_sender, first_receiver) = futures::channel::oneshot::channel();
7975 let mut first_sender = Some(first_sender);
8076
8177 let vdom = Vdom::new(
@@ -89,7 +85,7 @@ fn updated_listener_is_called() -> impl Future<Item = (), Error = JsValue> {
8985 }),
9086 );
9187
92− let (second_sender, second_receiver) = futures::sync::oneshot::channel();
88+ let (second_sender, second_receiver) = futures::channel::oneshot::channel();
9389 let mut second_sender = Some(second_sender);
9490
9591 vdom.weak()
@@ -100,25 +96,22 @@ fn updated_listener_is_called() -> impl Future<Item = (), Error = JsValue> {
10096 .send("second")
10197 .expect_throw("should not have dropped the receiver");
10298 })))
103− .map_err(|e| JsValue::from(e.to_string()))
104− .and_then(move |_| {
105− target(&container).click();
106−
107− first_receiver
108− .select(second_receiver)
109− .map(|(which, _other)| which)
110− .or_else(|(_error, other)| other)
111− .map(|which| {
112− assert_eq!(which, "second");
113− drop(vdom);
114− })
115− .map_err(|e| JsValue::from(e.to_string()))
116− })
99+ .await
100+ .unwrap();
101+
102+ target(&container).click();
103+
104+ match select(first_receiver, second_receiver).await {
105+ Either::Left((Ok(_), _)) => panic!(),
106+ Either::Left((Err(_), second)) => assert_eq!(second.await, Ok("second")),
107+ Either::Right((Ok(msg), _)) => assert_eq!(msg, "second"),
108+ Either::Right((Err(_), _)) => panic!(),
109+ }
117110}
118111
119112struct ListensOnlyOnFirstRender {
120113 count: Cell<usize>,
121− callback: Box<FnMut()>,
114+ callback: Box<dyn FnMut()>,
122115}
123116
124117impl ListensOnlyOnFirstRender {
@@ -133,8 +126,8 @@ impl ListensOnlyOnFirstRender {
133126 }
134127}
135128
136−impl Render for ListensOnlyOnFirstRender {
137− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
129+impl<'a> Render<'a> for ListensOnlyOnFirstRender {
130+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
138131 use dodrio::builder::*;
139132
140133 let count = self.count.get();
@@ -150,11 +143,11 @@ impl Render for ListensOnlyOnFirstRender {
150143 }
151144}
152145
153−#[wasm_bindgen_test(async)]
154−fn removed_listener_is_not_called() -> impl Future<Item = (), Error = JsValue> {
146+#[wasm_bindgen_test]
147+async fn removed_listener_is_not_called() {
155148 let container = create_element("div");
156149
157− let (outer_sender, outer_receiver) = futures::sync::oneshot::channel();
150+ let (outer_sender, outer_receiver) = futures::channel::oneshot::channel();
158151 let outer_sender = Rc::new(RefCell::new(Some(outer_sender)));
159152 let outer_listener = Closure::wrap(Box::new(move |_| {
160153 outer_sender
@@ -163,13 +156,13 @@ fn removed_listener_is_not_called() -> impl Future<Item = (), Error = JsValue> {
163156 .expect_throw("should only invoke outer_listener once")
164157 .send("outer")
165158 .expect_throw("should not have dropped receiver");
166− }) as Box<FnMut(web_sys::Event)>);
159+ }) as Box<dyn FnMut(web_sys::Event)>);
167160
168161 container
169162 .add_event_listener_with_callback("click", outer_listener.as_ref().unchecked_ref())
170163 .unwrap();
171164
172− let (vdom_sender, vdom_receiver) = futures::sync::oneshot::channel();
165+ let (vdom_sender, vdom_receiver) = futures::channel::oneshot::channel();
173166 let mut vdom_sender = Some(vdom_sender);
174167
175168 // Render a vdom with a listener into container.
@@ -184,33 +177,26 @@ fn removed_listener_is_not_called() -> impl Future<Item = (), Error = JsValue> {
184177 }),
185178 );
186179
187− vdom.weak()
188− // Re-render, so we aren't listening anymore.
189− .render()
190− .map_err(|e| JsValue::from(e.to_string()))
191− .and_then(move |_| {
192− target(&container).click();
193−
194− // We should get our container's event handler fired, and not the unmounted
195− // vdom's event handler.
196− outer_receiver
197− .select(vdom_receiver)
198− .map(|(which, _other)| which)
199− .or_else(|(_error, other)| other)
200− .map(|which| {
201− assert_eq!(which, "outer");
202− drop(outer_listener);
203− drop(vdom)
204− })
205− .map_err(|e| JsValue::from(e.to_string()))
206− })
180+ // Re-render, so we aren't listening anymore.
181+ vdom.weak().render().await.unwrap();
182+
183+ target(&container).click();
184+
185+ // We should get our container's event handler fired, and not the unmounted
186+ // vdom's event handler.
187+ match select(outer_receiver, vdom_receiver).await {
188+ Either::Left((Ok(msg), _)) => assert_eq!(msg, ("outer")),
189+ Either::Left((Err(_), _)) => panic!(),
190+ Either::Right((Ok(_), _)) => panic!(),
191+ Either::Right((Err(_), outer)) => assert_eq!(outer.await, Ok("outer")),
192+ }
207193}
208194
209−#[wasm_bindgen_test(async)]
210−fn event_does_not_fire_after_unmounting() -> impl Future<Item = (), Error = JsValue> {
195+#[wasm_bindgen_test]
196+async fn event_does_not_fire_after_unmounting() {
211197 let container = create_element("div");
212198
213− let (outer_sender, outer_receiver) = futures::sync::oneshot::channel();
199+ let (outer_sender, outer_receiver) = futures::channel::oneshot::channel();
214200 let outer_sender = Rc::new(RefCell::new(Some(outer_sender)));
215201 let outer_listener = Closure::wrap(Box::new(move |_| {
216202 outer_sender
@@ -219,13 +205,13 @@ fn event_does_not_fire_after_unmounting() -> impl Future<Item = (), Error = JsVa
219205 .expect_throw("should only invoke outer_listener once")
220206 .send("outer")
221207 .expect_throw("should not have dropped receiver");
222− }) as Box<FnMut(web_sys::Event)>);
208+ }) as Box<dyn FnMut(web_sys::Event)>);
223209
224210 container
225211 .add_event_listener_with_callback("click", outer_listener.as_ref().unchecked_ref())
226212 .unwrap();
227213
228− let (vdom_sender, vdom_receiver) = futures::sync::oneshot::channel();
214+ let (vdom_sender, vdom_receiver) = futures::channel::oneshot::channel();
229215 let mut vdom_sender = Some(vdom_sender);
230216
231217 // Render a vdom with a listener into container.
@@ -255,13 +241,10 @@ fn event_does_not_fire_after_unmounting() -> impl Future<Item = (), Error = JsVa
255241
256242 // We should get our container's event handler fired, and not the unmounted
257243 // vdom's event handler.
258− outer_receiver
259− .select(vdom_receiver)
260− .map(|(which, _other)| which)
261− .or_else(|(_error, other)| other)
262− .map(|which| {
263− assert_eq!(which, "outer");
264− drop(outer_listener);
265− })
266− .map_err(|e| JsValue::from(e.to_string()))
244+ match select(outer_receiver, vdom_receiver).await {
245+ Either::Left((Ok(msg), _)) => assert_eq!(msg, ("outer")),
246+ Either::Left((Err(_), _)) => panic!(),
247+ Either::Right((Ok(_), _)) => panic!(),
248+ Either::Right((Err(_), outer)) => assert_eq!(outer.await, Ok("outer")),
249+ }
267250}
tests/web/js_api.rs+30−37
@@ -1,8 +1,7 @@
11use super::{assert_rendered, create_element, RenderFn};
22use dodrio::{builder::*, Node, Render, RenderContext, Vdom};
33use dodrio_js_api::JsRender;
4−use futures::prelude::*;
5−use futures::sync::oneshot;
4+use futures::channel::oneshot;
65use js_sys::Object;
76use std::rc::Rc;
87use wasm_bindgen::prelude::*;
@@ -14,7 +13,7 @@ pub struct WrapJs {
1413}
1514
1615impl WrapJs {
17− pub fn new() -> (WrapJs, oneshot::Receiver<()>, Closure<FnMut()>) {
16+ pub fn new() -> (WrapJs, oneshot::Receiver<()>, Closure<dyn FnMut()>) {
1817 let (sender, receiver) = oneshot::channel();
1918
2019 let on_after_render = Closure::wrap(Box::new({
@@ -26,7 +25,7 @@ impl WrapJs {
2625 .send(())
2726 .expect_throw("receiver should not have been dropped");
2827 }
29− }) as Box<FnMut()>);
28+ }) as Box<dyn FnMut()>);
3029
3130 let component = WrapJs {
3231 inner: JsRender::new(JsComponent::new(&on_after_render)),
@@ -36,8 +35,8 @@ impl WrapJs {
3635 }
3736}
3837
39−impl Render for WrapJs {
40− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
38+impl<'a> Render<'a> for WrapJs {
39+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
4140 div(&cx)
4241 .attr("class", "wrap-js")
4342 .children([self.inner.render(cx)])
@@ -52,7 +51,7 @@ extern "C" {
5251 type JsComponent;
5352
5453 #[wasm_bindgen(constructor)]
55− fn new(on_after_render: &Closure<FnMut()>) -> JsComponent;
54+ fn new(on_after_render: &Closure<dyn FnMut()>) -> JsComponent;
5655}
5756
5857fn eval_js_rendering_component() {
@@ -96,14 +95,14 @@ fn eval_js_rendering_component() {
9695 });
9796}
9897
99−#[wasm_bindgen_test(async)]
100−fn can_use_js_rendering_components() -> impl Future<Item = (), Error = JsValue> {
98+#[wasm_bindgen_test]
99+async fn can_use_js_rendering_components() {
101100 eval_js_rendering_component();
102101
103102 let container = create_element("div");
104− let (component, receiver, closure) = WrapJs::new();
103+ let (component, receiver, _closure) = WrapJs::new();
104+ let _vdom = Rc::new(Vdom::new(&container, component));
105105
106− let vdom = Rc::new(Vdom::new(&container, component));
107106 assert_rendered(
108107 &container,
109108 &RenderFn(|cx| {
@@ -131,30 +130,24 @@ fn can_use_js_rendering_components() -> impl Future<Item = (), Error = JsValue>
131130 .expect_throw("should be an `HTMLElement` object")
132131 .click();
133132
134− receiver
135− .map(move |_| {
136− assert_rendered(
137− &container,
138− &RenderFn(|cx| {
139− div(&cx)
140− .attr("class", "wrap-js")
141− .children([span(&cx)
142− .attr("class", "js-component")
143− .children([
144− text("Here is some plain text"),
145− b(&cx)
146− .children([text("...and here is some bold text")])
147− .finish(),
148− text("1"),
149− ])
150− .finish()])
151− .finish()
152− }),
153− );
154−
155− drop(vdom);
156− drop(container);
157− drop(closure);
158− })
159− .map_err(|e| e.to_string().into())
133+ receiver.await.unwrap();
134+
135+ assert_rendered(
136+ &container,
137+ &RenderFn(|cx| {
138+ div(&cx)
139+ .attr("class", "wrap-js")
140+ .children([span(&cx)
141+ .attr("class", "js-component")
142+ .children([
143+ text("Here is some plain text"),
144+ b(&cx)
145+ .children([text("...and here is some bold text")])
146+ .finish(),
147+ text("1"),
148+ ])
149+ .finish()])
150+ .finish()
151+ }),
152+ );
160153}
tests/web/keyed.jsnew file+25
@@ -0,0 +1,25 @@
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.rsnew file+269
@@ -0,0 +1,269 @@
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+25−20
@@ -6,7 +6,7 @@ use bumpalo::Bump;
66use dodrio::{
77 Attribute, CachedSet, ElementNode, Node, NodeKind, Render, RenderContext, TextNode, Vdom,
88};
9−use futures::prelude::*;
9+use fxhash::FxHashMap;
1010use log::*;
1111use std::cell::RefCell;
1212use std::rc::Rc;
@@ -19,6 +19,7 @@ wasm_bindgen_test_configure!(run_in_browser);
1919pub mod cached;
2020pub mod events;
2121pub mod js_api;
22+pub mod keyed;
2223pub mod render;
2324
2425pub fn window() -> web_sys::Window {
@@ -49,12 +50,13 @@ pub fn init_logging() {
4950
5051/// Assert that the `container` contains the physical DOM tree that matches
5152/// `r`'s rendered virtual DOM.
52−pub fn assert_rendered<R: Render>(container: &web_sys::Element, r: &R) {
53+pub fn assert_rendered<R: for<'a> Render<'a>>(container: &web_sys::Element, r: &R) {
5354 init_logging();
5455
5556 let cached_set = &RefCell::new(CachedSet::default());
5657 let bump = &Bump::new();
57− let cx = &mut RenderContext::new(bump, cached_set);
58+ let templates = &mut FxHashMap::default();
59+ let cx = &mut RenderContext::new(bump, cached_set, templates);
5860 let node = r.render(cx);
5961 let child = container
6062 .first_child()
@@ -88,7 +90,7 @@ pub fn assert_rendered<R: Render>(container: &web_sys::Element, r: &R) {
8890 "actual.text_content() == expected.text()"
8991 );
9092 }
91− NodeKind::Element(ElementNode {
93+ NodeKind::Element(&ElementNode {
9294 tag_name,
9395 attributes,
9496 children,
@@ -110,7 +112,7 @@ pub fn assert_rendered<R: Render>(container: &web_sys::Element, r: &R) {
110112 }
111113 }
112114 NodeKind::Cached(ref c) => {
113− let expected = cached_set.get(c.id);
115+ let (expected, _template) = cached_set.get(c.id);
114116 check_node(cached_set, actual, &expected);
115117 }
116118 }
@@ -153,11 +155,11 @@ pub struct RenderFn<F>(F)
153155where
154156 F: for<'a> Fn(&mut RenderContext<'a>) -> Node<'a>;
155157
156−impl<F> Render for RenderFn<F>
158+impl<'a, F> Render<'a> for RenderFn<F>
157159where
158− F: for<'a> Fn(&mut RenderContext<'a>) -> Node<'a>,
160+ F: for<'b> Fn(&mut RenderContext<'b>) -> Node<'b>,
159161{
160− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
162+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
161163 (self.0)(cx)
162164 }
163165}
@@ -165,10 +167,10 @@ where
165167/// Assert that if we start by rendering the `before` virtual DOM tree into a
166168/// physical DOM tree, and then diff it with the `after` virtual DOM tree, then
167169/// the physical DOM tree correctly matches `after`.
168−pub fn assert_before_after<R, S>(before: R, after: S) -> impl Future<Item = (), Error = JsValue>
170+pub async fn assert_before_after<R, S>(before: R, after: S) -> Result<(), JsValue>
169171where
170− R: 'static + Render,
171− S: 'static + Render,
172+ R: 'static + for<'a> Render<'a>,
173+ S: 'static + for<'a> Render<'a>,
172174{
173175 let container = create_element("div");
174176
@@ -177,7 +179,7 @@ where
177179
178180 debug!("====== Rendering the *before* DOM into the physical DOM ======");
179181 let vdom1 = Rc::new(Vdom::new(&container, before.clone()));
180− let vdom2 = vdom1.clone();
182+ let _vdom2 = vdom1.clone();
181183
182184 debug!("====== Checking the *before* DOM against the physical DOM ======");
183185 assert_rendered(&container, &before);
@@ -185,12 +187,13 @@ where
185187 debug!("====== Rendering the *after* DOM into the physical DOM ======");
186188 let weak = vdom1.weak();
187189 weak.set_component(Box::new(after.clone()))
188− .map(move |_| {
189− debug!("====== Checking the *after* DOM against the physical DOM ======");
190− assert_rendered(&container, &after);
191− drop(vdom2);
192− })
193− .map_err(|e| JsValue::from(e.to_string()))
190+ .await
191+ .map_err(|e| e.to_string())?;
192+
193+ debug!("====== Checking the *after* DOM against the physical DOM ======");
194+ assert_rendered(&container, &after);
195+
196+ Ok(())
194197}
195198
196199/// A helper macro for declaring a bunch of `assert_before_after` tests.
@@ -207,14 +210,16 @@ macro_rules! before_after {
207210 }
208211 )* ) => {
209212 $(
210− #[wasm_bindgen_test(async)]
211− fn $name() -> impl Future<Item = (), Error = wasm_bindgen::JsValue> {
213+ #[wasm_bindgen_test]
214+ async fn $name() {
212215 use crate::{assert_before_after, RenderFn};
213216 log::debug!("############### {} ###############", stringify!($name));
214217 assert_before_after(
215218 RenderFn(|$before_bump| { $( $before )* }),
216219 RenderFn(|$after_bump| { $( $after )* })
217220 )
221+ .await
222+ .unwrap()
218223 }
219224 )*
220225 }
tests/web/render.rs+89−2
@@ -1,7 +1,7 @@
11use super::{assert_rendered, before_after, create_element, RenderFn};
2−use dodrio::{builder::*, Vdom};
3−use futures::prelude::*;
2+use dodrio::{builder::*, bumpalo::collections::String, Node, Render, RenderContext, Vdom};
43use std::rc::Rc;
4+use wasm_bindgen::{ JsCast};
55use wasm_bindgen_test::*;
66
77#[wasm_bindgen_test]
@@ -35,6 +35,93 @@ fn container_is_emptied_upon_drop() {
3535 assert!(container.first_child().is_none());
3636}
3737
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+
38125before_after! {
39126 same_text {
40127 before(_cx) {