on master help sign in
Merge pull request #59 from fitzgen/keyed-children
2be0c47Nick Fitzgerald
Cargo.lock+101−91
@@ -60,7 +60,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
60
60
61[[package]]
61[[package]]
62name = "bumpalo"
62name = "bumpalo"
63−version = "2.3.0"
63+version = "2.4.1"
64source = "registry+https://github.com/rust-lang/crates.io-index"
64source = "registry+https://github.com/rust-lang/crates.io-index"
65
65
66[[package]]
66[[package]]
@@ -107,7 +107,7 @@ version = "0.1.6"
107source = "registry+https://github.com/rust-lang/crates.io-index"
107source = "registry+https://github.com/rust-lang/crates.io-index"
108dependencies = [
108dependencies = [
109 "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
110+ "wasm-bindgen 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
111]
111]
112
112
113[[package]]
113[[package]]
@@ -116,26 +116,28 @@ version = "0.1.2"
116source = "registry+https://github.com/rust-lang/crates.io-index"
116source = "registry+https://github.com/rust-lang/crates.io-index"
117dependencies = [
117dependencies = [
118 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
119+ "web-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
120]
120]
121
121
122[[package]]
122[[package]]
123name = "criterion"
123name = "criterion"
124−version = "0.2.10"
124+version = "0.2.11"
125source = "registry+https://github.com/rust-lang/crates.io-index"
125source = "registry+https://github.com/rust-lang/crates.io-index"
126dependencies = [
126dependencies = [
127 "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
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)",
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)",
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)",
130+ "criterion-plot 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
131 "csv 1.0.5 (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)",
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)",
133 "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
134+ "libc 0.2.51 (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 "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_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_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 "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 "rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
140+ "rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
139 "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
141 "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)",
142 "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)",
143 "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -145,7 +147,7 @@ dependencies = [
145
147
146[[package]]
148[[package]]
147name = "criterion-plot"
149name = "criterion-plot"
148−version = "0.3.0"
150+version = "0.3.1"
149source = "registry+https://github.com/rust-lang/crates.io-index"
151source = "registry+https://github.com/rust-lang/crates.io-index"
150dependencies = [
152dependencies = [
151 "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
153 "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -205,19 +207,21 @@ dependencies = [
205name = "dodrio"
207name = "dodrio"
206version = "0.1.0"
208version = "0.1.0"
207dependencies = [
209dependencies = [
210+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
208− "bumpalo 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
211+ "bumpalo 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
209 "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
212 "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)",
213 "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)",
214+ "criterion 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
212 "dodrio-js-api 0.1.0",
215 "dodrio-js-api 0.1.0",
213 "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
216 "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
214 "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
217 "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)",
218+ "js-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
216 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
219 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
220+ "longest-increasing-subsequence 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
217− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
221+ "wasm-bindgen 0.2.42 (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)",
222+ "wasm-bindgen-futures 0.3.19 (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)",
223+ "wasm-bindgen-test 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
220− "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
224+ "web-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
221]
225]
222
226
223[[package]]
227[[package]]
@@ -228,9 +232,9 @@ dependencies = [
228 "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
232 "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
229 "dodrio 0.1.0",
233 "dodrio 0.1.0",
230 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
234 "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)",
235+ "wasm-bindgen 0.2.42 (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)",
236+ "wasm-bindgen-test 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
233− "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
237+ "web-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
234]
238]
235
239
236[[package]]
240[[package]]
@@ -240,11 +244,11 @@ dependencies = [
240 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
244 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
241 "dodrio 0.1.0",
245 "dodrio 0.1.0",
242 "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
246 "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)",
247+ "js-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
244− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
248+ "wasm-bindgen 0.2.42 (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)",
249+ "wasm-bindgen-futures 0.3.19 (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)",
250+ "wasm-bindgen-test 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
247− "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
251+ "web-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
248]
252]
249
253
250[[package]]
254[[package]]
@@ -253,9 +257,9 @@ version = "0.1.0"
253dependencies = [
257dependencies = [
254 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
258 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
255 "dodrio 0.1.0",
259 "dodrio 0.1.0",
256− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
260+ "wasm-bindgen 0.2.42 (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)",
261+ "wasm-bindgen-test 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
258− "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
262+ "web-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
259]
263]
260
264
261[[package]]
265[[package]]
@@ -264,9 +268,9 @@ version = "0.1.0"
264dependencies = [
268dependencies = [
265 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
269 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
266 "dodrio 0.1.0",
270 "dodrio 0.1.0",
267− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
271+ "wasm-bindgen 0.2.42 (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)",
272+ "wasm-bindgen-test 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
269− "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
273+ "web-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
270]
274]
271
275
272[[package]]
276[[package]]
@@ -275,10 +279,10 @@ version = "0.1.0"
275dependencies = [
279dependencies = [
276 "dodrio 0.1.0",
280 "dodrio 0.1.0",
277 "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
281 "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)",
282+ "js-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
279− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
283+ "wasm-bindgen 0.2.42 (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)",
284+ "wasm-bindgen-futures 0.3.19 (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)",
285+ "wasm-bindgen-test 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
282]
286]
283
287
284[[package]]
288[[package]]
@@ -288,9 +292,9 @@ dependencies = [
288 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
292 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
289 "dodrio 0.1.0",
293 "dodrio 0.1.0",
290 "dodrio-js-api 0.1.0",
294 "dodrio-js-api 0.1.0",
291− "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
295+ "js-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
292− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
296+ "wasm-bindgen 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
293− "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
297+ "web-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
294]
298]
295
299
296[[package]]
300[[package]]
@@ -302,12 +306,12 @@ dependencies = [
302 "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
306 "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
303 "dodrio 0.1.0",
307 "dodrio 0.1.0",
304 "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
308 "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)",
309+ "js-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
306 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
310 "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)",
311+ "wasm-bindgen 0.2.42 (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)",
312+ "wasm-bindgen-futures 0.3.19 (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)",
313+ "wasm-bindgen-test 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
310− "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
314+ "web-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
311]
315]
312
316
313[[package]]
317[[package]]
@@ -317,11 +321,11 @@ dependencies = [
317 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
321 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
318 "dodrio 0.1.0",
322 "dodrio 0.1.0",
319 "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
323 "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)",
324+ "js-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
321− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
325+ "wasm-bindgen 0.2.42 (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)",
326+ "wasm-bindgen-futures 0.3.19 (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)",
327+ "wasm-bindgen-test 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
324− "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
328+ "web-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
325]
329]
326
330
327[[package]]
331[[package]]
@@ -333,14 +337,14 @@ dependencies = [
333 "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
337 "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
334 "dodrio 0.1.0",
338 "dodrio 0.1.0",
335 "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
339 "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)",
340+ "js-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
337 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
341 "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)",
342 "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)",
343 "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)",
344+ "wasm-bindgen 0.2.42 (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)",
345+ "wasm-bindgen-futures 0.3.19 (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)",
346+ "wasm-bindgen-test 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
343− "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
347+ "web-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
344]
348]
345
349
346[[package]]
350[[package]]
@@ -429,10 +433,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
429
433
430[[package]]
434[[package]]
431name = "js-sys"
435name = "js-sys"
432−version = "0.3.17"
436+version = "0.3.19"
433source = "registry+https://github.com/rust-lang/crates.io-index"
437source = "registry+https://github.com/rust-lang/crates.io-index"
434dependencies = [
438dependencies = [
435− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
439+ "wasm-bindgen 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
436]
440]
437
441
438[[package]]
442[[package]]
@@ -453,6 +457,11 @@ dependencies = [
453 "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
457 "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
454]
458]
455
459
460+[[package]]
461+name = "longest-increasing-subsequence"
462+version = "0.1.0"
463+source = "registry+https://github.com/rust-lang/crates.io-index"
464+
456[[package]]
465[[package]]
457name = "memchr"
466name = "memchr"
458version = "2.2.0"
467version = "2.2.0"
@@ -778,79 +787,79 @@ dependencies = [
778
787
779[[package]]
788[[package]]
780name = "wasm-bindgen"
789name = "wasm-bindgen"
781−version = "0.2.40"
790+version = "0.2.42"
782source = "registry+https://github.com/rust-lang/crates.io-index"
791source = "registry+https://github.com/rust-lang/crates.io-index"
783dependencies = [
792dependencies = [
784− "wasm-bindgen-macro 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
793+ "wasm-bindgen-macro 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
785]
794]
786
795
787[[package]]
796[[package]]
788name = "wasm-bindgen-backend"
797name = "wasm-bindgen-backend"
789−version = "0.2.40"
798+version = "0.2.42"
790source = "registry+https://github.com/rust-lang/crates.io-index"
799source = "registry+https://github.com/rust-lang/crates.io-index"
791dependencies = [
800dependencies = [
792− "bumpalo 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
801+ "bumpalo 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
793 "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
802 "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)",
803 "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)",
804 "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)",
805 "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)",
806 "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)",
807+ "wasm-bindgen-shared 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
799]
808]
800
809
801[[package]]
810[[package]]
802name = "wasm-bindgen-futures"
811name = "wasm-bindgen-futures"
803−version = "0.3.17"
812+version = "0.3.19"
804source = "registry+https://github.com/rust-lang/crates.io-index"
813source = "registry+https://github.com/rust-lang/crates.io-index"
805dependencies = [
814dependencies = [
806 "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
815 "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)",
816+ "js-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
808− "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
817+ "wasm-bindgen 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
809]
818]
810
819
811[[package]]
820[[package]]
812name = "wasm-bindgen-macro"
821name = "wasm-bindgen-macro"
813−version = "0.2.40"
822+version = "0.2.42"
814source = "registry+https://github.com/rust-lang/crates.io-index"
823source = "registry+https://github.com/rust-lang/crates.io-index"
815dependencies = [
824dependencies = [
816 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
825 "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)",
826+ "wasm-bindgen-macro-support 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
818]
827]
819
828
820[[package]]
829[[package]]
821name = "wasm-bindgen-macro-support"
830name = "wasm-bindgen-macro-support"
822−version = "0.2.40"
831+version = "0.2.42"
823source = "registry+https://github.com/rust-lang/crates.io-index"
832source = "registry+https://github.com/rust-lang/crates.io-index"
824dependencies = [
833dependencies = [
825 "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
834 "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)",
835 "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)",
836 "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)",
837+ "wasm-bindgen-backend 0.2.42 (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)",
838+ "wasm-bindgen-shared 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
830]
839]
831
840
832[[package]]
841[[package]]
833name = "wasm-bindgen-shared"
842name = "wasm-bindgen-shared"
834−version = "0.2.40"
843+version = "0.2.42"
835source = "registry+https://github.com/rust-lang/crates.io-index"
844source = "registry+https://github.com/rust-lang/crates.io-index"
836
845
837[[package]]
846[[package]]
838name = "wasm-bindgen-test"
847name = "wasm-bindgen-test"
839−version = "0.2.40"
848+version = "0.2.42"
840source = "registry+https://github.com/rust-lang/crates.io-index"
849source = "registry+https://github.com/rust-lang/crates.io-index"
841dependencies = [
850dependencies = [
842 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
851 "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)",
852 "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)",
853+ "js-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
845 "scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
854 "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)",
855+ "wasm-bindgen 0.2.42 (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)",
856+ "wasm-bindgen-futures 0.3.19 (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)",
857+ "wasm-bindgen-test-macro 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
849]
858]
850
859
851[[package]]
860[[package]]
852name = "wasm-bindgen-test-macro"
861name = "wasm-bindgen-test-macro"
853−version = "0.2.40"
862+version = "0.2.42"
854source = "registry+https://github.com/rust-lang/crates.io-index"
863source = "registry+https://github.com/rust-lang/crates.io-index"
855dependencies = [
864dependencies = [
856 "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
865 "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -859,7 +868,7 @@ dependencies = [
859
868
860[[package]]
869[[package]]
861name = "wasm-bindgen-webidl"
870name = "wasm-bindgen-webidl"
862−version = "0.2.40"
871+version = "0.2.42"
863source = "registry+https://github.com/rust-lang/crates.io-index"
872source = "registry+https://github.com/rust-lang/crates.io-index"
864dependencies = [
873dependencies = [
865 "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
874 "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -868,21 +877,21 @@ dependencies = [
868 "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
877 "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)",
878 "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)",
879 "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)",
880+ "wasm-bindgen-backend 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
872 "weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
881 "weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
873]
882]
874
883
875[[package]]
884[[package]]
876name = "web-sys"
885name = "web-sys"
877−version = "0.3.17"
886+version = "0.3.19"
878source = "registry+https://github.com/rust-lang/crates.io-index"
887source = "registry+https://github.com/rust-lang/crates.io-index"
879dependencies = [
888dependencies = [
880 "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
889 "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)",
890 "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)",
891+ "js-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
883 "sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
892 "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)",
893+ "wasm-bindgen 0.2.42 (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)",
894+ "wasm-bindgen-webidl 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
886]
895]
887
896
888[[package]]
897[[package]]
@@ -937,7 +946,7 @@ dependencies = [
937"checksum backtrace 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f106c02a3604afcdc0df5d36cc47b44b55917dbaf3d808f71c163a0ddba64637"
946"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"
947"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"
948"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"
949+"checksum bumpalo 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4639720be048090544634e0402490838995ccdc9d2fe648f528f30d3c33ae71f"
941"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb"
950"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"
951"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"
952"checksum cc 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)" = "30f813bf45048a18eda9190fd3c6b78644146056740c43172a5a3699118588fd"
@@ -946,8 +955,8 @@ dependencies = [
946"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
955"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
947"checksum console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211"
956"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"
957"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"
958+"checksum criterion 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0363053954f3e679645fc443321ca128b7b950a6fe288cf5f9335cc22ee58394"
950−"checksum criterion-plot 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4107e4a5abb94267e0149922b8ff49dc70a87cc202820fdbfc0d3e1edbdc4b16"
959+"checksum criterion-plot 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76f9212ddf2f4a9eb2d401635190600656a1f88a932ef53d06e7fa4c7e02fb8e"
951"checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3"
960"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"
961"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"
962"checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9"
@@ -964,10 +973,11 @@ dependencies = [
964"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114"
973"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"
974"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"
975"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"
976+"checksum js-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "3c994fd445b81741d77f6bcd227d6ed645b95b35a2ecfd2050767450ff1c0b6d"
968"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
977"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"
978"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"
979"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
980+"checksum longest-increasing-subsequence 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3bd0dd2cd90571056fdb71f6275fada10131182f84899f4b2a916e565d81d86"
971"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39"
981"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"
982"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"
983"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
@@ -1011,16 +1021,16 @@ dependencies = [
1011"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737"
1021"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737"
1012"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
1022"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"
1023"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"
1024+"checksum wasm-bindgen 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "ffde3534e5fa6fd936e3260cd62cd644b8656320e369388f9303c955895e35d4"
1015−"checksum wasm-bindgen-backend 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c6d7f35ecbb4180513cdb9b298543321bcb278670730415cbb3205ff2c66a477"
1025+"checksum wasm-bindgen-backend 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "40c0543374a7ae881cdc5d32d19de28d1d1929e92263ffa7e31712cc2d53f9f1"
1016−"checksum wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "67bfbec85e48e8915b6acfa1c4d90b770ce033c96dc2067688f3bccd00d3b9e1"
1026+"checksum wasm-bindgen-futures 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "0ad171fc1f6e43f97d155d27f4ee5657bd8aa5cce7c497ef3a0a0c5b44618b2d"
1017−"checksum wasm-bindgen-macro 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "e3c86b06bcd28e92e87d2c2ad208889b2f69ea33f79810b91ef660cc3de65a4c"
1027+"checksum wasm-bindgen-macro 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "f914c94c2c5f4c9364510ca2429e59c92157ec89429243bcc245e983db990a71"
1018−"checksum wasm-bindgen-macro-support 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "81d7338dd8c67e193d8ef18e5802dc03d8710456baa792c1c2e66847e57fd389"
1028+"checksum wasm-bindgen-macro-support 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "9168c413491e4233db7b6884f09a43beb00c14d11d947ffd165242daa48a2385"
1019−"checksum wasm-bindgen-shared 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "0d57c3b66f2f3e4d96b50f49b7b7e2f4cfcddc88b15744433c98c5c105b26672"
1029+"checksum wasm-bindgen-shared 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "326c32126e1a157b6ced7400061a84ac5b11182b2cda6edad7314eb3ae9ac9fe"
1020−"checksum wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "85bbc3ad78a8d59a63a02d59345e8da9a3e0c1617f79e77fd1c3da6e047db63e"
1030+"checksum wasm-bindgen-test 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "8aed4ef1aa56f2ef4aff593c516de6a3e48711c703b3f7415c9151a7c9babd17"
1021−"checksum wasm-bindgen-test-macro 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "114dfdf01bd3659dd79db05480fbd2751759d1c79cd9edc22f2fe611c866e932"
1031+"checksum wasm-bindgen-test-macro 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "eb477c50f3c9a9ecd99c78fb9bd3b6599d414b918d5f72165b12c53738011ae1"
1022−"checksum wasm-bindgen-webidl 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "fc7052a577eefce17a439edfce5c2eedc8432fe7457bbe949624cdf946233fa7"
1032+"checksum wasm-bindgen-webidl 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "613dbf4d7d3bf10aeb212b35de14a8ef07222c26526d4f931061a83fc9e2a851"
1023−"checksum web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "72d02003924c77af871ad74216f828b6e32af776cbb3f5dab3dcde2f2713c012"
1033+"checksum web-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "24129e4be2281109b3e15a328d3d7f233ee232a5405f75ba1e9bb59a25ebc4d4"
1024"checksum weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "26a4c67f132386d965390b8a734d5d10adbcd30eb5cc74bd9229af8b83f10044"
1034"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"
1035"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770"
1026"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1036"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
Cargo.toml+10−8
@@ -34,17 +34,19 @@ required-features = ["xxx-unstable-internal-use-only"]
34xxx-unstable-internal-use-only = []
34xxx-unstable-internal-use-only = []
35
35
36[dependencies]
36[dependencies]
37−bumpalo = "2.3.0"
37+bumpalo = "2.4.1"
38cfg-if = "0.1.7"
38cfg-if = "0.1.7"
39futures = "0.1.26"
39futures = "0.1.26"
40−wasm-bindgen = "0.2.40"
40+wasm-bindgen = "0.2.42"
41−wasm-bindgen-futures = "0.3.17"
41+wasm-bindgen-futures = "0.3.19"
42−js-sys = "0.3.17"
42+js-sys = "0.3.19"
43fxhash = "0.2.1"
43fxhash = "0.2.1"
44log = { version = "0.4.6", optional = true }
44log = { version = "0.4.6", optional = true }
45+bitflags = "1.0.4"
46+longest-increasing-subsequence = "0.1.0"
45
47
46[dependencies.web-sys]
48[dependencies.web-sys]
47−version = "0.3.17"
49+version = "0.3.19"
48features = [
50features = [
49 "console",
51 "console",
50 "Document",
52 "Document",
@@ -58,10 +60,10 @@ features = [
58console_log = "0.1.2"
60console_log = "0.1.2"
59dodrio-js-api = { version = "=0.1.0", path = "./crates/js-api" }
61dodrio-js-api = { version = "=0.1.0", path = "./crates/js-api" }
60log = "0.4.6"
62log = "0.4.6"
61−wasm-bindgen-test = "0.2.40"
63+wasm-bindgen-test = "0.2.42"
62
64
63[dev-dependencies.web-sys]
65[dev-dependencies.web-sys]
64−version = "0.3.17"
66+version = "0.3.19"
65features = [
67features = [
66 "Attr",
68 "Attr",
67 "EventTarget",
69 "EventTarget",
@@ -71,7 +73,7 @@ features = [
71]
73]
72
74
73[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
75[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
74−criterion = "0.2.10"
76+criterion = "0.2.11"
75
77
76[profile.bench]
78[profile.bench]
77debug = true
79debug = true
crates/js-api/Cargo.toml+4−4
@@ -14,10 +14,10 @@ travis-ci = { repository = "fitzgen/dodrio" }
14
14
15[dependencies]
15[dependencies]
16dodrio = { version = "=0.1.0", path = "../.." }
16dodrio = { version = "=0.1.0", path = "../.." }
17−wasm-bindgen = "0.2.40"
17+wasm-bindgen = "0.2.42"
18−js-sys = "0.3.17"
18+js-sys = "0.3.19"
19−wasm-bindgen-futures = "0.3.17"
19+wasm-bindgen-futures = "0.3.19"
20futures = "0.1.26"
20futures = "0.1.26"
21
21
22[dev-dependencies]
22[dev-dependencies]
23−wasm-bindgen-test = "0.2.40"
23+wasm-bindgen-test = "0.2.42"
examples/counter/Cargo.toml+3−3
@@ -14,10 +14,10 @@ console_error_panic_hook = "0.1.6"
14console_log = "0.1.2"
14console_log = "0.1.2"
15dodrio = { path = "../.." }
15dodrio = { path = "../.." }
16log = "0.4.6"
16log = "0.4.6"
17−wasm-bindgen = "0.2.40"
17+wasm-bindgen = "0.2.42"
18
18
19[dependencies.web-sys]
19[dependencies.web-sys]
20−version = "0.3.17"
20+version = "0.3.19"
21features = [
21features = [
22 "console",
22 "console",
23 "Document",
23 "Document",
@@ -30,4 +30,4 @@ features = [
30]
30]
31
31
32[dev-dependencies]
32[dev-dependencies]
33−wasm-bindgen-test = "0.2.40"
33+wasm-bindgen-test = "0.2.42"
examples/game-of-life/Cargo.toml+5−5
@@ -11,14 +11,14 @@ crate-type = ["cdylib"]
11
11
12[dependencies]
12[dependencies]
13dodrio = { path = "../.." }
13dodrio = { path = "../.." }
14−wasm-bindgen = "0.2.40"
14+wasm-bindgen = "0.2.42"
15console_error_panic_hook = "0.1.6"
15console_error_panic_hook = "0.1.6"
16−js-sys = "0.3.17"
16+js-sys = "0.3.19"
17−wasm-bindgen-futures = "0.3.17"
17+wasm-bindgen-futures = "0.3.19"
18futures = "0.1.26"
18futures = "0.1.26"
19
19
20[dependencies.web-sys]
20[dependencies.web-sys]
21−version = "0.3.17"
21+version = "0.3.19"
22features = [
22features = [
23 "Document",
23 "Document",
24 "HtmlElement",
24 "HtmlElement",
@@ -27,4 +27,4 @@ features = [
27]
27]
28
28
29[dev-dependencies]
29[dev-dependencies]
30−wasm-bindgen-test = "0.2.40"
30+wasm-bindgen-test = "0.2.42"
examples/hello-world/Cargo.toml+3−3
@@ -11,11 +11,11 @@ crate-type = ["cdylib"]
11
11
12[dependencies]
12[dependencies]
13dodrio = { path = "../.." }
13dodrio = { path = "../.." }
14−wasm-bindgen = "0.2.40"
14+wasm-bindgen = "0.2.42"
15console_error_panic_hook = "0.1.6"
15console_error_panic_hook = "0.1.6"
16
16
17[dependencies.web-sys]
17[dependencies.web-sys]
18−version = "0.3.17"
18+version = "0.3.19"
19features = [
19features = [
20 "Document",
20 "Document",
21 "HtmlElement",
21 "HtmlElement",
@@ -24,4 +24,4 @@ features = [
24]
24]
25
25
26[dev-dependencies]
26[dev-dependencies]
27−wasm-bindgen-test = "0.2.40"
27+wasm-bindgen-test = "0.2.42"
examples/input-form/Cargo.toml+3−3
@@ -11,11 +11,11 @@ crate-type = ["cdylib"]
11
11
12[dependencies]
12[dependencies]
13dodrio = { path = "../.." }
13dodrio = { path = "../.." }
14−wasm-bindgen = "0.2.40"
14+wasm-bindgen = "0.2.42"
15console_error_panic_hook = "0.1.6"
15console_error_panic_hook = "0.1.6"
16
16
17[dependencies.web-sys]
17[dependencies.web-sys]
18−version = "0.3.17"
18+version = "0.3.19"
19features = [
19features = [
20 "console",
20 "console",
21 "Document",
21 "Document",
@@ -29,4 +29,4 @@ features = [
29]
29]
30
30
31[dev-dependencies]
31[dev-dependencies]
32−wasm-bindgen-test = "0.2.40"
32+wasm-bindgen-test = "0.2.42"
examples/js-component/Cargo.toml+3−3
@@ -12,12 +12,12 @@ crate-type = ["cdylib"]
12[dependencies]
12[dependencies]
13dodrio = { path = "../.." }
13dodrio = { path = "../.." }
14dodrio-js-api = { path = "../../crates/js-api" }
14dodrio-js-api = { path = "../../crates/js-api" }
15−wasm-bindgen = "0.2.40"
15+wasm-bindgen = "0.2.42"
16console_error_panic_hook = "0.1.6"
16console_error_panic_hook = "0.1.6"
17−js-sys = "0.3.17"
17+js-sys = "0.3.19"
18
18
19[dependencies.web-sys]
19[dependencies.web-sys]
20−version = "0.3.17"
20+version = "0.3.19"
21features = [
21features = [
22 "Document",
22 "Document",
23 "HtmlElement",
23 "HtmlElement",
examples/moire/Cargo.toml+5−5
@@ -12,16 +12,16 @@ cfg-if = "0.1.7"
12dodrio = { path = "../.." }
12dodrio = { path = "../.." }
13futures = "0.1.26"
13futures = "0.1.26"
14log = "0.4.6"
14log = "0.4.6"
15−wasm-bindgen = "0.2.40"
15+wasm-bindgen = "0.2.42"
16−wasm-bindgen-futures = "0.3.17"
16+wasm-bindgen-futures = "0.3.19"
17
17
18# Optional dependencies for logging.
18# Optional dependencies for logging.
19console_error_panic_hook = { optional = true, version = "0.1.6" }
19console_error_panic_hook = { optional = true, version = "0.1.6" }
20console_log = { optional = true, version = "0.1.2" }
20console_log = { optional = true, version = "0.1.2" }
21−js-sys = "0.3.17"
21+js-sys = "0.3.19"
22
22
23[dependencies.web-sys]
23[dependencies.web-sys]
24−version = "0.3.17"
24+version = "0.3.19"
25features = [
25features = [
26 "Document",
26 "Document",
27 "Element",
27 "Element",
@@ -35,7 +35,7 @@ features = [
35]
35]
36
36
37[dev-dependencies]
37[dev-dependencies]
38−wasm-bindgen-test = "0.2.40"
38+wasm-bindgen-test = "0.2.42"
39
39
40[features]
40[features]
41logging = [
41logging = [
examples/sierpinski-triangle/Cargo.toml+5−5
@@ -9,14 +9,14 @@ crate-type = ["cdylib"]
9
9
10[dependencies]
10[dependencies]
11dodrio = { path = "../.." }
11dodrio = { path = "../.." }
12−wasm-bindgen = "0.2.40"
12+wasm-bindgen = "0.2.42"
13console_error_panic_hook = "0.1.6"
13console_error_panic_hook = "0.1.6"
14−js-sys = "0.3.17"
14+js-sys = "0.3.19"
15−wasm-bindgen-futures = "0.3.17"
15+wasm-bindgen-futures = "0.3.19"
16futures = "0.1.26"
16futures = "0.1.26"
17
17
18[dependencies.web-sys]
18[dependencies.web-sys]
19−version = "0.3.17"
19+version = "0.3.19"
20features = [
20features = [
21 "Document",
21 "Document",
22 "HtmlElement",
22 "HtmlElement",
@@ -26,4 +26,4 @@ features = [
26]
26]
27
27
28[dev-dependencies]
28[dev-dependencies]
29−wasm-bindgen-test = "0.2.40"
29+wasm-bindgen-test = "0.2.42"
examples/todomvc/Cargo.toml+5−5
@@ -8,11 +8,11 @@ version = "0.1.0"
8cfg-if = "0.1.7"
8cfg-if = "0.1.7"
9dodrio = { path = "../.." }
9dodrio = { path = "../.." }
10futures = "0.1.26"
10futures = "0.1.26"
11−js-sys = "0.3.17"
11+js-sys = "0.3.19"
12serde = { features = ["derive"], version = "1.0.90" }
12serde = { features = ["derive"], version = "1.0.90" }
13serde_json = "1.0.39"
13serde_json = "1.0.39"
14−wasm-bindgen = "0.2.40"
14+wasm-bindgen = "0.2.42"
15−wasm-bindgen-futures = "0.3.17"
15+wasm-bindgen-futures = "0.3.19"
16
16
17# Optional dependencies for logging.
17# Optional dependencies for logging.
18console_error_panic_hook = { optional = true, version = "0.1.6" }
18console_error_panic_hook = { optional = true, version = "0.1.6" }
@@ -20,7 +20,7 @@ console_log = { optional = true, version = "0.1.2" }
20log = { optional = true, version = "0.4.6" }
20log = { optional = true, version = "0.4.6" }
21
21
22[dependencies.web-sys]
22[dependencies.web-sys]
23−version = "0.3.17"
23+version = "0.3.19"
24features = [
24features = [
25 "Document",
25 "Document",
26 "Event",
26 "Event",
@@ -35,7 +35,7 @@ features = [
35]
35]
36
36
37[dev-dependencies]
37[dev-dependencies]
38−wasm-bindgen-test = "0.2.40"
38+wasm-bindgen-test = "0.2.42"
39
39
40[features]
40[features]
41logging = [
41logging = [
js/change-list-interpreter.js+54−7
@@ -188,13 +188,57 @@ const OP_TABLE = [
188 },
188 },
189
189
190 // 17
190 // 17
191+ function saveChildrenToTemporaries(interpreter, mem8, mem32, i) {
192+ let temp = mem32[i++];
193+ const start = mem32[i++];
194+ const end = mem32[i++];
195+ const parent = top(interpreter.stack);
196+ const children = parent.childNodes;
197+ for (let i = start; i < end; i++) {
198+ interpreter.temporaries[temp++] = children[i];
199+ }
200+ return i;
201+ },
202+
203+ // 18
191− function setAttributeNS(interpreter, mem8, mem32, i) {
204+ function pushChild(interpreter, mem8, mem32, i) {
205+ const parent = top(interpreter.stack);
192− const nameId = mem32[i++];
206+ const n = mem32[i++];
193− const valueId = mem32[i++];
207+ const child = parent.childNodes[n];
208+ interpreter.stack.push(child);
209+ return i;
210+ },
211+
212+ // 19
213+ function pushTemporary(interpreter, mem8, mem32, i) {
214+ const temp = mem32[i++];
215+ interpreter.stack.push(interpreter.temporaries[temp]);
216+ return i;
217+ },
218+
219+ // 20
220+ function insertBefore(interpreter, mem8, mem32, i) {
194− const name = interpreter.getCachedString(nameId);
221+ const before = interpreter.stack.pop();
195− const value = interpreter.getCachedString(valueId);
222+ const after = interpreter.stack.pop();
223+ after.parentNode.insertBefore(before, after);
224+ interpreter.stack.push(before);
225+ return i;
226+ },
227+
228+ // 21
229+ function pushLastChild(interpreter, mem8, mem32, i) {
196− const node = top(interpreter.stack);
230+ const parent = top(interpreter.stack);
197− node.setAttributeNS(null, name, value);
231+ const child = parent.lastChild;
232+ interpreter.stack.push(child);
233+ return i;
234+ },
235+
236+ // 22
237+ function removeChild(interpreter, mem8, mem32, i) {
238+ const n = mem32[i++];
239+ const parent = top(interpreter.stack);
240+ const child = parent.childNodes[n];
241+ child.remove();
198 return i;
242 return i;
199 }
243 }
200];
244];
@@ -206,6 +250,7 @@ export class ChangeListInterpreter {
206 this.ranges = [];
250 this.ranges = [];
207 this.stack = [];
251 this.stack = [];
208 this.strings = new Map();
252 this.strings = new Map();
253+ this.temporaries = [];
209 }
254 }
210
255
211 unmount() {
256 unmount() {
@@ -218,6 +263,7 @@ export class ChangeListInterpreter {
218 this.ranges = null;
263 this.ranges = null;
219 this.stack = null;
264 this.stack = null;
220 this.strings = null;
265 this.strings = null;
266+ this.temporaries = null;
221 }
267 }
222
268
223 addChangeListRange(start, len) {
269 addChangeListRange(start, len) {
@@ -242,6 +288,7 @@ export class ChangeListInterpreter {
242
288
243 this.ranges.length = 0;
289 this.ranges.length = 0;
244 this.stack.length = 0;
290 this.stack.length = 0;
291+ this.temporaries.length = 0;
245 }
292 }
246
293
247 applyChangeRange(mem8, mem32, start, len) {
294 applyChangeRange(mem8, mem32, start, len) {
src/builder.rs+48−2
@@ -1,6 +1,6 @@
1//! Helpers for building virtual DOM nodes.
1//! Helpers for building virtual DOM nodes.
2
2
3−use crate::{Attribute, Listener, Node, RootRender, VdomWeak};
3+use crate::{node::NodeKey, Attribute, Listener, Node, RootRender, VdomWeak};
4use bumpalo::Bump;
4use bumpalo::Bump;
5
5
6/// A virtual DOM element builder.
6/// A virtual DOM element builder.
@@ -16,6 +16,7 @@ where
16 Children: 'a + AsRef<[Node<'a>]>,
16 Children: 'a + AsRef<[Node<'a>]>,
17{
17{
18 bump: &'a Bump,
18 bump: &'a Bump,
19+ key: NodeKey,
19 tag_name: &'a str,
20 tag_name: &'a str,
20 listeners: Listeners,
21 listeners: Listeners,
21 attributes: Attributes,
22 attributes: Attributes,
@@ -61,6 +62,7 @@ impl<'a>
61 let bump = bump.into();
62 let bump = bump.into();
62 ElementBuilder {
63 ElementBuilder {
63 bump,
64 bump,
65+ key: NodeKey::NONE,
64 tag_name,
66 tag_name,
65 listeners: bumpalo::collections::Vec::new_in(bump),
67 listeners: bumpalo::collections::Vec::new_in(bump),
66 attributes: bumpalo::collections::Vec::new_in(bump),
68 attributes: bumpalo::collections::Vec::new_in(bump),
@@ -110,6 +112,7 @@ where
110 {
112 {
111 ElementBuilder {
113 ElementBuilder {
112 bump: self.bump,
114 bump: self.bump,
115+ key: self.key,
113 tag_name: self.tag_name,
116 tag_name: self.tag_name,
114 listeners,
117 listeners,
115 attributes: self.attributes,
118 attributes: self.attributes,
@@ -148,6 +151,7 @@ where
148 {
151 {
149 ElementBuilder {
152 ElementBuilder {
150 bump: self.bump,
153 bump: self.bump,
154+ key: self.key,
151 tag_name: self.tag_name,
155 tag_name: self.tag_name,
152 listeners: self.listeners,
156 listeners: self.listeners,
153 attributes,
157 attributes,
@@ -186,6 +190,7 @@ where
186 {
190 {
187 ElementBuilder {
191 ElementBuilder {
188 bump: self.bump,
192 bump: self.bump,
193+ key: self.key,
189 tag_name: self.tag_name,
194 tag_name: self.tag_name,
190 listeners: self.listeners,
195 listeners: self.listeners,
191 attributes: self.attributes,
196 attributes: self.attributes,
@@ -204,7 +209,7 @@ where
204 /// let b = Bump::new();
209 /// let b = Bump::new();
205 ///
210 ///
206 /// // Create a `<td>` tag with an xhtml namespace
211 /// // Create a `<td>` tag with an xhtml namespace
207− /// let my_div = td(&b)
212+ /// let my_td = td(&b)
208 /// .namespace(Some("http://www.w3.org/1999/xhtml"))
213 /// .namespace(Some("http://www.w3.org/1999/xhtml"))
209 /// .finish();
214 /// .finish();
210 /// ```
215 /// ```
@@ -212,6 +217,7 @@ where
212 pub fn namespace(self, namespace: Option<&'a str>) -> Self {
217 pub fn namespace(self, namespace: Option<&'a str>) -> Self {
213 ElementBuilder {
218 ElementBuilder {
214 bump: self.bump,
219 bump: self.bump,
220+ key: self.key,
215 tag_name: self.tag_name,
221 tag_name: self.tag_name,
216 listeners: self.listeners,
222 listeners: self.listeners,
217 attributes: self.attributes,
223 attributes: self.attributes,
@@ -220,6 +226,45 @@ where
220 }
226 }
221 }
227 }
222
228
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+
223 /// Create the virtual DOM node described by this builder.
268 /// Create the virtual DOM node described by this builder.
224 ///
269 ///
225 /// # Example
270 /// # Example
@@ -239,6 +284,7 @@ where
239 pub fn finish(self) -> Node<'a> {
284 pub fn finish(self) -> Node<'a> {
240 Node::element(
285 Node::element(
241 self.bump,
286 self.bump,
287+ self.key,
242 self.tag_name,
288 self.tag_name,
243 self.listeners,
289 self.listeners,
244 self.attributes,
290 self.attributes,
src/cached.rs+17−10
@@ -1,5 +1,6 @@
1use crate::{
1use crate::{
2− cached_set::{CacheId, CachedSet},
2+ cached_set::CachedSet,
3+ node::{CachedNode, NodeKey},
3 Node, Render, RenderContext,
4 Node, Render, RenderContext,
4};
5};
5use std::cell::Cell;
6use std::cell::Cell;
@@ -10,7 +11,7 @@ use std::ops::{Deref, DerefMut};
10#[derive(Clone, Debug)]
11#[derive(Clone, Debug)]
11pub struct Cached<R> {
12pub struct Cached<R> {
12 inner: R,
13 inner: R,
13− cached: Cell<Option<CacheId>>,
14+ cached: Cell<Option<CachedNode>>,
14}
15}
15
16
16impl<R> Cached<R> {
17impl<R> Cached<R> {
@@ -112,7 +113,7 @@ where
112 R: Render,
113 R: Render,
113{
114{
114 fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
115 fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
115− let id = match self.cached.get() {
116+ let cached = match self.cached.get() {
116 // This does-the-cache-contain-this-id check is necessary because
117 // This does-the-cache-contain-this-id check is necessary because
117 // the same `Cached<R>` instance can be rendered into vdom A, which
118 // the same `Cached<R>` instance can be rendered into vdom A, which
118 // will save the results into A's cached set and yield id X. Then,
119 // will save the results into A's cached set and yield id X. Then,
@@ -140,21 +141,27 @@ where
140 //
141 //
141 // This is all do-able but is a bit more book keeping than we really
142 // This is all do-able but is a bit more book keeping than we really
142 // want to do unless it is well motivated.
143 // want to do unless it is well motivated.
143− Some(id)
144+ Some(cached)
144 if {
145 if {
145 let cached_set = cx.cached_set.borrow();
146 let cached_set = cx.cached_set.borrow();
146− cached_set.contains(id)
147+ cached_set.contains(cached.id)
147 } =>
148 } =>
148 {
149 {
149− id
150+ cached
150 }
151 }
151 _ => {
152 _ => {
153+ let mut key = NodeKey::NONE;
152− let id = CachedSet::insert(cx, |nested_cx| self.inner.render(nested_cx));
154+ let id = CachedSet::insert(cx, |nested_cx| {
155+ let node = self.inner.render(nested_cx);
156+ key = node.key();
157+ node
158+ });
159+ let cached = CachedNode { id, key };
153− self.cached.set(Some(id));
160+ self.cached.set(Some(cached));
154− id
161+ cached
155 }
162 }
156 };
163 };
157
164
158− Node::cached(id)
165+ cached.into()
159 }
166 }
160}
167}
src/cached_set.rs+23−28
@@ -64,7 +64,6 @@ impl CachedSet {
64 self.items.retain(|id, entry| {
64 self.items.retain(|id, entry| {
65 let keep = marked.contains(id);
65 let keep = marked.contains(id);
66 if !keep {
66 if !keep {
67− debug!("CachedSet::gc: removing {:?}", id);
68 let node: &Node = unsafe { &*entry.node };
67 let node: &Node = unsafe { &*entry.node };
69 registry.remove_subtree(node);
68 registry.remove_subtree(node);
70 }
69 }
@@ -76,33 +75,31 @@ impl CachedSet {
76 // node has.
75 // node has.
77 fn trace(&self, node: &Node) -> FxHashSet<CacheId> {
76 fn trace(&self, node: &Node) -> FxHashSet<CacheId> {
78 let mut edges = FxHashSet::default();
77 let mut edges = FxHashSet::default();
78+ self.trace_recursive(&mut edges, node);
79+ edges
80+ }
79
81
80− let bump = Bump::new();
82+ fn trace_recursive(&self, edges: &mut FxHashSet<CacheId>, node: &Node) {
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 {
83+ match &node.kind {
86− NodeKind::Text(_) => continue,
84+ NodeKind::Text(_) => return,
87− NodeKind::Cached(c) => {
85+ NodeKind::Cached(c) => {
88− debug_assert!(self.items.contains_key(&c.id));
86+ debug_assert!(self.items.contains_key(&c.id));
89− edges.insert(c.id);
87+ edges.insert(c.id);
90− edges.extend(
88+ edges.extend(
91− self.items
89+ self.items
92− .get(&c.id)
90+ .get(&c.id)
93− .expect_throw("CachedSet::trace: should have c.id in cached set")
91+ .expect_throw("CachedSet::trace: should have c.id in cached set")
94− .edges
92+ .edges
95− .iter()
93+ .iter()
96− .cloned(),
94+ .cloned(),
97− );
95+ );
98− }
96+ }
99− NodeKind::Element(el) => {
97+ NodeKind::Element(el) => {
100− stack.extend(el.children);
98+ for child in el.children {
99+ self.trace_recursive(edges, child);
101 }
100 }
102 }
101 }
103 }
102 }
104−
105− edges
106 }
103 }
107
104
108 fn next_id(&mut self) -> CacheId {
105 fn next_id(&mut self) -> CacheId {
@@ -129,7 +126,6 @@ impl CachedSet {
129
126
130 let mut set = set.borrow_mut();
127 let mut set = set.borrow_mut();
131 let id = set.next_id();
128 let id = set.next_id();
132− debug!("CachedSet::insert: id = {:?}; entry = {:?}", id, entry);
133 set.items.insert(id, entry);
129 set.items.insert(id, entry);
134 id
130 id
135 }
131 }
@@ -140,8 +136,7 @@ impl CachedSet {
140 }
136 }
141
137
142 /// Get the node for the given cache id.
138 /// Get the node for the given cache id.
143− pub fn get(&self, mut id: CacheId) -> Node {
139+ pub fn get(&self, mut id: CacheId) -> &Node {
144− debug!("CachedSet::get: id = {:?}", id);
145 loop {
140 loop {
146 let entry = self
141 let entry = self
147 .items
142 .items
@@ -152,7 +147,7 @@ impl CachedSet {
152 id = c.id;
147 id = c.id;
153 continue;
148 continue;
154 } else {
149 } else {
155− return node.clone();
150+ return node;
156 }
151 }
157 }
152 }
158 }
153 }
src/change_list/emitter.rs+56
@@ -201,4 +201,60 @@ define_change_list_instructions! {
201 /// stack.push(document.createElementNS(tag_name, namespace))
201 /// stack.push(document.createElementNS(tag_name, namespace))
202 /// ```
202 /// ```
203 create_element_ns(tag_name_key, namespace_key) = 16,
203 create_element_ns(tag_name_key, namespace_key) = 16,
204+
205+ /// Stack: `[...] -> [...]`
206+ ///
207+ /// ```text
208+ /// parent = stack.top()
209+ /// children = parent.childNodes
210+ /// temp = temp_base
211+ /// for i in start .. end:
212+ /// temporaries[temp] = children[i]
213+ /// temp += 1
214+ /// ```
215+ save_children_to_temporaries(temp_base, start, end) = 17,
216+
217+ /// Stack: `[... Node] -> [... Node Node]`
218+ ///
219+ /// ```text
220+ /// parent = stack.top()
221+ /// child = parent.childNodes[n]
222+ /// stack.push(child)
223+ /// ```
224+ push_child(n) = 18,
225+
226+ /// Stack: `[...] -> [... Node]`
227+ ///
228+ /// ```text
229+ /// stack.push(temporaries[temp])
230+ /// ```
231+ push_temporary(temp) = 19,
232+
233+ /// Stack: `[... Node Node] -> [... Node]`
234+ ///
235+ /// ```text
236+ /// before = stack.pop()
237+ /// after = stack.pop()
238+ /// after.insertBefore(before)
239+ /// stack.push(before)
240+ /// ```
241+ insert_before() = 20,
242+
243+ /// Stack: `[... Node] -> [... Node Node]`
244+ ///
245+ /// ```text
246+ /// parent = stack.top()
247+ /// child = parent.lastChild
248+ /// stack.push(child)
249+ /// ```
250+ push_last_child() = 21,
251+
252+ /// Stack: `[... Node] -> [... Node]`
253+ ///
254+ /// ```text
255+ /// parent = stack.top()
256+ /// child = parent.childNodes[n]
257+ /// child.remove()
258+ /// ```
259+ remove_child(n) = 22,
204}
260}
src/change_list/mod.rs+52−2
@@ -17,11 +17,11 @@ pub(crate) struct ChangeListPersistentState {
17
17
18pub(crate) struct ChangeListBuilder<'a> {
18pub(crate) struct ChangeListBuilder<'a> {
19 state: &'a mut ChangeListPersistentState,
19 state: &'a mut ChangeListPersistentState,
20+ next_temporary: u32,
20}
21}
21
22
22impl Drop for ChangeListPersistentState {
23impl Drop for ChangeListPersistentState {
23 fn drop(&mut self) {
24 fn drop(&mut self) {
24− debug!("Dropping ChangeListPersistentState");
25 self.interpreter.unmount();
25 self.interpreter.unmount();
26 }
26 }
27}
27}
@@ -43,7 +43,10 @@ impl ChangeListPersistentState {
43 }
43 }
44
44
45 pub(crate) fn builder<'a>(&'a mut self) -> ChangeListBuilder<'a> {
45 pub(crate) fn builder<'a>(&'a mut self) -> ChangeListBuilder<'a> {
46− ChangeListBuilder { state: self }
46+ ChangeListBuilder {
47+ state: self,
48+ next_temporary: 0,
49+ }
47 }
50 }
48}
51}
49
52
@@ -77,6 +80,53 @@ cfg_if::cfg_if! {
77}
80}
78
81
79impl ChangeListBuilder<'_> {
82impl ChangeListBuilder<'_> {
83+ pub fn next_temporary(&self) -> u32 {
84+ self.next_temporary
85+ }
86+
87+ pub fn set_next_temporary(&mut self, next_temporary: u32) {
88+ self.next_temporary = next_temporary;
89+ }
90+
91+ pub fn save_children_to_temporaries(&mut self, start: usize, end: usize) -> u32 {
92+ debug_assert!(start < end);
93+ let temp_base = self.next_temporary;
94+ debug!(
95+ "emit: save_children_to_temporaries({}, {}, {})",
96+ temp_base, start, end
97+ );
98+ self.next_temporary = temp_base + (end - start) as u32;
99+ self.state
100+ .emitter
101+ .save_children_to_temporaries(temp_base, start as u32, end as u32);
102+ temp_base
103+ }
104+
105+ pub fn push_temporary(&self, temp: u32) {
106+ debug!("emit: push_temporary({})", temp);
107+ self.state.emitter.push_temporary(temp);
108+ }
109+
110+ pub fn push_child(&self, child: usize) {
111+ debug!("emit: push_child({})", child);
112+ self.state.emitter.push_child(child as u32);
113+ }
114+
115+ pub fn remove_child(&self, child: usize) {
116+ debug!("emit: remove_child({})", child);
117+ self.state.emitter.remove_child(child as u32);
118+ }
119+
120+ pub fn push_last_child(&self) {
121+ debug!("emit: push_last_child()");
122+ self.state.emitter.push_last_child();
123+ }
124+
125+ pub fn insert_before(&self) {
126+ debug!("emit: insert_before()");
127+ self.state.emitter.insert_before();
128+ }
129+
80 pub fn ensure_string(&mut self, string: &str) -> StringKey {
130 pub fn ensure_string(&mut self, string: &str) -> StringKey {
81 self.state
131 self.state
82 .strings
132 .strings
src/change_list/strings.rs+4−3
@@ -45,8 +45,7 @@ impl StringsCache {
45 }
45 }
46
46
47 pub fn drop_unused_strings(&mut self, emitter: &InstructionEmitter) {
47 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| {
50 if entry.used {
49 if entry.used {
51 // Since this entry was used during while rendering this frame,
50 // Since this entry was used during while rendering this frame,
52 // it is likely going to be used for the next frame as well. So
51 // it is likely going to be used for the next frame as well. So
@@ -57,7 +56,9 @@ impl StringsCache {
57 entry.used = false;
56 entry.used = false;
58 true
57 true
59 } else {
58 } 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);
61 false
62 false
62 }
63 }
63 });
64 });
src/diff.rs+665−58
@@ -4,15 +4,26 @@ use crate::{
4 events::EventsRegistry,
4 events::EventsRegistry,
5 node::{Attribute, ElementNode, Listener, Node, NodeKind, TextNode},
5 node::{Attribute, ElementNode, Listener, Node, NodeKind, TextNode},
6};
6};
7−use fxhash::FxHashSet;
7+use fxhash::{FxHashMap, FxHashSet};
8−use std::cmp;
8+use std::cmp::Ordering;
9+use std::u32;
10+use wasm_bindgen::UnwrapThrowExt;
9
11
12+// Diff the `old` node with the `new` node. Emits instructions to modify a
13+// physical DOM node that reflects `old` into something that reflects `new`.
14+//
15+// Upon entry to this function, the physical DOM node must be on the top of the
16+// change list stack:
17+//
18+// [... node]
19+//
20+// The change list stack is in the same state when this function exits.
10pub(crate) fn diff(
21pub(crate) fn diff(
11 cached_set: &CachedSet,
22 cached_set: &CachedSet,
12 change_list: &mut ChangeListBuilder,
23 change_list: &mut ChangeListBuilder,
13 registry: &mut EventsRegistry,
24 registry: &mut EventsRegistry,
14− old: Node,
25+ old: &Node,
15− new: Node,
26+ new: &Node,
16 cached_roots: &mut FxHashSet<CacheId>,
27 cached_roots: &mut FxHashSet<CacheId>,
17) {
28) {
18 match (&new.kind, &old.kind) {
29 match (&new.kind, &old.kind) {
@@ -20,22 +31,18 @@ pub(crate) fn diff(
20 &NodeKind::Text(TextNode { text: new_text }),
31 &NodeKind::Text(TextNode { text: new_text }),
21 &NodeKind::Text(TextNode { text: old_text }),
32 &NodeKind::Text(TextNode { text: old_text }),
22 ) => {
33 ) => {
23− debug!(" both are text nodes");
24 if new_text != old_text {
34 if new_text != old_text {
25− debug!(" text needs updating");
26 change_list.set_text(new_text);
35 change_list.set_text(new_text);
27 }
36 }
28 }
37 }
29
38
30 (&NodeKind::Text(_), &NodeKind::Element(_)) => {
39 (&NodeKind::Text(_), &NodeKind::Element(_)) => {
31− debug!(" replacing a text node with an element");
32 create(cached_set, change_list, registry, new, cached_roots);
40 create(cached_set, change_list, registry, new, cached_roots);
33 registry.remove_subtree(&old);
41 registry.remove_subtree(&old);
34 change_list.replace_with();
42 change_list.replace_with();
35 }
43 }
36
44
37 (&NodeKind::Element(_), &NodeKind::Text(_)) => {
45 (&NodeKind::Element(_), &NodeKind::Text(_)) => {
38− debug!(" replacing an element with a text node");
39 create(cached_set, change_list, registry, new, cached_roots);
46 create(cached_set, change_list, registry, new, cached_roots);
40 // Note: text nodes cannot have event listeners, so we don't need to
47 // Note: text nodes cannot have event listeners, so we don't need to
41 // remove the old node's listeners from our registry her.
48 // remove the old node's listeners from our registry her.
@@ -44,6 +51,7 @@ pub(crate) fn diff(
44
51
45 (
52 (
46 &NodeKind::Element(ElementNode {
53 &NodeKind::Element(ElementNode {
54+ key: _,
47 tag_name: new_tag_name,
55 tag_name: new_tag_name,
48 listeners: new_listeners,
56 listeners: new_listeners,
49 attributes: new_attributes,
57 attributes: new_attributes,
@@ -51,6 +59,7 @@ pub(crate) fn diff(
51 namespace: new_namespace,
59 namespace: new_namespace,
52 }),
60 }),
53 &NodeKind::Element(ElementNode {
61 &NodeKind::Element(ElementNode {
62+ key: _,
54 tag_name: old_tag_name,
63 tag_name: old_tag_name,
55 listeners: old_listeners,
64 listeners: old_listeners,
56 attributes: old_attributes,
65 attributes: old_attributes,
@@ -58,9 +67,7 @@ pub(crate) fn diff(
58 namespace: old_namespace,
67 namespace: old_namespace,
59 }),
68 }),
60 ) => {
69 ) => {
61− debug!(" updating an element");
62 if new_tag_name != old_tag_name || new_namespace != old_namespace {
70 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");
64 create(cached_set, change_list, registry, new, cached_roots);
71 create(cached_set, change_list, registry, new, cached_roots);
65 registry.remove_subtree(&old);
72 registry.remove_subtree(&old);
66 change_list.replace_with();
73 change_list.replace_with();
@@ -113,14 +120,19 @@ pub(crate) fn diff(
113 }
120 }
114}
121}
115
122
123+// Diff event listeners between `old` and `new`.
124+//
125+// The listeners' node must be on top of the change list stack:
126+//
127+// [... node]
128+//
129+// The change list stack is left unchanged.
116fn diff_listeners(
130fn diff_listeners(
117 change_list: &mut ChangeListBuilder,
131 change_list: &mut ChangeListBuilder,
118 registry: &mut EventsRegistry,
132 registry: &mut EventsRegistry,
119 old: &[Listener],
133 old: &[Listener],
120 new: &[Listener],
134 new: &[Listener],
121) {
135) {
122− debug!(" updating event listeners");
123−
124 'outer1: for new_l in new {
136 'outer1: for new_l in new {
125 unsafe {
137 unsafe {
126 // Safety relies on removing `new_l` from the registry manually at
138 // Safety relies on removing `new_l` from the registry manually at
@@ -151,9 +163,14 @@ fn diff_listeners(
151 }
163 }
152}
164}
153
165
166+// Diff a node's attributes.
167+//
168+// The attributes' node must be on top of the change list stack:
169+//
170+// [... node]
171+//
172+// The change list stack is left unchanged.
154fn diff_attributes(change_list: &mut ChangeListBuilder, old: &[Attribute], new: &[Attribute]) {
173fn diff_attributes(change_list: &mut ChangeListBuilder, old: &[Attribute], new: &[Attribute]) {
155− debug!(" updating attributes");
156−
157 // Do O(n^2) passes to add/update and remove attributes, since
174 // Do O(n^2) passes to add/update and remove attributes, since
158 // there are almost always very few attributes.
175 // there are almost always very few attributes.
159 'outer: for new_attr in new {
176 'outer: for new_attr in new {
@@ -182,6 +199,14 @@ fn diff_attributes(change_list: &mut ChangeListBuilder, old: &[Attribute], new:
182 }
199 }
183}
200}
184
201
202+// Diff the given set of old and new children.
203+//
204+// The parent must be on top of the change list stack when this function is
205+// entered:
206+//
207+// [... parent]
208+//
209+// the change list stack is in the same state when this function returns.
185fn diff_children(
210fn diff_children(
186 cached_set: &CachedSet,
211 cached_set: &CachedSet,
187 change_list: &mut ChangeListBuilder,
212 change_list: &mut ChangeListBuilder,
@@ -190,83 +215,671 @@ fn diff_children(
190 new: &[Node],
215 new: &[Node],
191 cached_roots: &mut FxHashSet<CacheId>,
216 cached_roots: &mut FxHashSet<CacheId>,
192) {
217) {
193− debug!(" updating children shared by old and new");
218+ if new.is_empty() {
219+ if !old.is_empty() {
220+ remove_all_children(change_list, registry, old);
221+ }
222+ return;
223+ }
224+
225+ if old.is_empty() {
226+ create_and_append_children(cached_set, change_list, registry, new, cached_roots);
227+ return;
228+ }
229+
230+ let new_is_keyed = new[0].key().is_some();
231+ let old_is_keyed = old[0].key().is_some();
232+
233+ debug_assert!(
234+ new.iter().all(|n| n.key().is_some() == new_is_keyed),
235+ "all siblings must be keyed or all siblings must be non-keyed"
236+ );
237+ debug_assert!(
238+ old.iter().all(|o| o.key().is_some() == old_is_keyed),
239+ "all siblings must be keyed or all siblings must be non-keyed"
240+ );
241+
242+ if new_is_keyed && old_is_keyed {
243+ let t = change_list.next_temporary();
244+ diff_keyed_children(cached_set, change_list, registry, old, new, cached_roots);
245+ change_list.set_next_temporary(t);
246+ } else {
247+ diff_non_keyed_children(cached_set, change_list, registry, old, new, cached_roots);
248+ }
249+}
250+
251+// Diffing "keyed" children.
252+//
253+// With keyed children, we care about whether we delete, move, or create nodes
254+// versus mutate existing nodes in place. Presumably there is some sort of CSS
255+// transition animation that makes the virtual DOM diffing algorithm
256+// observable. By specifying keys for nodes, we know which virtual DOM nodes
257+// must reuse (or not reuse) the same physical DOM nodes.
258+//
259+// This is loosely based on Inferno's keyed patching implementation. However, we
260+// have to modify the algorithm since we are compiling the diff down into change
261+// list instructions that will be executed later, rather than applying the
262+// changes to the DOM directly as we compare virtual DOMs.
263+//
264+// https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
265+//
266+// When entering this function, the parent must be on top of the change list
267+// stack:
268+//
269+// [... parent]
270+//
271+// Upon exiting, the change list stack is in the same state.
272+fn diff_keyed_children(
273+ cached_set: &CachedSet,
274+ change_list: &mut ChangeListBuilder,
275+ registry: &mut EventsRegistry,
276+ old: &[Node],
277+ new: &[Node],
278+ cached_roots: &mut FxHashSet<CacheId>,
279+) {
280+ if cfg!(debug_assertions) {
281+ let mut keys = FxHashSet::default();
282+ let mut assert_unique_keys = |children: &[Node]| {
283+ keys.clear();
284+ for child in children {
285+ let key = child.key();
286+ debug_assert!(
287+ key.is_some(),
288+ "if any sibling is keyed, all siblings must be keyed"
289+ );
290+ keys.insert(key);
291+ }
292+ debug_assert_eq!(
293+ children.len(),
294+ keys.len(),
295+ "keyed siblings must each have a unique key"
296+ );
297+ };
298+ assert_unique_keys(old);
299+ assert_unique_keys(new);
300+ }
194
301
195− let num_children_to_diff = cmp::min(new.len(), old.len());
302+ // First up, we diff all the nodes with the same key at the beginning of the
196− let mut new_children = new.iter();
303+ // children.
197− let mut old_children = old.iter();
304+ //
305+ // `shared_prefix_count` is the count of how many nodes at the start of
306+ // `new` and `old` share the same keys.
307+ let shared_prefix_count =
308+ match diff_keyed_prefix(cached_set, change_list, registry, old, new, cached_roots) {
309+ KeyedPrefixResult::Finished => return,
310+ KeyedPrefixResult::MoreWorkToDo(count) => count,
311+ };
312+
313+ // Next, we find out how many of the nodes at the end of the children have
314+ // the same key. We do _not_ diff them yet, since we want to emit the change
315+ // list instructions such that they can be applied in a single pass over the
316+ // DOM. Instead, we just save this information for later.
317+ //
318+ // `shared_suffix_count` is the count of how many nodes at the end of `new`
319+ // and `old` share the same keys.
320+ let shared_suffix_count = old[shared_prefix_count..]
321+ .iter()
322+ .rev()
323+ .zip(new[shared_prefix_count..].iter().rev())
324+ .take_while(|&(old, new)| old.key() == new.key())
325+ .count();
326+
327+ let old_shared_suffix_start = old.len() - shared_suffix_count;
328+ let new_shared_suffix_start = new.len() - shared_suffix_count;
329+
330+ // Ok, we now hopefully have a smaller range of children in the middle
331+ // within which to re-order nodes with the same keys, remove old nodes with
332+ // now-unused keys, and create new nodes with fresh keys.
333+ diff_keyed_middle(
334+ cached_set,
335+ change_list,
336+ registry,
337+ &old[shared_prefix_count..old_shared_suffix_start],
338+ &new[shared_prefix_count..new_shared_suffix_start],
339+ cached_roots,
340+ shared_prefix_count,
341+ shared_suffix_count,
342+ old_shared_suffix_start,
343+ );
344+
345+ // Finally, diff the nodes at the end of `old` and `new` that share keys.
346+ let old_suffix = &old[old_shared_suffix_start..];
347+ let new_suffix = &new[new_shared_suffix_start..];
348+ debug_assert_eq!(old_suffix.len(), new_suffix.len());
349+ if !old_suffix.is_empty() {
350+ diff_keyed_suffix(
351+ cached_set,
352+ change_list,
353+ registry,
354+ old_suffix,
355+ new_suffix,
356+ cached_roots,
357+ new_shared_suffix_start,
358+ );
359+ }
360+}
361+
362+enum KeyedPrefixResult {
363+ // Fast path: we finished diffing all the children just by looking at the
364+ // prefix of shared keys!
365+ Finished,
366+ // There is more diffing work to do. Here is a count of how many children at
367+ // the beginning of `new` and `old` we already processed.
368+ MoreWorkToDo(usize),
369+}
370+
371+// Diff the prefix of children in `new` and `old` that share the same keys in
372+// the same order.
373+//
374+// Upon entry of this function, the change list stack must be:
375+//
376+// [... parent]
377+//
378+// Upon exit, the change list stack is the same.
379+fn diff_keyed_prefix(
380+ cached_set: &CachedSet,
381+ change_list: &mut ChangeListBuilder,
382+ registry: &mut EventsRegistry,
383+ old: &[Node],
384+ new: &[Node],
385+ cached_roots: &mut FxHashSet<CacheId>,
386+) -> KeyedPrefixResult {
198 let mut pushed = false;
387 let mut pushed = false;
388+ let mut shared_prefix_count = 0;
199
389
200− for (i, (new_child, old_child)) in new_children
390+ for (old, new) in old.iter().zip(new.iter()) {
201− .by_ref()
391+ if old.key() != new.key() {
202− .zip(old_children.by_ref())
392+ break;
203− .take(num_children_to_diff)
393+ }
204− .enumerate()
394+
205− {
395+ if pushed {
206− if i == 0 {
396+ debug_assert!(shared_prefix_count > 0);
397+ change_list.pop_push_next_sibling();
398+ } else {
399+ debug_assert_eq!(shared_prefix_count, 0);
207 change_list.push_first_child();
400 change_list.push_first_child();
208 pushed = true;
401 pushed = true;
209− } else {
210− debug_assert!(pushed);
211− change_list.pop_push_next_sibling();
212 }
402 }
213
403
214− diff(
404+ diff(cached_set, change_list, registry, old, new, cached_roots);
405+ shared_prefix_count += 1;
406+
407+ // At the end of the loop, the change list stack looks like
408+ //
409+ // [... parent child_we_just_diffed]
410+ debug_assert!(pushed);
411+ }
412+
413+ // If that was all of the old children, then create and append the remaining
414+ // new children and we're finished.
415+ if shared_prefix_count == old.len() {
416+ debug_assert!(
417+ pushed,
418+ "we handle the case of empty children before calling this method, so
419+ `shared_prefix_count` must be greater than zero, and therefore we
420+ must have pushed in the above loop"
421+ );
422+ change_list.pop();
423+ create_and_append_children(
215 cached_set,
424 cached_set,
216 change_list,
425 change_list,
217 registry,
426 registry,
218− old_child.clone(),
427+ &new[shared_prefix_count..],
219− new_child.clone(),
220 cached_roots,
428 cached_roots,
221 );
429 );
430+ return KeyedPrefixResult::Finished;
222 }
431 }
223
432
224− if old_children.next().is_some() {
433+ // And if that was all of the new children, then remove all of the remaining
225− debug!(" removing extra old children");
434+ // old children and we're finished.
226− debug_assert!(new_children.next().is_none());
435+ if shared_prefix_count == new.len() {
227− if pushed {
436+ // Same as above.
437+ debug_assert!(pushed);
438+ change_list.pop_push_next_sibling();
439+ change_list.remove_self_and_next_siblings();
440+ return KeyedPrefixResult::Finished;
441+ }
442+
443+ if pushed {
444+ debug_assert!(shared_prefix_count > 0);
445+ change_list.pop();
446+ }
447+ KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
448+}
449+
450+// The most-general, expensive code path for keyed children diffing.
451+//
452+// We find the longest subsequence within `old` of children that are relatively
453+// ordered the same way in `new` (via finding a longest-increasing-subsequence
454+// of the old child's index within `new`). The children that are elements of
455+// this subsequence will remain in place, minimizing the number of DOM moves we
456+// will have to do.
457+//
458+// Upon entry to this function, the change list stack must be:
459+//
460+// [... parent]
461+//
462+// Upon exit from this function, it will be restored to that same state.
463+fn diff_keyed_middle(
464+ cached_set: &CachedSet,
465+ change_list: &mut ChangeListBuilder,
466+ registry: &mut EventsRegistry,
467+ old: &[Node],
468+ mut new: &[Node],
469+ cached_roots: &mut FxHashSet<CacheId>,
470+ shared_prefix_count: usize,
471+ shared_suffix_count: usize,
472+ old_shared_suffix_start: usize,
473+) {
474+ // Should have already diffed the shared-key prefixes and suffixes.
475+ debug_assert_ne!(new.first().map(|n| n.key()), old.first().map(|o| o.key()));
476+ debug_assert_ne!(new.last().map(|n| n.key()), old.last().map(|o| o.key()));
477+
478+ // The algorithm below relies upon using `u32::MAX` as a sentinel
479+ // value, so if we have that many new nodes, it won't work. This
480+ // check is a bit academic (hence only enabled in debug), since
481+ // wasm32 doesn't have enough address space to hold that many nodes
482+ // in memory.
483+ debug_assert!(new.len() < u32::MAX as usize);
484+
485+ // Map from each `old` node's key to its index within `old`.
486+ let mut old_key_to_old_index = FxHashMap::default();
487+ old_key_to_old_index.reserve(old.len());
488+ old_key_to_old_index.extend(old.iter().enumerate().map(|(i, o)| (o.key(), i)));
489+
490+ // The set of shared keys between `new` and `old`.
491+ let mut shared_keys = FxHashSet::default();
492+ // Map from each index in `new` to the index of the node in `old` that
493+ // has the same key.
494+ let mut new_index_to_old_index = Vec::with_capacity(new.len());
495+ new_index_to_old_index.extend(new.iter().map(|n| {
496+ let key = n.key();
497+ if let Some(&i) = old_key_to_old_index.get(&key) {
498+ shared_keys.insert(key);
499+ i
500+ } else {
501+ u32::MAX as usize
502+ }
503+ }));
504+
505+ // If none of the old keys are reused by the new children, then we
506+ // remove all the remaining old children and create the new children
507+ // afresh.
508+ if shared_suffix_count == 0 && shared_keys.is_empty() {
509+ if shared_prefix_count == 0 {
510+ remove_all_children(change_list, registry, old);
511+ } else {
228 change_list.pop_push_next_sibling();
512 change_list.pop_push_next_sibling();
513+ remove_self_and_next_siblings(change_list, registry, &old[shared_prefix_count..]);
514+ }
515+ create_and_append_children(cached_set, change_list, registry, new, cached_roots);
516+ return;
517+ }
518+
519+ // Save each of the old children whose keys are reused in the new
520+ // children.
521+ let mut old_index_to_temp = vec![u32::MAX; old.len()];
522+ let mut start = 0;
523+ loop {
524+ let end = (start..old.len())
525+ .find(|&i| {
526+ let key = old[i].key();
527+ !shared_keys.contains(&key)
528+ })
529+ .unwrap_or(old.len());
530+
531+ if end - start > 0 {
532+ let mut t = change_list.save_children_to_temporaries(
533+ shared_prefix_count + start,
534+ shared_prefix_count + end,
535+ );
536+ for i in start..end {
537+ old_index_to_temp[i] = t;
538+ t += 1;
539+ }
540+ }
541+
542+ debug_assert!(end <= old.len());
543+ if end == old.len() {
544+ break;
229 } else {
545 } else {
230− change_list.push_first_child();
546+ start = end + 1;
231 }
547 }
232− change_list.remove_self_and_next_siblings();
548+ }
233− pushed = false;
549+
550+ // Remove any old children whose keys were not reused in the new
551+ // children. Remove from the end first so that we don't mess up indices.
552+ let mut removed_count = 0;
553+ for (i, old_child) in old.iter().enumerate().rev() {
554+ if !shared_keys.contains(&old_child.key()) {
555+ registry.remove_subtree(old_child);
556+ change_list.remove_child(i + shared_prefix_count);
557+ removed_count += 1;
558+ }
559+ }
560+
561+ // If there aren't any more new children, then we are done!
562+ if new.is_empty() {
563+ return;
564+ }
565+
566+ // The longest increasing subsequence within `new_index_to_old_index`. This
567+ // is the longest sequence on DOM nodes in `old` that are relatively ordered
568+ // correctly within `new`. We will leave these nodes in place in the DOM,
569+ // and only move nodes that are not part of the LIS. This results in the
570+ // maximum number of DOM nodes left in place, AKA the minimum number of DOM
571+ // nodes moved.
572+ let mut new_index_is_in_lis = FxHashSet::default();
573+ new_index_is_in_lis.reserve(new_index_to_old_index.len());
574+ let mut predecessors = vec![0; new_index_to_old_index.len()];
575+ let mut starts = vec![0; new_index_to_old_index.len()];
576+ longest_increasing_subsequence::lis_with(
577+ &new_index_to_old_index,
578+ &mut new_index_is_in_lis,
579+ |a, b| a < b,
580+ &mut predecessors,
581+ &mut starts,
582+ );
583+
584+ // Now we will iterate from the end of the new children back to the
585+ // beginning, diffing old children we are reusing and if they aren't in the
586+ // LIS moving them to their new destination, or creating new children. Note
587+ // that iterating in reverse order lets us use `Node.prototype.insertBefore`
588+ // to move/insert children.
589+ //
590+ // But first, we ensure that we have a child on the change list stack that
591+ // we can `insertBefore`. We handle this once before looping over `new`
592+ // children, so that we don't have to keep checking on every loop iteration.
593+ if shared_suffix_count > 0 {
594+ // There is a shared suffix after these middle children. We will be
595+ // inserting before that shared suffix, so add the first child of that
596+ // shared suffix to the change list stack.
597+ //
598+ // [... parent]
599+ change_list.push_child(old_shared_suffix_start - removed_count);
600+ // [... parent first_child_of_shared_suffix]
234 } else {
601 } else {
235− debug!(" creating new children");
602+ // There is no shared suffix coming after these middle children.
603+ // Therefore we have to process the last child in `new` and move it to
604+ // the end of the parent's children if it isn't already there.
605+ let last_index = new.len() - 1;
606+ let last = new.last().unwrap_throw();
607+ new = &new[..new.len() - 1];
608+ if shared_keys.contains(&last.key()) {
609+ let old_index = new_index_to_old_index[last_index];
610+ let temp = old_index_to_temp[old_index];
611+ // [... parent]
612+ change_list.push_temporary(temp);
613+ // [... parent last]
614+ diff(
615+ cached_set,
616+ change_list,
617+ registry,
618+ &old[old_index],
619+ last,
620+ cached_roots,
621+ );
622+ if new_index_is_in_lis.contains(&last_index) {
623+ // Don't move it, since it is already where it needs to be.
624+ } else {
625+ change_list.append_child();
626+ // [... parent]
627+ change_list.push_temporary(temp);
628+ // [... parent last]
629+ }
630+ } else {
631+ // [... parent]
632+ create(cached_set, change_list, registry, last, cached_roots);
633+ // [... parent last]
634+ change_list.append_child();
635+ // [... parent]
636+ change_list.push_last_child();
637+ // [... parent last]
638+ }
639+ }
640+
236− for (i, new_child) in new_children.enumerate() {
641+ for (new_index, new_child) in new.iter().enumerate().rev() {
237− if i == 0 && pushed {
642+ let old_index = new_index_to_old_index[new_index];
643+ if old_index == u32::MAX as usize {
644+ debug_assert!(!shared_keys.contains(&new_child.key()));
645+ // [... parent successor]
646+ create(cached_set, change_list, registry, new_child, cached_roots);
647+ // [... parent successor new_child]
648+ change_list.insert_before();
649+ // [... parent new_child]
650+ } else {
651+ debug_assert!(shared_keys.contains(&new_child.key()));
652+ let temp = old_index_to_temp[old_index];
653+ debug_assert_ne!(temp, u32::MAX);
654+
655+ if new_index_is_in_lis.contains(&new_index) {
656+ // [... parent successor]
238 change_list.pop();
657 change_list.pop();
239− pushed = false;
658+ // [... parent]
659+ change_list.push_temporary(temp);
660+ // [... parent new_child]
661+ } else {
662+ // [... parent successor]
663+ change_list.push_temporary(temp);
664+ // [... parent successor new_child]
665+ change_list.insert_before();
666+ // [... parent new_child]
240 }
667 }
241− create(
668+
669+ diff(
242 cached_set,
670 cached_set,
243 change_list,
671 change_list,
244 registry,
672 registry,
673+ &old[old_index],
245− new_child.clone(),
674+ new_child,
246 cached_roots,
675 cached_roots,
247 );
676 );
248− change_list.append_child();
249 }
677 }
250 }
678 }
251
679
252− debug!(" done updating children");
680+ // [... parent child]
253− if pushed {
681+ change_list.pop();
682+ // [... parent]
683+}
684+
685+// Diff the suffix of keyed children that share the same keys in the same order.
686+//
687+// The parent must be on the change list stack when we enter this function:
688+//
689+// [... parent]
690+//
691+// When this function exits, the change list stack remains the same.
692+fn diff_keyed_suffix(
693+ cached_set: &CachedSet,
694+ change_list: &mut ChangeListBuilder,
695+ registry: &mut EventsRegistry,
696+ old: &[Node],
697+ new: &[Node],
698+ cached_roots: &mut FxHashSet<CacheId>,
699+ new_shared_suffix_start: usize,
700+) {
701+ debug_assert_eq!(old.len(), new.len());
702+ debug_assert!(!old.is_empty());
703+
704+ // [... parent]
705+ change_list.push_child(new_shared_suffix_start);
706+ // [... parent new_child]
707+
708+ for (old_child, new_child) in old.iter().zip(new.iter()) {
709+ diff(
710+ cached_set,
711+ change_list,
712+ registry,
713+ old_child,
714+ new_child,
715+ cached_roots,
716+ );
717+
718+ // [... parent this_new_child]
719+ change_list.pop_push_next_sibling();
720+ // [... parent next_new_child]
721+ }
722+
723+ // [... parent]
724+ change_list.pop();
725+}
726+
727+// Diff children that are not keyed.
728+//
729+// The parent must be on the top of the change list stack when entering this
730+// function:
731+//
732+// [... parent]
733+//
734+// the change list stack is in the same state when this function returns.
735+fn diff_non_keyed_children(
736+ cached_set: &CachedSet,
737+ change_list: &mut ChangeListBuilder,
738+ registry: &mut EventsRegistry,
739+ old: &[Node],
740+ new: &[Node],
741+ cached_roots: &mut FxHashSet<CacheId>,
742+) {
743+ // Handled these cases in `diff_children` before calling this function.
744+ debug_assert!(!new.is_empty());
745+ debug_assert!(!old.is_empty());
746+
747+ for (i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() {
748+ if i == 0 {
749+ // [... parent]
750+ change_list.push_first_child();
751+ // [... parent first_child]
752+ } else {
753+ // [... parent prev_sibling]
754+ change_list.pop_push_next_sibling();
755+ // [... parent next_sibling]
756+ }
757+
758+ diff(
759+ cached_set,
760+ change_list,
761+ registry,
762+ old_child,
763+ new_child,
764+ cached_roots,
765+ );
766+ }
767+
768+ // Note that because `new` and `old` are not empty, the previous loop always
769+ // executes at least once, so the change list stack is now:
770+ //
771+ // [... parent child]
772+
773+ match old.len().cmp(&new.len()) {
774+ Ordering::Greater => {
775+ // [... parent last_shared_child]
776+ change_list.pop_push_next_sibling();
777+ // [... parent first_child_to_remove]
778+ remove_self_and_next_siblings(change_list, registry, &old[new.len()..]);
779+ // [... parent]
780+ }
781+ Ordering::Less => {
782+ // [... parent last_child]
783+ change_list.pop();
784+ // [... parent]
785+ create_and_append_children(
786+ cached_set,
787+ change_list,
788+ registry,
789+ &new[old.len()..],
790+ cached_roots,
791+ );
792+ }
793+ Ordering::Equal => {
794+ // [... parent child]
254− change_list.pop();
795+ change_list.pop();
796+ // [... parent]
797+ }
255 }
798 }
256}
799}
257
800
801+// Create the given children and append them to the parent node.
802+//
803+// The parent node must currently be on top of the change list stack:
804+//
805+// [... parent]
806+//
807+// When this function returns, the change list stack is in the same state.
808+fn create_and_append_children(
809+ cached_set: &CachedSet,
810+ change_list: &mut ChangeListBuilder,
811+ registry: &mut EventsRegistry,
812+ new: &[Node],
813+ cached_roots: &mut FxHashSet<CacheId>,
814+) {
815+ for child in new {
816+ create(cached_set, change_list, registry, child, cached_roots);
817+ change_list.append_child();
818+ }
819+}
820+
821+// Remove all of a node's children.
822+//
823+// The change list stack must have this shape upon entry to this function:
824+//
825+// [... parent]
826+//
827+// When this function returns, the change list stack is in the same state.
828+fn remove_all_children(
829+ change_list: &mut ChangeListBuilder,
830+ registry: &mut EventsRegistry,
831+ old: &[Node],
832+) {
833+ for child in old {
834+ registry.remove_subtree(child);
835+ }
836+ // Fast way to remove all children: set the node's textContent to an empty
837+ // string.
838+ change_list.set_text("");
839+}
840+
841+// Remove the current child and all of its following siblings.
842+//
843+// The change list stack must have this shape upon entry to this function:
844+//
845+// [... parent child]
846+//
847+// After the function returns, the child is no longer on the change list stack:
848+//
849+// [... parent]
850+fn remove_self_and_next_siblings(
851+ change_list: &mut ChangeListBuilder,
852+ registry: &mut EventsRegistry,
853+ old: &[Node],
854+) {
855+ for child in old {
856+ registry.remove_subtree(child);
857+ }
858+ change_list.remove_self_and_next_siblings();
859+}
860+
861+// Emit instructions to create the given virtual node.
862+//
863+// The change list stack may have any shape upon entering this function:
864+//
865+// [...]
866+//
867+// When this function returns, the new node is on top of the change list stack:
868+//
869+// [... node]
258fn create(
870fn create(
259 cached_set: &CachedSet,
871 cached_set: &CachedSet,
260 change_list: &mut ChangeListBuilder,
872 change_list: &mut ChangeListBuilder,
261 registry: &mut EventsRegistry,
873 registry: &mut EventsRegistry,
262− node: Node,
874+ node: &Node,
263 cached_roots: &mut FxHashSet<CacheId>,
875 cached_roots: &mut FxHashSet<CacheId>,
264) {
876) {
265 match node.kind {
877 match node.kind {
266 NodeKind::Text(TextNode { text }) => {
878 NodeKind::Text(TextNode { text }) => {
267 change_list.create_text_node(text);
879 change_list.create_text_node(text);
268 }
880 }
269− NodeKind::Element(ElementNode {
881+ NodeKind::Element(&ElementNode {
882+ key: _,
270 tag_name,
883 tag_name,
271 listeners,
884 listeners,
272 attributes,
885 attributes,
@@ -288,17 +901,11 @@ fn create(
288 change_list.set_attribute(&attr.name, &attr.value);
901 change_list.set_attribute(&attr.name, &attr.value);
289 }
902 }
290 for child in children {
903 for child in children {
291− create(
904+ create(cached_set, change_list, registry, child, cached_roots);
292− cached_set,
293− change_list,
294− registry,
295− child.clone(),
296− cached_roots,
297− );
298 change_list.append_child();
905 change_list.append_child();
299 }
906 }
300 }
907 }
301− NodeKind::Cached(c) => {
908+ NodeKind::Cached(ref c) => {
302 cached_roots.insert(c.id);
909 cached_roots.insert(c.id);
303 let node = cached_set.get(c.id);
910 let node = cached_set.get(c.id);
304 create(cached_set, change_list, registry, node, cached_roots)
911 create(cached_set, change_list, registry, node, cached_roots)
src/events.rs+8−17
@@ -19,7 +19,6 @@ cfg_if::cfg_if! {
19 pub(crate) fn clear_active_listeners(&mut self) {}
19 pub(crate) fn clear_active_listeners(&mut self) {}
20 }
20 }
21 } else {
21 } else {
22− use bumpalo::Bump;
23 use crate::{
22 use crate::{
24 node::{ElementNode, ListenerCallback, NodeKind},
23 node::{ElementNode, ListenerCallback, NodeKind},
25 vdom::VdomWeak,
24 vdom::VdomWeak,
@@ -64,7 +63,6 @@ cfg_if::cfg_if! {
64
63
65 let weak_registry = Rc::downgrade(&registry);
64 let weak_registry = Rc::downgrade(&registry);
66 let closure = Closure::wrap(Box::new(move |event, a, b| {
65 let closure = Closure::wrap(Box::new(move |event, a, b| {
67− debug!("Events trampoline invoked with (0x{:x}, 0x{:x})", a, b);
68 debug_assert!(a != 0);
66 debug_assert!(a != 0);
69
67
70 // if the VdomInnerExclusive is keeping this closure alive, then the
68 // if the VdomInnerExclusive is keeping this closure alive, then the
@@ -95,24 +93,19 @@ cfg_if::cfg_if! {
95
93
96 pub(crate) fn remove(&mut self, listener: &Listener) {
94 pub(crate) fn remove(&mut self, listener: &Listener) {
97 let id = listener.get_callback_parts();
95 let id = listener.get_callback_parts();
98− debug!("EventsRegistry::remove(0x{:x}, 0x{:x})", id.0, id.1);
99 debug_assert!(id.0 != 0);
96 debug_assert!(id.0 != 0);
100 self.active.remove(&id);
97 self.active.remove(&id);
101 }
98 }
102
99
103 pub(crate) fn remove_subtree(&mut self, node: &Node) {
100 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 {
101+ match node.kind {
110− NodeKind::Cached(_) | NodeKind::Text(_) => continue,
102+ NodeKind::Cached(_) | NodeKind::Text(_) => return,
111− NodeKind::Element(ElementNode {listeners, children, ..}) => {
103+ NodeKind::Element(&ElementNode {listeners, children, ..}) => {
112− for l in listeners {
104+ for l in listeners {
113− self.remove(l);
105+ self.remove(l);
114− }
106+ }
115− stack.extend(children);
107+ for child in children {
108+ self.remove_subtree(child)
116 }
109 }
117 }
110 }
118 }
111 }
@@ -128,7 +121,6 @@ cfg_if::cfg_if! {
128 /// diffing.
121 /// diffing.
129 pub(crate) unsafe fn add<'a>(&mut self, listener: &'a Listener<'a>) {
122 pub(crate) unsafe fn add<'a>(&mut self, listener: &'a Listener<'a>) {
130 let id = listener.get_callback_parts();
123 let id = listener.get_callback_parts();
131− debug!("EventsRegistry::add(0x{:x}, 0x{:x})", id.0, id.1);
132 debug_assert!(id.0 != 0);
124 debug_assert!(id.0 != 0);
133
125
134 let callback =
126 let callback =
@@ -139,7 +131,6 @@ cfg_if::cfg_if! {
139
131
140 /// Clear all event listeners from the registry.
132 /// Clear all event listeners from the registry.
141 pub(crate) fn clear_active_listeners(&mut self) {
133 pub(crate) fn clear_active_listeners(&mut self) {
142− debug!("EventsRegistry::clear_active_listeners()");
143 self.active.clear();
134 self.active.clear();
144 }
135 }
145 }
136 }
src/node.rs+53−20
@@ -2,8 +2,8 @@ use crate::{cached_set::CacheId, RootRender, VdomWeak};
2use bumpalo::Bump;
2use bumpalo::Bump;
3use std::fmt;
3use std::fmt;
4use std::iter;
4use std::iter;
5−use std::marker::PhantomData;
6use std::mem;
5use std::mem;
6+use std::u32;
7
7
8/// A virtual DOM node.
8/// A virtual DOM node.
9#[derive(Debug, Clone)]
9#[derive(Debug, Clone)]
@@ -24,11 +24,11 @@ pub_unstable_internal! {
24 Text(TextNode<'a>),
24 Text(TextNode<'a>),
25
25
26 /// An element potentially with attributes and children.
26 /// An element potentially with attributes and children.
27− Element(ElementNode<'a>),
27+ Element(&'a ElementNode<'a>),
28
28
29 /// A node in the vdom's `CachedSet`. This allows us to avoid
29 /// A node in the vdom's `CachedSet`. This allows us to avoid
30 /// re-rendering and re-diffing subtrees.
30 /// re-rendering and re-diffing subtrees.
31− Cached(CachedNode<'a>),
31+ Cached(CachedNode),
32 }
32 }
33}
33}
34
34
@@ -46,6 +46,7 @@ pub_unstable_internal! {
46 /// children.
46 /// children.
47 #[derive(Debug, Clone)]
47 #[derive(Debug, Clone)]
48 pub(crate) struct ElementNode<'a> {
48 pub(crate) struct ElementNode<'a> {
49+ pub key: NodeKey,
49 pub tag_name: &'a str,
50 pub tag_name: &'a str,
50 pub listeners: &'a [Listener<'a>],
51 pub listeners: &'a [Listener<'a>],
51 pub attributes: &'a [Attribute<'a>],
52 pub attributes: &'a [Attribute<'a>],
@@ -59,10 +60,31 @@ pub_unstable_internal! {
59 /// allows us to avoid both re-rendering a sub-tree and re-diffing
60 /// allows us to avoid both re-rendering a sub-tree and re-diffing
60 /// it. `CachedNode`s cannot contain cycles, since they can only contain
61 /// it. `CachedNode`s cannot contain cycles, since they can only contain
61 /// other `CachedNode`s that already exist when they are being constructed.
62 /// other `CachedNode`s that already exist when they are being constructed.
62− #[derive(Debug, Clone)]
63+ #[derive(Debug, Clone, Copy)]
63− pub(crate) struct CachedNode<'a> {
64+ pub(crate) struct CachedNode {
64 pub id: CacheId,
65 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+pub_unstable_internal! {
72+ /// The key for keyed children.
73+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
74+ pub(crate) struct NodeKey(pub(crate) u32);
75+}
76+
77+impl NodeKey {
78+ pub const NONE: NodeKey = NodeKey(u32::MAX);
79+
80+ #[inline]
81+ pub fn is_none(&self) -> bool {
82+ *self == Self::NONE
83+ }
84+
85+ #[inline]
86+ pub fn is_some(&self) -> bool {
87+ !self.is_none()
66 }
88 }
67}
89}
68
90
@@ -92,6 +114,15 @@ pub struct Attribute<'a> {
92 pub(crate) value: &'a str,
114 pub(crate) value: &'a str,
93}
115}
94
116
117+impl<'a> From<CachedNode> for Node<'a> {
118+ #[inline]
119+ fn from(c: CachedNode) -> Self {
120+ Node {
121+ kind: NodeKind::Cached(c),
122+ }
123+ }
124+}
125+
95impl fmt::Debug for Listener<'_> {
126impl fmt::Debug for Listener<'_> {
96 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
97 let (a, b) = self.get_callback_parts();
128 let (a, b) = self.get_callback_parts();
@@ -135,6 +166,7 @@ impl<'a> Node<'a> {
135 #[inline]
166 #[inline]
136 pub(crate) fn element<Listeners, Attributes, Children>(
167 pub(crate) fn element<Listeners, Attributes, Children>(
137 bump: &'a Bump,
168 bump: &'a Bump,
169+ key: NodeKey,
138 tag_name: &'a str,
170 tag_name: &'a str,
139 listeners: Listeners,
171 listeners: Listeners,
140 attributes: Attributes,
172 attributes: Attributes,
@@ -155,14 +187,17 @@ impl<'a> Node<'a> {
155 let attributes: &'a Attributes = bump.alloc(attributes);
187 let attributes: &'a Attributes = bump.alloc(attributes);
156 let attributes: &'a [Attribute<'a>] = attributes.as_ref();
188 let attributes: &'a [Attribute<'a>] = attributes.as_ref();
157
189
190+ let element = bump.alloc_with(|| ElementNode {
191+ key,
192+ tag_name,
193+ listeners,
194+ attributes,
195+ children,
196+ namespace,
197+ });
198+
158 Node {
199 Node {
159− kind: NodeKind::Element(ElementNode {
200+ kind: NodeKind::Element(element),
160− tag_name,
161− listeners,
162− attributes,
163− children,
164− namespace,
165− }),
166 }
201 }
167 }
202 }
168
203
@@ -174,14 +209,12 @@ impl<'a> Node<'a> {
174 }
209 }
175 }
210 }
176
211
177− /// Construct a new cached node with the given id.
178 #[inline]
212 #[inline]
179− pub(crate) fn cached(id: CacheId) -> Node<'a> {
213+ pub(crate) fn key(&self) -> NodeKey {
180− Node {
214+ match &self.kind {
215+ NodeKind::Text(_) => NodeKey::NONE,
216+ NodeKind::Element(e) => e.key,
181− kind: NodeKind::Cached(CachedNode {
217+ NodeKind::Cached(c) => c.key,
182− id,
183− _phantom: PhantomData,
184− }),
185 }
218 }
186 }
219 }
187}
220}
src/vdom.rs+4−6
@@ -2,7 +2,7 @@ use super::change_list::ChangeListPersistentState;
2use super::RootRender;
2use super::RootRender;
3use crate::cached_set::CachedSet;
3use crate::cached_set::CachedSet;
4use crate::events::EventsRegistry;
4use crate::events::EventsRegistry;
5−use crate::node::Node;
5+use crate::node::{Node, NodeKey};
6use crate::RenderContext;
6use crate::RenderContext;
7use bumpalo::Bump;
7use bumpalo::Bump;
8use futures::future::Future;
8use futures::future::Future;
@@ -166,7 +166,7 @@ impl Vdom {
166
166
167 // Create a dummy `<div/>` in our container.
167 // Create a dummy `<div/>` in our container.
168 initialize_container(container);
168 initialize_container(container);
169− let current_root = Node::element(&dom_buffers[0], "div", [], [], [], None);
169+ let current_root = Node::element(&dom_buffers[0], NodeKey::NONE, "div", [], [], [], None);
170 let current_root = Some(unsafe { extend_node_lifetime(current_root) });
170 let current_root = Some(unsafe { extend_node_lifetime(current_root) });
171
171
172 let container = container.clone();
172 let container = container.clone();
@@ -277,8 +277,8 @@ impl VdomInnerExclusive {
277 &cached_set,
277 &cached_set,
278 &mut change_list,
278 &mut change_list,
279 &mut registry,
279 &mut registry,
280− old_contents,
280+ &old_contents,
281− new_contents.clone(),
281+ &new_contents,
282 &mut cache_roots,
282 &mut cache_roots,
283 );
283 );
284
284
@@ -424,7 +424,6 @@ impl VdomWeak {
424 /// If you don't want to do more things after the render completes, then use
424 /// If you don't want to do more things after the render completes, then use
425 /// `schedule_render` instead of `render`.
425 /// `schedule_render` instead of `render`.
426 pub fn render(&self) -> impl Future<Item = (), Error = VdomDroppedError> {
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())
427 futures::future::ok(self.inner.upgrade())
429 .and_then(|inner| inner.ok_or(()))
428 .and_then(|inner| inner.ok_or(()))
430 .map_err(|_| VdomDroppedError {})
429 .map_err(|_| VdomDroppedError {})
@@ -449,7 +448,6 @@ impl VdomWeak {
449 // animation frames.
448 // animation frames.
450 let _ = inner.shared.render_scheduled.take();
449 let _ = inner.shared.render_scheduled.take();
451
450
452− debug!("VdomWeak::render: finished rendering");
453 let r = resolve.call0(&JsValue::null());
451 let r = resolve.call0(&JsValue::null());
454 debug_assert!(r.is_ok());
452 debug_assert!(r.is_ok());
455 }
453 }
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+270
@@ -0,0 +1,270 @@
1+use crate::{assert_rendered, create_element};
2+use dodrio::{builder::*, bumpalo, Node, Render, RenderContext, Vdom};
3+use futures::Future;
4+use log::*;
5+use std::rc::Rc;
6+use wasm_bindgen::prelude::*;
7+use wasm_bindgen_test::*;
8+
9+struct Keyed(u16);
10+
11+impl Render for Keyed {
12+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
13+ let key = bumpalo::format!(in cx.bump, "{}", self.0).into_bump_str();
14+ div(&cx)
15+ .attr("class", "keyed")
16+ .attr("id", key)
17+ .key(self.0 as u32)
18+ .finish()
19+ }
20+}
21+
22+fn keyed<'a, Keys>(cx: &mut RenderContext<'a>, keys: Keys) -> Node<'a>
23+where
24+ Keys: AsRef<[u16]>,
25+{
26+ let mut parent = div(&cx).attr("class", "parent");
27+
28+ for &k in keys.as_ref() {
29+ parent = parent.child(Keyed(k).render(cx));
30+ }
31+
32+ parent.finish()
33+}
34+
35+fn assert_keyed<Before, After>(
36+ before: Before,
37+ after: After,
38+) -> impl Future<Item = (), Error = JsValue>
39+where
40+ Before: 'static + Render,
41+ After: 'static + Render,
42+{
43+ #[wasm_bindgen(module = "/tests/web/keyed.js")]
44+ extern "C" {
45+ #[wasm_bindgen(js_name = saveKeyedElements)]
46+ fn save_keyed_elements(container: &web_sys::Element) -> JsValue;
47+
48+ #[wasm_bindgen(js_name = checkKeyedElements)]
49+ fn check_keyed_elements(container: &web_sys::Element, saved: JsValue);
50+ }
51+
52+ let container = create_element("div");
53+
54+ let before = Rc::new(before);
55+ let after = Rc::new(after);
56+
57+ debug!("====== Rendering the *before* DOM into the physical DOM ======");
58+ let vdom1 = Rc::new(Vdom::new(&container, before.clone()));
59+ let vdom2 = vdom1.clone();
60+ let saved = save_keyed_elements(&container);
61+
62+ debug!("====== Checking the *before* DOM against the physical DOM ======");
63+ assert_rendered(&container, &before);
64+
65+ debug!("====== Rendering the *after* DOM into the physical DOM ======");
66+ let weak = vdom1.weak();
67+ weak.set_component(Box::new(after.clone()))
68+ .map(move |_| {
69+ debug!("====== Checking the *after* DOM against the physical DOM ======");
70+ assert_rendered(&container, &after);
71+ check_keyed_elements(&container, saved);
72+ drop(vdom2);
73+ })
74+ .map_err(|e| JsValue::from(e.to_string()))
75+}
76+
77+macro_rules! keyed_tests {
78+ ( $(
79+ $name:ident {
80+ before($before_cx:ident) {
81+ $( $before:tt )*
82+ }
83+ after($after_cx:ident) {
84+ $( $after:tt )*
85+ }
86+ }
87+ )* ) => {
88+ $(
89+ #[wasm_bindgen_test(async)]
90+ fn $name() -> impl Future<Item = (), Error = wasm_bindgen::JsValue> {
91+ use crate::RenderFn;
92+ log::debug!("############### {} ###############", stringify!($name));
93+ assert_keyed(
94+ RenderFn(|$before_cx| { $( $before )* }),
95+ RenderFn(|$after_cx| { $( $after )* }),
96+ )
97+ }
98+ )*
99+ }
100+}
101+
102+keyed_tests! {
103+ same_order {
104+ before(cx) {
105+ keyed(cx, [1, 2, 3])
106+ }
107+ after(cx) {
108+ keyed(cx, [1, 2, 3])
109+ }
110+ }
111+
112+ same_order_append {
113+ before(cx) {
114+ keyed(cx, [1])
115+ }
116+ after(cx) {
117+ keyed(cx, [1, 2])
118+ }
119+ }
120+
121+ same_order_delete {
122+ before(cx) {
123+ keyed(cx, [1, 2, 3])
124+ }
125+ after(cx) {
126+ keyed(cx, [1, 2])
127+ }
128+ }
129+
130+ same_suffix {
131+ before(cx) {
132+ keyed(cx, [1, 2, 3])
133+ }
134+ after(cx) {
135+ keyed(cx, [4, 2, 3])
136+ }
137+ }
138+
139+ same_prefix_and_suffix_reorder_middle {
140+ before(cx) {
141+ keyed(cx, [1, 2, 3, 4])
142+ }
143+ after(cx) {
144+ keyed(cx, [1, 3, 2, 4])
145+ }
146+ }
147+
148+ same_prefix_and_suffix_new_middle {
149+ before(cx) {
150+ keyed(cx, [2, 3, 4, 5])
151+ }
152+ after(cx) {
153+ keyed(cx, [2, 7, 8, 5])
154+ }
155+ }
156+
157+ reverse_order {
158+ before(cx) {
159+ keyed(cx, [1, 2, 3])
160+ }
161+ after(cx) {
162+ keyed(cx, [3, 2, 1])
163+ }
164+ }
165+
166+ no_shared_keys {
167+ before(cx) {
168+ keyed(cx, [1, 2])
169+ }
170+ after(cx) {
171+ keyed(cx, [3, 4])
172+ }
173+ }
174+
175+ new_keys_in_middle {
176+ before(cx) {
177+ keyed(cx, [1, 2])
178+ }
179+ after(cx) {
180+ keyed(cx, [1, 3, 4, 2])
181+ }
182+ }
183+
184+ new_keys_at_start_and_end {
185+ before(cx) {
186+ keyed(cx, [1, 2, 3])
187+ }
188+ after(cx) {
189+ keyed(cx, [4, 1, 2, 3, 5])
190+ }
191+ }
192+
193+ delete_prefix {
194+ before(cx) {
195+ keyed(cx, [1, 2, 3])
196+ }
197+ after(cx) {
198+ keyed(cx, [2, 3])
199+ }
200+ }
201+
202+ delete_suffix {
203+ before(cx) {
204+ keyed(cx, [1, 2, 3])
205+ }
206+ after(cx) {
207+ keyed(cx, [1, 2])
208+ }
209+ }
210+
211+ delete_middle {
212+ before(cx) {
213+ keyed(cx, [1, 2, 3])
214+ }
215+ after(cx) {
216+ keyed(cx, [1, 3])
217+ }
218+ }
219+
220+ nested_keyed_children {
221+ before(cx) {
222+ ul(&cx)
223+ .children([
224+ li(&cx)
225+ .key(1)
226+ .children([
227+ keyed(cx, [2, 3, 4])
228+ ])
229+ .finish(),
230+ li(&cx)
231+ .key(5)
232+ .children([
233+ keyed(cx, [6, 7, 8])
234+ ])
235+ .finish(),
236+ li(&cx)
237+ .key(9)
238+ .children([
239+ keyed(cx, [10, 11, 12])
240+ ])
241+ .finish(),
242+ ])
243+ .finish()
244+ }
245+ after(cx) {
246+ ul(&cx)
247+ .children([
248+ li(&cx)
249+ .key(9)
250+ .children([
251+ keyed(cx, [12, 11, 10])
252+ ])
253+ .finish(),
254+ li(&cx)
255+ .key(5)
256+ .children([
257+ keyed(cx, [8, 7, 6])
258+ ])
259+ .finish(),
260+ li(&cx)
261+ .key(1)
262+ .children([
263+ keyed(cx, [4, 3, 2])
264+ ])
265+ .finish(),
266+ ])
267+ .finish()
268+ }
269+ }
270+}
tests/web/main.rs+2−1
@@ -19,6 +19,7 @@ wasm_bindgen_test_configure!(run_in_browser);
19pub mod cached;
19pub mod cached;
20pub mod events;
20pub mod events;
21pub mod js_api;
21pub mod js_api;
22+pub mod keyed;
22pub mod render;
23pub mod render;
23
24
24pub fn window() -> web_sys::Window {
25pub fn window() -> web_sys::Window {
@@ -88,7 +89,7 @@ pub fn assert_rendered<R: Render>(container: &web_sys::Element, r: &R) {
88 "actual.text_content() == expected.text()"
89 "actual.text_content() == expected.text()"
89 );
90 );
90 }
91 }
91− NodeKind::Element(ElementNode {
92+ NodeKind::Element(&ElementNode {
92 tag_name,
93 tag_name,
93 attributes,
94 attributes,
94 children,
95 children,