on master help sign in
Add event handling infrastructure
9a82a84Nick Fitzgerald
Cargo.lock+57−77
@@ -1,3 +1,5 @@
1+# This file is automatically @generated by Cargo.
2+# It is not intended for manual editing.
1[[package]]
3[[package]]
2name = "aho-corasick"
4name = "aho-corasick"
3version = "0.6.9"
5version = "0.6.9"
@@ -46,7 +48,6 @@ dependencies = [
46[[package]]
48[[package]]
47name = "bumpalo"
49name = "bumpalo"
48version = "1.2.0"
50version = "1.2.0"
49−source = "registry+https://github.com/rust-lang/crates.io-index"
50
51
51[[package]]
52[[package]]
52name = "cc"
53name = "cc"
@@ -81,7 +82,7 @@ dependencies = [
81 "rustfix 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
82 "rustfix 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
82 "serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
83 "serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
83 "serde_derive 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
84 "serde_derive 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
84− "serde_json 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
85+ "serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)",
85 "tester 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
86 "tester 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
86 "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
87 "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
87]
88]
@@ -89,10 +90,9 @@ dependencies = [
89[[package]]
90[[package]]
90name = "console_error_panic_hook"
91name = "console_error_panic_hook"
91version = "0.1.5"
92version = "0.1.5"
92−source = "registry+https://github.com/rust-lang/crates.io-index"
93dependencies = [
93dependencies = [
94 "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
94 "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
95− "wasm-bindgen 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
95+ "wasm-bindgen 0.2.33",
96]
96]
97
97
98[[package]]
98[[package]]
@@ -101,7 +101,7 @@ version = "0.1.2"
101source = "registry+https://github.com/rust-lang/crates.io-index"
101source = "registry+https://github.com/rust-lang/crates.io-index"
102dependencies = [
102dependencies = [
103 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
103 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
104− "web-sys 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)",
104+ "web-sys 0.3.10",
105]
105]
106
106
107[[package]]
107[[package]]
@@ -113,46 +113,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
113name = "dodrio"
113name = "dodrio"
114version = "0.1.0"
114version = "0.1.0"
115dependencies = [
115dependencies = [
116− "bumpalo 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
116+ "bumpalo 1.2.0",
117 "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
117 "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
118− "js-sys 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)",
118+ "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
119+ "js-sys 0.3.10",
119 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
120 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
120− "wasm-bindgen 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
121+ "wasm-bindgen 0.2.33",
122+ "wasm-bindgen-futures 0.3.10",
121− "wasm-bindgen-test 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
123+ "wasm-bindgen-test 0.2.33",
122− "web-sys 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)",
124+ "web-sys 0.3.10",
123]
125]
124
126
125[[package]]
127[[package]]
126name = "dodrio-counter"
128name = "dodrio-counter"
127version = "0.1.0"
129version = "0.1.0"
128dependencies = [
130dependencies = [
131+ "console_error_panic_hook 0.1.5",
129− "console_error_panic_hook 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
132+ "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
130 "dodrio 0.1.0",
133 "dodrio 0.1.0",
131− "wasm-bindgen 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
134+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
132− "wasm-bindgen-test 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
135+ "wasm-bindgen 0.2.33",
133− "web-sys 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)",
136+ "wasm-bindgen-test 0.2.33",
137+ "web-sys 0.3.10",
134]
138]
135
139
136[[package]]
140[[package]]
137name = "dodrio-hello-world"
141name = "dodrio-hello-world"
138version = "0.1.0"
142version = "0.1.0"
139dependencies = [
143dependencies = [
140− "console_error_panic_hook 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
144+ "console_error_panic_hook 0.1.5",
141 "dodrio 0.1.0",
145 "dodrio 0.1.0",
142− "wasm-bindgen 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
146+ "wasm-bindgen 0.2.33",
143− "wasm-bindgen-test 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
147+ "wasm-bindgen-test 0.2.33",
144− "web-sys 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)",
148+ "web-sys 0.3.10",
145]
149]
146
150
147[[package]]
151[[package]]
148name = "dodrio-input-form"
152name = "dodrio-input-form"
149version = "0.1.0"
153version = "0.1.0"
150dependencies = [
154dependencies = [
151− "console_error_panic_hook 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
155+ "console_error_panic_hook 0.1.5",
152 "dodrio 0.1.0",
156 "dodrio 0.1.0",
153− "wasm-bindgen 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
157+ "wasm-bindgen 0.2.33",
154− "wasm-bindgen-test 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
158+ "wasm-bindgen-test 0.2.33",
155− "web-sys 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)",
159+ "web-sys 0.3.10",
156]
160]
157
161
158[[package]]
162[[package]]
@@ -181,7 +185,7 @@ name = "failure_derive"
181version = "0.1.5"
185version = "0.1.5"
182source = "registry+https://github.com/rust-lang/crates.io-index"
186source = "registry+https://github.com/rust-lang/crates.io-index"
183dependencies = [
187dependencies = [
184− "proc-macro2 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)",
188+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
185 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
189 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
186 "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
190 "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
187 "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
191 "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -234,9 +238,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
234[[package]]
238[[package]]
235name = "js-sys"
239name = "js-sys"
236version = "0.3.10"
240version = "0.3.10"
237−source = "registry+https://github.com/rust-lang/crates.io-index"
238dependencies = [
241dependencies = [
239− "wasm-bindgen 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
242+ "wasm-bindgen 0.2.33",
240]
243]
241
244
242[[package]]
245[[package]]
@@ -295,7 +298,7 @@ dependencies = [
295
298
296[[package]]
299[[package]]
297name = "proc-macro2"
300name = "proc-macro2"
298−version = "0.4.26"
301+version = "0.4.27"
299source = "registry+https://github.com/rust-lang/crates.io-index"
302source = "registry+https://github.com/rust-lang/crates.io-index"
300dependencies = [
303dependencies = [
301 "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
304 "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -311,7 +314,7 @@ name = "quote"
311version = "0.6.11"
314version = "0.6.11"
312source = "registry+https://github.com/rust-lang/crates.io-index"
315source = "registry+https://github.com/rust-lang/crates.io-index"
313dependencies = [
316dependencies = [
314− "proc-macro2 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)",
317+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
315]
318]
316
319
317[[package]]
320[[package]]
@@ -361,7 +364,7 @@ dependencies = [
361 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
364 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
362 "serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
365 "serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
363 "serde_derive 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
366 "serde_derive 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
364− "serde_json 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
367+ "serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)",
365]
368]
366
369
367[[package]]
370[[package]]
@@ -384,14 +387,14 @@ name = "serde_derive"
384version = "1.0.85"
387version = "1.0.85"
385source = "registry+https://github.com/rust-lang/crates.io-index"
388source = "registry+https://github.com/rust-lang/crates.io-index"
386dependencies = [
389dependencies = [
387− "proc-macro2 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)",
390+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
388 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
391 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
389 "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
392 "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
390]
393]
391
394
392[[package]]
395[[package]]
393name = "serde_json"
396name = "serde_json"
394−version = "1.0.37"
397+version = "1.0.38"
395source = "registry+https://github.com/rust-lang/crates.io-index"
398source = "registry+https://github.com/rust-lang/crates.io-index"
396dependencies = [
399dependencies = [
397 "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
400 "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -420,7 +423,7 @@ name = "syn"
420version = "0.15.26"
423version = "0.15.26"
421source = "registry+https://github.com/rust-lang/crates.io-index"
424source = "registry+https://github.com/rust-lang/crates.io-index"
422dependencies = [
425dependencies = [
423− "proc-macro2 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)",
426+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
424 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
427 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
425 "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
428 "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
426]
429]
@@ -430,7 +433,7 @@ name = "synstructure"
430version = "0.10.1"
433version = "0.10.1"
431source = "registry+https://github.com/rust-lang/crates.io-index"
434source = "registry+https://github.com/rust-lang/crates.io-index"
432dependencies = [
435dependencies = [
433− "proc-macro2 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)",
436+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
434 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
437 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
435 "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
438 "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
436 "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
439 "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -514,109 +517,99 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
514[[package]]
517[[package]]
515name = "wasm-bindgen"
518name = "wasm-bindgen"
516version = "0.2.33"
519version = "0.2.33"
517−source = "registry+https://github.com/rust-lang/crates.io-index"
518dependencies = [
520dependencies = [
519− "wasm-bindgen-macro 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
521+ "wasm-bindgen-macro 0.2.33",
520]
522]
521
523
522[[package]]
524[[package]]
523name = "wasm-bindgen-backend"
525name = "wasm-bindgen-backend"
524version = "0.2.33"
526version = "0.2.33"
525−source = "registry+https://github.com/rust-lang/crates.io-index"
526dependencies = [
527dependencies = [
527 "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
528 "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
528 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
529 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
529− "proc-macro2 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)",
530+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
530 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
531 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
531 "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
532 "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
532− "wasm-bindgen-shared 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
533+ "wasm-bindgen-shared 0.2.33",
533]
534]
534
535
535[[package]]
536[[package]]
536name = "wasm-bindgen-futures"
537name = "wasm-bindgen-futures"
537version = "0.3.10"
538version = "0.3.10"
538−source = "registry+https://github.com/rust-lang/crates.io-index"
539dependencies = [
539dependencies = [
540 "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
540 "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
541− "js-sys 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)",
541+ "js-sys 0.3.10",
542− "wasm-bindgen 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
542+ "wasm-bindgen 0.2.33",
543]
543]
544
544
545[[package]]
545[[package]]
546name = "wasm-bindgen-macro"
546name = "wasm-bindgen-macro"
547version = "0.2.33"
547version = "0.2.33"
548−source = "registry+https://github.com/rust-lang/crates.io-index"
549dependencies = [
548dependencies = [
550 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
549 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
551− "wasm-bindgen-macro-support 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
550+ "wasm-bindgen-macro-support 0.2.33",
552]
551]
553
552
554[[package]]
553[[package]]
555name = "wasm-bindgen-macro-support"
554name = "wasm-bindgen-macro-support"
556version = "0.2.33"
555version = "0.2.33"
557−source = "registry+https://github.com/rust-lang/crates.io-index"
558dependencies = [
556dependencies = [
559− "proc-macro2 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)",
557+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
560 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
558 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
561 "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
559 "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
562− "wasm-bindgen-backend 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
560+ "wasm-bindgen-backend 0.2.33",
563− "wasm-bindgen-shared 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
561+ "wasm-bindgen-shared 0.2.33",
564]
562]
565
563
566[[package]]
564[[package]]
567name = "wasm-bindgen-shared"
565name = "wasm-bindgen-shared"
568version = "0.2.33"
566version = "0.2.33"
569−source = "registry+https://github.com/rust-lang/crates.io-index"
570
567
571[[package]]
568[[package]]
572name = "wasm-bindgen-test"
569name = "wasm-bindgen-test"
573version = "0.2.33"
570version = "0.2.33"
574−source = "registry+https://github.com/rust-lang/crates.io-index"
575dependencies = [
571dependencies = [
576− "console_error_panic_hook 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
572+ "console_error_panic_hook 0.1.5",
577 "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
573 "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
578− "js-sys 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)",
574+ "js-sys 0.3.10",
579 "scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
575 "scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
580− "wasm-bindgen 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
576+ "wasm-bindgen 0.2.33",
581− "wasm-bindgen-futures 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)",
577+ "wasm-bindgen-futures 0.3.10",
582− "wasm-bindgen-test-macro 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
578+ "wasm-bindgen-test-macro 0.2.33",
583]
579]
584
580
585[[package]]
581[[package]]
586name = "wasm-bindgen-test-macro"
582name = "wasm-bindgen-test-macro"
587version = "0.2.33"
583version = "0.2.33"
588−source = "registry+https://github.com/rust-lang/crates.io-index"
589dependencies = [
584dependencies = [
590− "proc-macro2 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)",
585+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
591 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
586 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
592]
587]
593
588
594[[package]]
589[[package]]
595name = "wasm-bindgen-webidl"
590name = "wasm-bindgen-webidl"
596version = "0.2.27"
591version = "0.2.27"
597−source = "registry+https://github.com/rust-lang/crates.io-index"
598dependencies = [
592dependencies = [
599 "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
593 "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
600 "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
594 "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
601 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
595 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
602− "proc-macro2 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)",
596+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
603 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
597 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
604 "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
598 "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
605− "wasm-bindgen-backend 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
599+ "wasm-bindgen-backend 0.2.33",
606 "weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
600 "weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
607]
601]
608
602
609[[package]]
603[[package]]
610name = "web-sys"
604name = "web-sys"
611version = "0.3.10"
605version = "0.3.10"
612−source = "registry+https://github.com/rust-lang/crates.io-index"
613dependencies = [
606dependencies = [
614 "env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
607 "env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
615 "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
608 "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
616− "js-sys 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)",
609+ "js-sys 0.3.10",
617 "sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
610 "sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
618− "wasm-bindgen 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
611+ "wasm-bindgen 0.2.33",
619− "wasm-bindgen-webidl 0.2.27 (registry+https://github.com/rust-lang/crates.io-index)",
612+ "wasm-bindgen-webidl 0.2.27",
620]
613]
621
614
622[[package]]
615[[package]]
@@ -679,11 +672,9 @@ dependencies = [
679"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799"
672"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799"
680"checksum backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b5b493b66e03090ebc4343eb02f94ff944e0cbc9ac6571491d170ba026741eb5"
673"checksum backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b5b493b66e03090ebc4343eb02f94ff944e0cbc9ac6571491d170ba026741eb5"
681"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6"
674"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6"
682−"checksum bumpalo 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "38da2bd7a4195f03c07e3cb21d7963729722541c6b94ec245ac818eab48b2ff7"
683"checksum cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4a8b715cb4597106ea87c7c84b2f1d452c7492033765df7f32651e66fcf749"
675"checksum cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4a8b715cb4597106ea87c7c84b2f1d452c7492033765df7f32651e66fcf749"
684"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
676"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
685"checksum compiletest_rs 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0d76d4322a40f6b0db7259d4f2c8a65ed8b0d84fce0bbc61b98cf47f8ec6eec3"
677"checksum compiletest_rs 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0d76d4322a40f6b0db7259d4f2c8a65ed8b0d84fce0bbc61b98cf47f8ec6eec3"
686−"checksum console_error_panic_hook 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6c5dd2c094474ec60a6acaf31780af270275e3153bafff2db5995b715295762e"
687"checksum console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e7871d2947441b0fdd8e2bd1ce2a2f75304f896582c0d572162d48290683c48"
678"checksum console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e7871d2947441b0fdd8e2bd1ce2a2f75304f896582c0d572162d48290683c48"
688"checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a"
679"checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a"
689"checksum env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afb070faf94c85d17d50ca44f6ad076bce18ae92f0037d350947240a36e9d42e"
680"checksum env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afb070faf94c85d17d50ca44f6ad076bce18ae92f0037d350947240a36e9d42e"
@@ -695,7 +686,6 @@ dependencies = [
695"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
686"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
696"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114"
687"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114"
697"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b"
688"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b"
698−"checksum js-sys 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)" = "f0edfcbe54ba2071053f2e67f5dfbcea344b69aedfb371b76e27392427a5750f"
699"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
689"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
700"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"
690"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"
701"checksum libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)" = "e962c7641008ac010fa60a7dfdc1712449f29c44ef2d4702394aea943ee75047"
691"checksum libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)" = "e962c7641008ac010fa60a7dfdc1712449f29c44ef2d4702394aea943ee75047"
@@ -703,7 +693,7 @@ dependencies = [
703"checksum memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e1dd4eaac298c32ce07eb6ed9242eda7d82955b9170b7d6db59b2e02cc63fcb8"
693"checksum memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e1dd4eaac298c32ce07eb6ed9242eda7d82955b9170b7d6db59b2e02cc63fcb8"
704"checksum miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226"
694"checksum miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226"
705"checksum nom 4.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b30adc557058ce00c9d0d7cb3c6e0b5bc6f36e2e2eabe74b0ba726d194abd588"
695"checksum nom 4.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b30adc557058ce00c9d0d7cb3c6e0b5bc6f36e2e2eabe74b0ba726d194abd588"
706−"checksum proc-macro2 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)" = "38fddd23d98b2144d197c0eca5705632d4fe2667d14a6be5df8934f8d74f1978"
696+"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915"
707"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
697"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
708"checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1"
698"checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1"
709"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85"
699"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85"
@@ -716,7 +706,7 @@ dependencies = [
716"checksum scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28"
706"checksum scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28"
717"checksum serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)" = "534b8b91a95e0f71bca3ed5824752d558da048d4248c91af873b63bd60519752"
707"checksum serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)" = "534b8b91a95e0f71bca3ed5824752d558da048d4248c91af873b63bd60519752"
718"checksum serde_derive 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)" = "a915306b0f1ac5607797697148c223bedeaa36bcc2e28a01441cd638cc6567b4"
708"checksum serde_derive 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)" = "a915306b0f1ac5607797697148c223bedeaa36bcc2e28a01441cd638cc6567b4"
719−"checksum serde_json 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "4b90a9fbe1211e57d3e1c15670f1cb00802988fb23a1a4aad7a2b63544f1920e"
709+"checksum serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)" = "27dce848e7467aa0e2fcaf0a413641499c0b745452aaca1194d24dedde9e13c9"
720"checksum socket2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c4d11a52082057d87cb5caa31ad812f4504b97ab44732cd8359df2e9ff9f48e7"
710"checksum socket2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c4d11a52082057d87cb5caa31ad812f4504b97ab44732cd8359df2e9ff9f48e7"
721"checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3"
711"checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3"
722"checksum syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)" = "f92e629aa1d9c827b2bb8297046c1ccffc57c99b947a680d3ccff1f136a3bee9"
712"checksum syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)" = "f92e629aa1d9c827b2bb8297046c1ccffc57c99b947a680d3ccff1f136a3bee9"
@@ -732,16 +722,6 @@ dependencies = [
732"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
722"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
733"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737"
723"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737"
734"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
724"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
735−"checksum wasm-bindgen 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "2d8c1eb210a0e91e24feb8ccd6f7484a5d442bfdf2ff179204b3a1d16e1029cc"
736−"checksum wasm-bindgen-backend 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "57ab9a5e88bf5dea5be82bb5f8b68c0f8e550675796ac88570ea4c4e89923413"
737−"checksum wasm-bindgen-futures 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d35a917b7c857bf8bce3e47d51d7d74a074d467aa2b750857fc7d9f878483124"
738−"checksum wasm-bindgen-macro 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "52909345426e198a0d34f63526f9f4d86ca50c24b4a22a019d0bc86570ffe1e3"
739−"checksum wasm-bindgen-macro-support 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "2a130a5906bd540390cda0a28e7f8a2d450222461beb92a5eb5c6a33b8b8bc2a"
740−"checksum wasm-bindgen-shared 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "9c406cd5b42c36db6f76f17d28bcd26187fd90cda686231f3d04de07a716fc3a"
741−"checksum wasm-bindgen-test 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "edda7c4a76a8b2c1852e2bf707c3c537ae1a910fe5324bda77b10d249b6afd51"
742−"checksum wasm-bindgen-test-macro 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "1a97eada8f48c14b098237f45332b3b9d88bbc15ee9f93bf2478853fae85ebf2"
743−"checksum wasm-bindgen-webidl 0.2.27 (registry+https://github.com/rust-lang/crates.io-index)" = "068ab8c6b5682c5ac2aa7c2178ecdfc91143e3d62da458997ee1299c3fbf0e85"
744−"checksum web-sys 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53890ae11f09f4f08f8dc0d76d03cedfb4aa1f667b17ee1b9d6aa23b314cb940"
745"checksum weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "26a4c67f132386d965390b8a734d5d10adbcd30eb5cc74bd9229af8b83f10044"
725"checksum weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "26a4c67f132386d965390b8a734d5d10adbcd30eb5cc74bd9229af8b83f10044"
746"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
726"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
747"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
727"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
Cargo.toml+18−4
@@ -9,16 +9,19 @@ default = []
9
9
10[dependencies]
10[dependencies]
11bumpalo = "1.0.2"
11bumpalo = "1.0.2"
12+futures = "0.1"
12−wasm-bindgen = "0.2.32"
13+wasm-bindgen = "0.2.33"
14+wasm-bindgen-futures = "0.3.10"
13−js-sys = "0.3.9"
15+js-sys = "0.3.10"
14log = "0.4.6"
16log = "0.4.6"
15
17
16[dependencies.web-sys]
18[dependencies.web-sys]
17−version = "0.3.9"
19+version = "0.3.10"
18features = [
20features = [
19 "console",
21 "console",
20 "Document",
22 "Document",
21 "Element",
23 "Element",
24+ "Event",
22 "Node",
25 "Node",
23 "Window",
26 "Window",
24]
27]
@@ -28,9 +31,11 @@ wasm-bindgen-test = "0.2"
28console_log = "0.1.2"
31console_log = "0.1.2"
29
32
30[dev-dependencies.web-sys]
33[dev-dependencies.web-sys]
31−version = "0.3.9"
34+version = "0.3.10"
32features = [
35features = [
33 "Attr",
36 "Attr",
37+ "EventTarget",
38+ "HtmlElement",
34 "NamedNodeMap",
39 "NamedNodeMap",
35 "NodeList",
40 "NodeList",
36]
41]
@@ -46,3 +51,12 @@ members = [
46 "./examples/hello-world/crate",
51 "./examples/hello-world/crate",
47 "./examples/input-form/crate",
52 "./examples/input-form/crate",
48]
53]
54+
55+[patch.crates-io]
56+wasm-bindgen = { path = "../wasm-bindgen" }
57+web-sys = { path = "../wasm-bindgen/crates/web-sys" }
58+js-sys = { path = "../wasm-bindgen/crates/js-sys" }
59+wasm-bindgen-futures = { path = "../wasm-bindgen/crates/futures" }
60+wasm-bindgen-test = { path = "../wasm-bindgen/crates/test" }
61+bumpalo = { path = "../bumpalo" }
62+console_error_panic_hook = { path = "../console_error_panic_hook" }
examples/counter/crate/Cargo.toml+3−1
@@ -10,9 +10,11 @@ crate-type = ["cdylib", "rlib"]
10[features]
10[features]
11
11
12[dependencies]
12[dependencies]
13+console_error_panic_hook = "0.1.1"
14+console_log = "0.1.2"
13dodrio = { path = "../../.." }
15dodrio = { path = "../../.." }
16+log = "0.4.6"
14wasm-bindgen = "0.2"
17wasm-bindgen = "0.2"
15−console_error_panic_hook = "0.1.1"
16
18
17[dependencies.web-sys]
19[dependencies.web-sys]
18version = "0.3.8"
20version = "0.3.8"
examples/counter/crate/src/lib.rs+32−38
@@ -1,7 +1,7 @@
1use dodrio::bumpalo::{self, Bump};
1use dodrio::bumpalo::{self, Bump};
2−use dodrio::{Attribute, Node, Render};
2+use dodrio::{on, Node, Render};
3+use log::*;
3use wasm_bindgen::prelude::*;
4use wasm_bindgen::prelude::*;
4−use wasm_bindgen::JsCast;
5
5
6struct Counter {
6struct Counter {
7 val: isize,
7 val: isize,
@@ -28,31 +28,34 @@ impl Render for Counter {
28 {
28 {
29 let val = bumpalo::format!(in bump, "{}", self.val);
29 let val = bumpalo::format!(in bump, "{}", self.val);
30
30
31− let increment = Node::element(
32− bump,
33− "button",
34− [Attribute {
35− name: "data-action",
36− value: "increment",
37− }],
38− [Node::text("+")],
39− );
40−
41− let decrement = Node::element(
42− bump,
43− "button",
44− [Attribute {
45− name: "data-action",
46− value: "decrement",
47− }],
48− [Node::text("-")],
49− );
50−
51 Node::element(
31 Node::element(
52 bump,
32 bump,
53 "div",
33 "div",
54 [],
34 [],
35+ [],
36+ [
37+ Node::element(
38+ bump,
39+ "button",
40+ [on(bump, "click", |root, vdom, _event| {
41+ root.unwrap_mut::<Counter>().increment();
42+ vdom.schedule_render();
43+ })],
44+ [],
45+ [Node::text("+")],
46+ ),
55− [increment, Node::text(val.into_bump_str()), decrement],
47+ Node::text(val.into_bump_str()),
48+ Node::element(
49+ bump,
50+ "button",
51+ [on(bump, "click", |root, vdom, _event| {
52+ root.unwrap_mut::<Counter>().decrement();
53+ vdom.schedule_render();
54+ })],
55+ [],
56+ [Node::text("-")],
57+ ),
58+ ],
56 )
59 )
57 }
60 }
58}
61}
@@ -60,27 +63,18 @@ impl Render for Counter {
60#[wasm_bindgen]
63#[wasm_bindgen]
61pub fn run() {
64pub fn run() {
62 console_error_panic_hook::set_once();
65 console_error_panic_hook::set_once();
66+ console_log::init_with_level(Level::Trace).expect("should initialize logging OK");
63
67
64 let window = web_sys::window().unwrap();
68 let window = web_sys::window().unwrap();
65 let document = window.document().unwrap();
69 let document = window.document().unwrap();
66 let body = document.body().unwrap();
70 let body = document.body().unwrap();
67
71
72+ // Construct a new counter component.
68 let counter = Counter::new();
73 let counter = Counter::new();
69− let mut vdom = dodrio::Vdom::new(&body, counter);
70
74
71− let on_click = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
75+ // Mount our counter component to the `<body>`.
72− match event
76+ let vdom = dodrio::Vdom::new(&body, counter);
73− .target()
74− .and_then(|t| t.dyn_into::<web_sys::Element>().ok())
75− .and_then(|e| e.get_attribute("data-action"))
76− {
77− Some(ref s) if s == "increment" => vdom.component_mut().increment(),
78− Some(ref s) if s == "decrement" => vdom.component_mut().decrement(),
79− _ => {}
80− }
81− vdom.render();
82− }) as Box<FnMut(_)>);
83
77
84− let _ = body.add_event_listener_with_callback("click", on_click.as_ref().unchecked_ref());
78+ // Run the virtual DOM and its listeners forever.
85− on_click.forget();
79+ vdom.forget();
86}
80}
examples/hello-world/.bin/create-rust-webpack.jsdeleted−38
@@ -1,38 +0,0 @@
1−#!/usr/bin/env node
2−
3−const { spawn } = require("child_process");
4−const fs = require("fs");
5−
6−let folderName = '.';
7−
8−if (process.argv.length >= 3) {
9− folderName = process.argv[2];
10− if (!fs.existsSync(folderName)) {
11− fs.mkdirSync(folderName);
12− }
13−}
14−
15−const clone = spawn("git", ["clone", "https://github.com/rustwasm/rust-webpack-template.git", folderName]);
16−
17−clone.on("close", (code) => {
18− if (code !== 0) {
19− handleError("install", code);
20− } else {
21− console.log(" 🦀 Rust + 🕸 WebAssembly + Webpack = ❤️ ");
22−
23− const install = spawn('npm', ['install'], { cwd: folderName });
24− install.on("close", (code) => {
25− if (code !== 0) {
26− handleError("install", code);
27− } else {
28− console.log(" Installed dependencies ✅ ");
29− }
30− });
31− }
32−});
33−
34−function handleError(type, errCode) {
35− // TODO(sven): handle error here
36− console.error()
37− process.exit(errCode);
38−}
examples/hello-world/crate/src/lib.rs+21−15
@@ -1,16 +1,18 @@
1use dodrio::bumpalo::Bump;
1use dodrio::bumpalo::Bump;
2−use dodrio::{Node, Render};
2+use dodrio::{Node, Render, Vdom};
3use wasm_bindgen::prelude::*;
3use wasm_bindgen::prelude::*;
4
4
5−struct HelloWorld<'a>(&'a str);
5+struct Hello<'who> {
6+ who: &'who str,
7+}
6
8
7−impl<'who> HelloWorld<'who> {
9+impl<'who> Hello<'who> {
8− fn new(who: &str) -> HelloWorld {
10+ fn new(who: &str) -> Hello {
9− HelloWorld(who)
11+ Hello { who }
10 }
12 }
11}
13}
12
14
13−impl<'who> Render for HelloWorld<'who> {
15+impl<'who> Render for Hello<'who> {
14 fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
16 fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
15 where
17 where
16 'a: 'bump,
18 'a: 'bump,
@@ -19,7 +21,8 @@ impl<'who> Render for HelloWorld<'who> {
19 bump,
21 bump,
20 "p",
22 "p",
21 [],
23 [],
24+ [],
22− [Node::text("Hello, "), Node::text(self.0), Node::text("!")],
25+ [Node::text("Hello, "), Node::text(self.who), Node::text("!")],
23 )
26 )
24 }
27 }
25}
28}
@@ -28,14 +31,17 @@ impl<'who> Render for HelloWorld<'who> {
28pub fn run() {
31pub fn run() {
29 console_error_panic_hook::set_once();
32 console_error_panic_hook::set_once();
30
33
31− let window = web_sys::window().unwrap();
34+ let window = web_sys::window().unwrap_throw();
32− let document = window.document().unwrap();
35+ let document = window.document().unwrap_throw();
33− let body = document.body().unwrap();
36+ let body = document.body().unwrap_throw();
37+
38+ // Create a new `Hello` render component.
39+ let component = Hello::new("World");
34
40
35− // Create a new dodrio vdom contained in the body, with an initial virtual
41+ // Create a virtual DOM and mount it and the `Hello` render component to the
36− // dom.
42+ // `<body>`.
37− let vdom = dodrio::Vdom::new(body.as_ref(), HelloWorld::new("World"));
43+ let vdom = Vdom::new(body.as_ref(), component);
38
44
39− // Render a new node tree into the virtual dom.
45+ // Run the virtual DOM forever and don't unmount it.
40− vdom.render_component(HelloWorld::new("Dodrio"));
46+ vdom.forget();
41}
47}
examples/input-form/crate/src/lib.rs+18−17
@@ -1,5 +1,5 @@
1use dodrio::bumpalo::{self, Bump};
1use dodrio::bumpalo::{self, Bump};
2−use dodrio::{Attribute, Node, Render};
2+use dodrio::{on, Attribute, Node, Render};
3use wasm_bindgen::prelude::*;
3use wasm_bindgen::prelude::*;
4use wasm_bindgen::JsCast;
4use wasm_bindgen::JsCast;
5
5
@@ -26,6 +26,18 @@ impl Render for SayHelloTo {
26 let input = Node::element(
26 let input = Node::element(
27 bump,
27 bump,
28 "input",
28 "input",
29+ [on(bump, "input", |root, vdom, event| {
30+ let input = match event
31+ .target()
32+ .and_then(|t| t.dyn_into::<web_sys::HtmlInputElement>().ok())
33+ {
34+ Some(input) => input,
35+ None => return,
36+ };
37+
38+ root.unwrap_mut::<SayHelloTo>().set_who(input.value());
39+ vdom.schedule_render();
40+ })],
29 [
41 [
30 Attribute {
42 Attribute {
31 name: "type",
43 name: "type",
@@ -42,7 +54,7 @@ impl Render for SayHelloTo {
42 let hello = bumpalo::format!(in bump, "Hello, {}!", self.who);
54 let hello = bumpalo::format!(in bump, "Hello, {}!", self.who);
43 let hello = Node::text(hello.into_bump_str());
55 let hello = Node::text(hello.into_bump_str());
44
56
45− Node::element(bump, "div", [], [input, hello])
57+ Node::element(bump, "div", [], [], [input, hello])
46 }
58 }
47}
59}
48
60
@@ -55,21 +67,10 @@ pub fn run() {
55 let body = document.body().unwrap();
67 let body = document.body().unwrap();
56
68
57 let say_hello = SayHelloTo::new("World");
69 let say_hello = SayHelloTo::new("World");
58− let mut vdom = dodrio::Vdom::new(&body, say_hello);
59−
60− let on_input = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
61− let input = match event
62− .target()
63− .and_then(|t| t.dyn_into::<web_sys::HtmlInputElement>().ok())
64− {
65− Some(input) => input,
66− None => return,
67− };
68
70
69− vdom.component_mut().set_who(input.value());
71+ // Mount the component to the `<body>`.
70− vdom.render();
72+ let vdom = dodrio::Vdom::new(&body, say_hello);
71− }) as Box<FnMut(_)>);
72
73
73− let _ = body.add_event_listener_with_callback("input", on_input.as_ref().unchecked_ref());
74+ // Run the component forever.
74− on_input.forget();
75+ vdom.forget();
75}
76}
js/change-list.js+93−12
@@ -11,7 +11,7 @@ function string(mem, pointer, length) {
11
11
12const OP_TABLE = [
12const OP_TABLE = [
13 // 0
13 // 0
14− function setText(stack, mem8, mem32, i) {
14+ function setText(stack, eventsTrampoline, listeners, mem8, mem32, i) {
15 // console.log("setText");
15 // console.log("setText");
16 const pointer = mem32[i++];
16 const pointer = mem32[i++];
17 const length = mem32[i++];
17 const length = mem32[i++];
@@ -23,7 +23,7 @@ const OP_TABLE = [
23 },
23 },
24
24
25 // 1
25 // 1
26− function removeSelfAndNextSiblings(stack, mem8, mem32, i) {
26+ function removeSelfAndNextSiblings(stack, eventsTrampoline, listeners, mem8, mem32, i) {
27 // console.log("removeSelfAndNextSiblings");
27 // console.log("removeSelfAndNextSiblings");
28 const node = stack.pop();
28 const node = stack.pop();
29 // console.log(" top(stack) =", node);
29 // console.log(" top(stack) =", node);
@@ -38,7 +38,7 @@ const OP_TABLE = [
38 },
38 },
39
39
40 // 2
40 // 2
41− function replaceWith(stack, mem8, mem32, i) {
41+ function replaceWith(stack, eventsTrampoline, listeners, mem8, mem32, i) {
42 // console.log("replaceWith");
42 // console.log("replaceWith");
43 const newNode = stack.pop();
43 const newNode = stack.pop();
44 // console.log(" newNode =", newNode);
44 // console.log(" newNode =", newNode);
@@ -50,7 +50,7 @@ const OP_TABLE = [
50 },
50 },
51
51
52 // 3
52 // 3
53− function setAttribute(stack, mem8, mem32, i) {
53+ function setAttribute(stack, eventsTrampoline, listeners, mem8, mem32, i) {
54 // console.log("setAttribute");
54 // console.log("setAttribute");
55 const pointer1 = mem32[i++];
55 const pointer1 = mem32[i++];
56 const length1 = mem32[i++];
56 const length1 = mem32[i++];
@@ -66,7 +66,7 @@ const OP_TABLE = [
66 },
66 },
67
67
68 // 4
68 // 4
69− function removeAttribute(stack, mem8, mem32, i) {
69+ function removeAttribute(stack, eventsTrampoline, listeners, mem8, mem32, i) {
70 // console.log("removeAttribute");
70 // console.log("removeAttribute");
71 const pointer = mem32[i++];
71 const pointer = mem32[i++];
72 const length = mem32[i++];
72 const length = mem32[i++];
@@ -77,7 +77,7 @@ const OP_TABLE = [
77 },
77 },
78
78
79 // 5
79 // 5
80− function pushFirstChild(stack, mem8, mem32, i) {
80+ function pushFirstChild(stack, eventsTrampoline, listeners, mem8, mem32, i) {
81 // console.log("pushFirstChild");
81 // console.log("pushFirstChild");
82 // console.log(" top(stack) =", top(stack));
82 // console.log(" top(stack) =", top(stack));
83 stack.push(top(stack).firstChild);
83 stack.push(top(stack).firstChild);
@@ -85,7 +85,7 @@ const OP_TABLE = [
85 },
85 },
86
86
87 // 6
87 // 6
88− function popPushNextSibling(stack, mem8, mem32, i) {
88+ function popPushNextSibling(stack, eventsTrampoline, listeners, mem8, mem32, i) {
89 // console.log("popPushNextSibling");
89 // console.log("popPushNextSibling");
90 // console.log(" top(stack) =", top(stack));
90 // console.log(" top(stack) =", top(stack));
91 const node = stack.pop();
91 const node = stack.pop();
@@ -94,7 +94,7 @@ const OP_TABLE = [
94 },
94 },
95
95
96 // 7
96 // 7
97− function pop(stack, mem8, mem32, i) {
97+ function pop(stack, eventsTrampoline, listeners, mem8, mem32, i) {
98 // console.log("pop");
98 // console.log("pop");
99 // console.log(" top(stack) =", top(stack));
99 // console.log(" top(stack) =", top(stack));
100 stack.pop();
100 stack.pop();
@@ -102,7 +102,7 @@ const OP_TABLE = [
102 },
102 },
103
103
104 // 8
104 // 8
105− function appendChild(stack, mem8, mem32, i) {
105+ function appendChild(stack, eventsTrampoline, listeners, mem8, mem32, i) {
106 // console.log("appendChild");
106 // console.log("appendChild");
107 // console.log(" top(stack) =", top(stack));
107 // console.log(" top(stack) =", top(stack));
108 const child = stack.pop();
108 const child = stack.pop();
@@ -111,7 +111,7 @@ const OP_TABLE = [
111 },
111 },
112
112
113 // 9
113 // 9
114− function createTextNode(stack, mem8, mem32, i) {
114+ function createTextNode(stack, eventsTrampoline, listeners, mem8, mem32, i) {
115 // console.log("appendChild");
115 // console.log("appendChild");
116 const pointer = mem32[i++];
116 const pointer = mem32[i++];
117 const length = mem32[i++];
117 const length = mem32[i++];
@@ -123,7 +123,7 @@ const OP_TABLE = [
123 },
123 },
124
124
125 // 10
125 // 10
126− function createElement(stack, mem8, mem32, i) {
126+ function createElement(stack, eventsTrampoline, listeners, mem8, mem32, i) {
127 // console.log("createElement");
127 // console.log("createElement");
128 const pointer = mem32[i++];
128 const pointer = mem32[i++];
129 const length = mem32[i++];
129 const length = mem32[i++];
@@ -132,17 +132,92 @@ const OP_TABLE = [
132 // console.log(" top(stack) =", top(stack));
132 // console.log(" top(stack) =", top(stack));
133 stack.push(document.createElement(tagName));
133 stack.push(document.createElement(tagName));
134 return i;
134 return i;
135+ },
136+
137+ // 11
138+ function newEventListener(stack, eventsTrampoline, listeners, mem8, mem32, i) {
139+ const pointer = mem32[i++];
140+ const length = mem32[i++];
141+ const eventType = string(mem8, pointer, length);
142+ const a = mem32[i++];
143+ const b = mem32[i++];
144+ const el = top(stack);
145+ const listener = new Listener(a, b, eventType, eventsTrampoline, el);
146+ listeners.add(listener);
147+ el.addEventListener(eventType, listener.callback);
148+ el[`dodrio-${eventType}`] = listener;
149+ return i;
150+ },
151+
152+ // 12
153+ function updateEventListener(stack, eventsTrampoline, listeners, mem8, mem32, i) {
154+ const pointer = mem32[i++];
155+ const length = mem32[i++];
156+ const eventType = string(mem8, pointer, length);
157+ const el = top(stack);
158+ const listener = el[`dodrio-${eventType}`];
159+ listener.a = mem32[i++];
160+ listener.b = mem32[i++];
161+ return i;
162+ },
163+
164+ // 13
165+ function removeEventListener(stack, eventsTrampoline, listeners, mem8, mem32, i) {
166+ const pointer = mem32[i++];
167+ const length = mem32[i++];
168+ const eventType = string(mem8, pointer, length);
169+ const el = top(stack);
170+ const listener = el[`dodrio-${eventType}`];
171+ el.removeEventListener(eventType, listener.callback);
172+ listeners.delete(listener);
173+ return i;
135 }
174 }
136];
175];
137
176
177+class Listener {
178+ constructor(a, b, eventType, trampoline, el) {
179+ this.a = a;
180+ this.b = b;
181+ this.eventType = eventType;
182+ this.trampoline = trampoline;
183+ this.el = el;
184+ this.callback = this.callback.bind(this);
185+ }
186+
187+ callback(event) {
188+ this.trampoline(event, this.a, this.b);
189+ }
190+}
191+
138// export
192// export
139class ChangeList {
193class ChangeList {
140 constructor(container) {
194 constructor(container) {
195+ this.listeners = new Set();
196+ this.trampoline = null;
141 this.container = container;
197 this.container = container;
142 this.ranges = [];
198 this.ranges = [];
143 this.stack = [];
199 this.stack = [];
144 }
200 }
145
201
202+ unmount() {
203+ for (const listener of this.listeners) {
204+ listener.el.removeEventListener(listener.eventType, listener.callback);
205+ listener.trampoline = () => {
206+ throw new Error("invocation of listener after it has been removed");
207+ };
208+ listener.a = 0;
209+ listener.b = 0;
210+ }
211+
212+ // Null out all of our properties just to ensure that if we mistakenly ever
213+ // call a method on this instance again, it will throw.
214+ this.listeners = null;
215+ this.trampoline = null;
216+ this.container = null;
217+ this.ranges = null;
218+ this.stack = null;
219+ }
220+
146 addChangeListRange(start, len) {
221 addChangeListRange(start, len) {
147 this.ranges.push(start);
222 this.ranges.push(start);
148 this.ranges.push(len);
223 this.ranges.push(len);
@@ -175,8 +250,14 @@ class ChangeList {
175 // console.log();
250 // console.log();
176 // console.log(OP_TABLE[op].name);
251 // console.log(OP_TABLE[op].name);
177 // console.log(this.stack);
252 // console.log(this.stack);
178− i = OP_TABLE[op](this.stack, mem8, mem32, i);
253+ i = OP_TABLE[op](this.stack, this.eventsTrampoline, this.listeners, mem8, mem32, i);
179 }
254 }
180 }
255 }
256+
257+ initEventsTrampoline(trampoline) {
258+ this.eventsTrampoline = (...args) => {
259+ trampoline(...args);
260+ };
261+ }
181}
262}
182window.ChangeList = ChangeList;
263window.ChangeList = ChangeList;
src/change_list.rs+110−2
@@ -1,5 +1,7 @@
1+use crate::Listener;
1use bumpalo::{Bump, BumpAllocSafe};
2use bumpalo::{Bump, BumpAllocSafe};
2use log::*;
3use log::*;
4+use std::fmt;
3use std::sync::Once;
5use std::sync::Once;
4use wasm_bindgen::prelude::*;
6use wasm_bindgen::prelude::*;
5
7
@@ -14,17 +16,44 @@ pub mod js {
14 #[wasm_bindgen(constructor)]
16 #[wasm_bindgen(constructor)]
15 pub fn new(container: &web_sys::Node) -> ChangeList;
17 pub fn new(container: &web_sys::Node) -> ChangeList;
16
18
19+ #[wasm_bindgen(structural, method)]
20+ pub fn unmount(this: &ChangeList);
21+
17 #[wasm_bindgen(structural, method, js_name = addChangeListRange)]
22 #[wasm_bindgen(structural, method, js_name = addChangeListRange)]
18 pub fn add_change_list_range(this: &ChangeList, start: usize, len: usize);
23 pub fn add_change_list_range(this: &ChangeList, start: usize, len: usize);
19
24
20 #[wasm_bindgen(structural, method, js_name = applyChanges)]
25 #[wasm_bindgen(structural, method, js_name = applyChanges)]
21 pub fn apply_changes(this: &ChangeList, memory: JsValue);
26 pub fn apply_changes(this: &ChangeList, memory: JsValue);
27+
28+ #[wasm_bindgen(structural, method, js_name = initEventsTrampoline)]
29+ pub fn init_events_trampoline(
30+ this: &ChangeList,
31+ trampoline: &Closure<Fn(web_sys::Event, u32, u32)>,
32+ );
22 }
33 }
23}
34}
24
35
25pub(crate) struct ChangeList {
36pub(crate) struct ChangeList {
26 bump: Bump,
37 bump: Bump,
27 js: js::ChangeList,
38 js: js::ChangeList,
39+ events_trampoline: Option<Closure<Fn(web_sys::Event, u32, u32)>>,
40+}
41+
42+impl Drop for ChangeList {
43+ fn drop(&mut self) {
44+ debug!("Dropping ChangeList");
45+ self.js.unmount();
46+ }
47+}
48+
49+impl fmt::Debug for ChangeList {
50+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
51+ f.debug_struct("ChangeList")
52+ .field("bump", &self.bump)
53+ .field("js", &self.js)
54+ .field("events_trampoline", &"..")
55+ .finish()
56+ }
28}
57}
29
58
30impl ChangeList {
59impl ChangeList {
@@ -42,7 +71,11 @@ impl ChangeList {
42
71
43 let bump = Bump::new();
72 let bump = Bump::new();
44 let js = js::ChangeList::new(container);
73 let js = js::ChangeList::new(container);
45− ChangeList { bump, js }
74+ ChangeList {
75+ bump,
76+ js,
77+ events_trampoline: None,
78+ }
46 }
79 }
47
80
48 pub(crate) fn apply_changes(&mut self) {
81 pub(crate) fn apply_changes(&mut self) {
@@ -55,6 +88,15 @@ impl ChangeList {
55 js.apply_changes(wasm_bindgen::memory());
88 js.apply_changes(wasm_bindgen::memory());
56 self.bump.reset();
89 self.bump.reset();
57 }
90 }
91+
92+ pub(crate) fn init_events_trampoline(
93+ &mut self,
94+ trampoline: Closure<Fn(web_sys::Event, u32, u32)>,
95+ ) {
96+ debug_assert!(self.events_trampoline.is_none());
97+ self.js.init_events_trampoline(&trampoline);
98+ self.events_trampoline = Some(trampoline);
99+ }
58}
100}
59
101
60#[wasm_bindgen]
102#[wasm_bindgen]
@@ -166,6 +208,37 @@ pub enum ChangeDiscriminant {
166 /// stack.push(createElement(String(pointer, length))
208 /// stack.push(createElement(String(pointer, length))
167 /// ```
209 /// ```
168 CreateElement = 10,
210 CreateElement = 10,
211+
212+ /// Immediate: `(pointer, length, A, B)`
213+ ///
214+ /// Stack: `[... Node] -> [... Node]`
215+ ///
216+ /// ```text
217+ /// event = String(pointer, length)
218+ /// callback = ProxyToRustCallback(A, B);
219+ /// stack.top().addEventListener(event, callback);
220+ /// ```
221+ NewEventListener = 11,
222+
223+ /// Immediate: `(pointer, length, A, B)`
224+ ///
225+ /// Stack: `[... Node] -> [... Node]`
226+ ///
227+ /// ```text
228+ /// event = String(pointer, length)
229+ /// new_callback = ProxyToRustCallback(A, B);
230+ /// stack.top().updateEventlistener(new_callback)
231+ /// ```
232+ UpdateEventListener = 12,
233+
234+ /// Immediates: `(pointer, length)`
235+ ///
236+ /// Stack: `[... Node] -> [... Node]`
237+ ///
238+ /// ```text
239+ /// stack.top().removeEventListener(String(pointer, length));
240+ /// ```
241+ RemoveEventListener = 13,
169}
242}
170
243
171impl BumpAllocSafe for ChangeDiscriminant {}
244impl BumpAllocSafe for ChangeDiscriminant {}
@@ -186,7 +259,7 @@ impl ChangeList {
186 self.bump.alloc([discriminant as u32, a, b]);
259 self.bump.alloc([discriminant as u32, a, b]);
187 }
260 }
188
261
189− // Note: no 3-immediates opcodes at this time.
262+ // Note: no 3-immediate opcodes at this time.
190
263
191 // Allocate an opcode with four immediates.
264 // Allocate an opcode with four immediates.
192 fn op4(&self, discriminant: ChangeDiscriminant, a: u32, b: u32, c: u32, d: u32) {
265 fn op4(&self, discriminant: ChangeDiscriminant, a: u32, b: u32, c: u32, d: u32) {
@@ -271,4 +344,39 @@ impl ChangeList {
271 tag_name.len() as u32,
344 tag_name.len() as u32,
272 );
345 );
273 }
346 }
347+
348+ pub(crate) fn emit_new_event_listener(&self, listener: &Listener) {
349+ debug!("emit_new_event_listener({:?})", listener);
350+ let (a, b) = listener.get_callback_parts();
351+ debug_assert!(a != 0);
352+ self.op4(
353+ ChangeDiscriminant::NewEventListener,
354+ listener.event.as_ptr() as u32,
355+ listener.event.len() as u32,
356+ a,
357+ b,
358+ );
359+ }
360+
361+ pub(crate) fn emit_update_event_listener(&self, listener: &Listener) {
362+ debug!("emit_update_event_listener({:?})", listener);
363+ let (a, b) = listener.get_callback_parts();
364+ debug_assert!(a != 0);
365+ self.op4(
366+ ChangeDiscriminant::UpdateEventListener,
367+ listener.event.as_ptr() as u32,
368+ listener.event.len() as u32,
369+ a,
370+ b,
371+ );
372+ }
373+
374+ pub(crate) fn emit_remove_event_listener(&self, event: &str) {
375+ debug!("emit_remove_event_listener({:?})", event);
376+ self.op2(
377+ ChangeDiscriminant::RemoveEventListener,
378+ event.as_ptr() as u32,
379+ event.len() as u32,
380+ );
381+ }
274}
382}
src/events.rsnew file+99
@@ -0,0 +1,99 @@
1+use crate::vdom::{VdomInner, VdomWeak};
2+use crate::{Listener, ListenerCallback};
3+use log::*;
4+use std::cell::RefCell;
5+use std::collections::HashMap;
6+use std::fmt;
7+use std::mem;
8+use std::rc::{Rc, Weak};
9+use wasm_bindgen::closure::Closure;
10+use wasm_bindgen::prelude::*;
11+
12+/// The events registry manages event listeners for a virtual DOM.
13+///
14+/// The events registry is persistent across virtual DOM rendering and double
15+/// buffering.
16+pub(crate) struct EventsRegistry {
17+ vdom: Weak<VdomInner>,
18+ active: HashMap<(u32, u32), ListenerCallback<'static>>,
19+}
20+
21+impl fmt::Debug for EventsRegistry {
22+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
23+ f.debug_struct("EventsRegistry")
24+ .field("active", &self.active.keys().collect::<Vec<_>>())
25+ .finish()
26+ }
27+}
28+
29+impl EventsRegistry {
30+ /// Construct a new events registry and JS function trampoline that weakly
31+ /// holds the new registry and can be used by JS to invoke listeners on the
32+ /// Rust side.
33+ pub(crate) fn new(
34+ vdom: Weak<VdomInner>,
35+ ) -> (
36+ Rc<RefCell<EventsRegistry>>,
37+ Closure<Fn(web_sys::Event, u32, u32)>,
38+ ) {
39+ let registry = Rc::new(RefCell::new(EventsRegistry {
40+ vdom,
41+ active: HashMap::new(),
42+ }));
43+
44+ let weak_registry = Rc::downgrade(&registry);
45+ let closure = Closure::wrap(Box::new(move |event, a, b| {
46+ debug!("Events trampoline invoked with (0x{:x}, 0x{:x})", a, b);
47+ debug_assert!(a != 0);
48+
49+ // if the VdomInnerExclusive is keeping this closure alive, then the
50+ // VdomInnerExclusive should also be keeping the registry alive
51+ let registry = weak_registry.upgrade().unwrap_throw();
52+ let registry = registry.borrow();
53+
54+ match registry.active.get(&(a, b)) {
55+ None => warn!(
56+ "EventsRegistry closure invoked with unknown listener parts: \
57+ (0x{:x}, 0x{:x})",
58+ a, b
59+ ),
60+ Some(callback) => {
61+ let vdom = registry.vdom.upgrade().expect_throw(
62+ "if the registry is still around, then the vdom should still be around",
63+ );
64+ let vdom_weak = VdomWeak::new(&vdom);
65+ let mut vdom = vdom.exclusive.borrow_mut();
66+ let component = vdom.component_raw_mut();
67+ callback(component, vdom_weak, event);
68+ }
69+ }
70+ }) as Box<Fn(web_sys::Event, u32, u32)>);
71+
72+ (registry, closure)
73+ }
74+
75+ /// Add an event listener to the registry, exposing to JS.
76+ ///
77+ /// # Unsafety
78+ ///
79+ /// The listener's lifetime is extended to `'static` and it is the caller's
80+ /// responsibility to ensure that the listener is not kept in the registry
81+ /// after it is dropped. This is typically done via clearing the whole
82+ /// registry on each render and repopulating it during diffing.
83+ pub(crate) unsafe fn add<'a>(&mut self, listener: &'a Listener<'a>) {
84+ let id = listener.get_callback_parts();
85+ debug!("EventsRegistry::add(0x{:x}, 0x{:x})", id.0, id.1);
86+ debug_assert!(id.0 != 0);
87+
88+ let callback =
89+ mem::transmute::<ListenerCallback<'a>, ListenerCallback<'static>>(listener.callback);
90+ let old = self.active.insert(id, callback);
91+ debug_assert!(old.is_none());
92+ }
93+
94+ /// Clear all event listeners from the registry.
95+ pub(crate) fn clear_active_listeners(&mut self) {
96+ debug!("EventsRegistry::clear_active_listeners()");
97+ self.active.clear();
98+ }
99+}
src/lib.rs+143−3
@@ -1,22 +1,78 @@
1−//! TODO
1+//! The `dodrio` virtual DOM.
2+//!
3+//! ## Example
4+//!
5+//! ```no_run
6+//! /// A component that greets someone.
7+//! pub struct Hello<'who> {
8+//! who: &'who str,
9+//! }
10+//!
11+//! impl<'who> Hello<'who> {
12+//! /// Construct a new `Hello` component that greets the given `who`.
13+//! pub fn new(who: &str) -> Hello {
14+//! Hello { who }
15+//! }
16+//! }
17+//!
18+//! impl<'who> Render for Hello<'who> {
19+//! fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
20+//! where
21+//! 'a: 'bump,
22+//! {
23+//! let id = bumpalo::format!(in bump, "hello-{}", self.who);
24+//! Node::element(
25+//! bump,
26+//! // The element's tag name. In this case a `<p>` element.
27+//! "p",
28+//! // Listeners.
29+//! [on(bump, "click", |root, _vdom, _event| {
30+//! let hello = root.unwrap_mut::<Hello>();
31+//! let window = web_sys::window().expect_throw("should have a `Window` on the Web");
32+//! window.alert(hello.who);
33+//! })],
34+//! // Attributes.
35+//! [Attribute { name: "id", value: &id }],
36+//! // Child nodes.
37+//! [
38+//! Node::text("Hello, "),
39+//! Node::element(
40+//! bump,
41+//! "strong",
42+//! [],
43+//! [],
44+//! [
45+//! Node::text(self.who),
46+//! Node::text("!"),
47+//! ],
48+//! ),
49+//! ],
50+//! )
51+//! }
52+//! }
53+//! ```
2
54
3// Re-export the `bumpalo` crate.
55// Re-export the `bumpalo` crate.
4pub use bumpalo;
56pub use bumpalo;
5
57
6use bumpalo::Bump;
58use bumpalo::Bump;
59+use std::any::Any;
60+use std::rc::Rc;
61+use wasm_bindgen::UnwrapThrowExt;
7
62
8// Only `pub` so that the wasm-bindgen bindings work.
63// Only `pub` so that the wasm-bindgen bindings work.
9#[doc(hidden)]
64#[doc(hidden)]
10pub mod change_list;
65pub mod change_list;
11
66
12mod cached;
67mod cached;
68+mod events;
13mod node;
69mod node;
14mod vdom;
70mod vdom;
15
71
16// Re-export items at the top level.
72// Re-export items at the top level.
17pub use self::cached::Cached;
73pub use self::cached::Cached;
18−pub use self::node::{Attribute, ElementNode, Node, TextNode};
74+pub use self::node::{on, Attribute, ElementNode, Listener, ListenerCallback, Node, TextNode};
19−pub use self::vdom::Vdom;
75+pub use self::vdom::{Vdom, VdomWeak};
20
76
21pub trait Render {
77pub trait Render {
22 fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
78 fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
@@ -35,3 +91,87 @@ where
35 (**self).render(bump)
91 (**self).render(bump)
36 }
92 }
37}
93}
94+
95+impl<R> Render for Rc<R>
96+where
97+ R: Render,
98+{
99+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
100+ where
101+ 'a: 'bump,
102+ {
103+ (**self).render(bump)
104+ }
105+}
106+
107+/// A `RootRender` is a render component that can be the root rendering component
108+/// mounted to a virtual DOM.
109+///
110+/// In addition to rendering, it must also be `'static` so that it can be owned
111+/// by the virtual DOM and `Any` so that it can be downcast to its concrete type
112+/// by event listener callbacks.
113+///
114+/// You do not need to implement this trait by hand: there is a blanket
115+/// implementation for all `Render` types that fulfill the `RootRender`
116+/// requirements.
117+pub trait RootRender: Any + Render {
118+ /// Get this `&RootRender` trait object as an `&Any` trait object reference.
119+ fn as_any(&self) -> &Any;
120+
121+ /// Get this `&mut RootRender` trait object as an `&mut Any` trait object
122+ /// reference.
123+ fn as_any_mut(&mut self) -> &mut Any;
124+}
125+
126+impl<T: Any + Render> RootRender for T {
127+ fn as_any(&self) -> &Any {
128+ self
129+ }
130+
131+ fn as_any_mut(&mut self) -> &mut Any {
132+ self
133+ }
134+}
135+
136+impl dyn RootRender {
137+ /// Downcast this shared `&dyn RootRender` trait object reference to its
138+ /// underlying concrete type.
139+ ///
140+ /// # Panics
141+ ///
142+ /// Panics if this virtual DOM's root rendering component is not an `R`
143+ /// instance.
144+ pub fn unwrap_ref<R: RootRender>(&self) -> &R {
145+ self.as_any()
146+ .downcast_ref::<R>()
147+ .expect_throw("bad `RootRender::unwrap_ref` call")
148+ }
149+
150+ /// Downcast this exclusive `&mut dyn RootRender` trait object reference to
151+ /// its underlying concrete type.
152+ ///
153+ /// # Panics
154+ ///
155+ /// Panics if this virtual DOM's root rendering component is not an `R`
156+ /// instance.
157+ pub fn unwrap_mut<R: RootRender>(&mut self) -> &mut R {
158+ self.as_any_mut()
159+ .downcast_mut::<R>()
160+ .expect_throw("bad `RootRender::unwrap_ref` call")
161+ }
162+}
163+
164+#[cfg(test)]
165+mod tests {
166+ #[test]
167+ fn render_is_object_safe() {
168+ #[allow(dead_code)]
169+ fn takes_dyn_render(_: &dyn super::Render) {}
170+ }
171+
172+ #[test]
173+ fn root_render_is_object_safe() {
174+ #[allow(dead_code)]
175+ fn takes_dyn_render(_: &dyn super::RootRender) {}
176+ }
177+}
src/node.rs+108−29
@@ -1,4 +1,7 @@
1+use crate::{RootRender, VdomWeak};
1use bumpalo::{Bump, BumpAllocSafe};
2use bumpalo::{Bump, BumpAllocSafe};
3+use std::fmt;
4+use std::mem;
2
5
3/// A node is either a text node or an element.
6/// A node is either a text node or an element.
4#[derive(Debug, Clone)]
7#[derive(Debug, Clone)]
@@ -22,17 +25,83 @@ pub struct TextNode<'a> {
22#[derive(Debug, Clone)]
25#[derive(Debug, Clone)]
23pub struct ElementNode<'a> {
26pub struct ElementNode<'a> {
24 pub(crate) tag_name: &'a str,
27 pub(crate) tag_name: &'a str,
28+ pub(crate) listeners: &'a [Listener<'a>],
25 pub(crate) attributes: &'a [Attribute<'a>],
29 pub(crate) attributes: &'a [Attribute<'a>],
26 pub(crate) children: &'a [Node<'a>],
30 pub(crate) children: &'a [Node<'a>],
27}
31}
28
32
33+/// An event listener callback function.
34+///
35+/// It takes three parameters:
36+///
37+/// 1. The virtual DOM's root rendering component.
38+/// 2. A capability to scheduler virtual DOM re-rendering.
39+/// 3. The event that ocurred.
40+pub type ListenerCallback<'a> =
41+ &'a (dyn Fn(&mut dyn RootRender, VdomWeak, web_sys::Event) + 'static);
42+
43+/// An event listener.
44+pub struct Listener<'a> {
45+ /// The type of event to listen for.
46+ pub event: &'a str,
47+ /// The callback to invoke when the event happens.
48+ pub callback: ListenerCallback<'a>,
49+}
50+
29#[derive(Clone, Debug)]
51#[derive(Clone, Debug)]
30pub struct Attribute<'a> {
52pub struct Attribute<'a> {
31 pub name: &'a str,
53 pub name: &'a str,
32 pub value: &'a str,
54 pub value: &'a str,
33}
55}
34
56
57+impl<'a> BumpAllocSafe for Node<'a> {}
58+impl<'a> BumpAllocSafe for Listener<'a> {}
59+impl<'a> BumpAllocSafe for Attribute<'a> {}
60+
61+impl fmt::Debug for Listener<'_> {
62+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63+ let (a, b) = self.get_callback_parts();
64+ let a = a as *mut u32;
65+ let b = b as *mut u32;
66+ f.debug_struct("Listener")
67+ .field("event", &self.event)
68+ .field("callback", &(a, b))
69+ .finish()
70+ }
71+}
72+
35impl<'a> Node<'a> {
73impl<'a> Node<'a> {
74+ /// Construct a new element node with the given tag name and children.
75+ #[inline]
76+ pub fn element<Listeners, Attributes, Children>(
77+ bump: &'a Bump,
78+ tag_name: &'a str,
79+ listeners: Listeners,
80+ attributes: Attributes,
81+ children: Children,
82+ ) -> Node<'a>
83+ where
84+ Listeners: 'a + BumpAllocSafe + AsRef<[Listener<'a>]>,
85+ Attributes: 'a + BumpAllocSafe + AsRef<[Attribute<'a>]>,
86+ Children: 'a + BumpAllocSafe + AsRef<[Node<'a>]>,
87+ {
88+ let children: &'a Children = bump.alloc(children);
89+ let children: &'a [Node<'a>] = children.as_ref();
90+
91+ let listeners: &'a Listeners = bump.alloc(listeners);
92+ let listeners: &'a [Listener<'a>] = listeners.as_ref();
93+
94+ let attributes: &'a Attributes = bump.alloc(attributes);
95+ let attributes: &'a [Attribute<'a>] = attributes.as_ref();
96+
97+ Node::Element(ElementNode {
98+ tag_name,
99+ listeners,
100+ attributes,
101+ children,
102+ })
103+ }
104+
36 /// Is this node a text node?
105 /// Is this node a text node?
37 pub fn is_text(&self) -> bool {
106 pub fn is_text(&self) -> bool {
38 match self {
107 match self {
@@ -48,6 +117,12 @@ impl<'a> Node<'a> {
48 _ => false,
117 _ => false,
49 }
118 }
50 }
119 }
120+
121+ /// Construct a new text node with the given text.
122+ #[inline]
123+ pub fn text(text: &'a str) -> Node<'a> {
124+ Node::Text(TextNode { text })
125+ }
51}
126}
52
127
53impl<'a> TextNode<'a> {
128impl<'a> TextNode<'a> {
@@ -74,38 +149,42 @@ impl<'a> ElementNode<'a> {
74 }
149 }
75}
150}
76
151
77−impl<'a> BumpAllocSafe for Node<'a> {}
152+union CallbackFatPtr<'a> {
78−impl<'a> BumpAllocSafe for Attribute<'a> {}
153+ callback: ListenerCallback<'a>,
154+ parts: (u32, u32),
155+}
79
156
80−impl<'a> Node<'a> {
157+impl Listener<'_> {
81− /// Construct a new text node with the given text.
82 #[inline]
158 #[inline]
83− pub fn text(text: &'a str) -> Node<'a> {
159+ pub(crate) fn get_callback_parts(&self) -> (u32, u32) {
84− Node::Text(TextNode { text })
160+ assert_eq!(
161+ mem::size_of::<ListenerCallback>(),
162+ mem::size_of::<CallbackFatPtr>()
163+ );
164+
165+ unsafe {
166+ let fat = CallbackFatPtr {
167+ callback: self.callback,
168+ };
169+ let (a, b) = fat.parts;
170+ debug_assert!(a != 0);
171+ (a, b)
172+ }
85 }
173 }
174+}
86
175
87− /// Construct a new element node with the given tag name and children.
176+/// Utility function for creating event listeners and downcasting the component
88− #[inline]
177+/// to its `RootRender` concrete type.
89− pub fn element<Attributes, Children>(
178+pub fn on<'a, F>(bump: &'a Bump, event: &'a str, callback: F) -> Listener<'a>
90− bump: &'a Bump,
91− tag_name: &'a str,
92− attributes: Attributes,
93− children: Children,
94− ) -> Node<'a>
95− where
179+where
96− Attributes: 'a + BumpAllocSafe + AsRef<[Attribute<'a>]>,
180+ F: Fn(&mut dyn RootRender, VdomWeak, web_sys::Event) + 'static,
97− Children: 'a + BumpAllocSafe + AsRef<[Node<'a>]>,
181+{
98− {
182+ Listener {
99− let children: &'a Children = bump.alloc(children);
183+ event,
100− let children: &'a [Node<'a>] = children.as_ref();
184+ callback: bump.alloc(
101−
185+ move |component: &mut dyn RootRender, vdom: VdomWeak, event: web_sys::Event| {
102− let attributes: &'a Attributes = bump.alloc(attributes);
186+ callback(component, vdom, event);
103− let attributes: &'a [Attribute<'a>] = attributes.as_ref();
104−
105− Node::Element(ElementNode {
106− tag_name,
107− attributes,
108− children,
109− })
187+ },
188+ ),
110 }
189 }
111}
190}
src/vdom.rs+417−69
@@ -1,16 +1,63 @@
1use super::change_list::ChangeList;
1use super::change_list::ChangeList;
2−use super::node::{Attribute, ElementNode, Node, TextNode};
2+use super::node::{Attribute, ElementNode, Listener, Node, TextNode};
3−use super::Render;
3+use super::RootRender;
4+use crate::events::EventsRegistry;
4use bumpalo::Bump;
5use bumpalo::Bump;
6+use futures::future::Future;
5use log::*;
7use log::*;
8+use std::cell::Cell;
9+use std::cell::RefCell;
6use std::cmp;
10use std::cmp;
11+use std::fmt;
7use std::mem;
12use std::mem;
13+use std::mem::ManuallyDrop;
14+use std::rc::{Rc, Weak};
15+use wasm_bindgen::prelude::*;
16+use wasm_bindgen::JsCast;
17+use wasm_bindgen_futures::JsFuture;
18+
19+/// A strong handle to a mounted virtual DOM.
20+///
21+/// When this handle is dropped, the virtual DOM is unmounted and its listeners
22+/// removed. To keep it mounted forever, use the `Vdom::forget` method.
23+#[must_use = "A `Vdom` will only keep rendering and listening to events while it has not been \
24+ dropped. If you want a `Vdom` to run forever, call `Vdom::forget`."]
25+pub struct Vdom {
26+ inner: Rc<VdomInner>,
27+}
28+
29+/// A weak handle to a virtual DOM.
30+///
31+/// Does not prevent the virtual DOM from being unmounted: only keeping the
32+/// original `Vdom` alive guarantees that.
33+///
34+/// A `VdomWeak` also gives you the capability to scheduling re-rendering (say
35+/// after mutating the render component state).
36+#[derive(Clone, Debug)]
37+pub struct VdomWeak {
38+ inner: Weak<VdomInner>,
39+}
40+
41+#[derive(Debug)]
42+pub(crate) struct VdomInner {
43+ pub(crate) shared: VdomInnerShared,
44+ pub(crate) exclusive: RefCell<VdomInnerExclusive>,
45+}
46+
47+pub(crate) struct VdomInnerShared {
48+ pub(crate) render_scheduled: Cell<Option<js_sys::Promise>>,
49+}
50+
51+pub(crate) struct VdomInnerExclusive {
52+ // Always `Some` except just before we drop. Just an option so that
53+ // `unmount` can take the component out but we can still have a Drop
54+ // implementation.
55+ component: Option<Box<RootRender>>,
8
56
9−pub struct Vdom<R> {
10− component: R,
11 dom_buffers: [Bump; 2],
57 dom_buffers: [Bump; 2],
12− change_list: ChangeList,
58+ change_list: ManuallyDrop<ChangeList>,
13 container: web_sys::Element,
59 container: web_sys::Element,
60+ events_registry: Option<Rc<RefCell<EventsRegistry>>>,
14
61
15 // Actually a reference into `self.dom_buffers[0]` or if `self.component` is
62 // Actually a reference into `self.dom_buffers[0]` or if `self.component` is
16 // caching renders, into `self.component`'s bump.
63 // caching renders, into `self.component`'s bump.
@@ -21,19 +68,77 @@ unsafe fn extend_node_lifetime<'a>(node: Node<'a>) -> Node<'static> {
21 mem::transmute(node)
68 mem::transmute(node)
22}
69}
23
70
71+impl fmt::Debug for VdomInnerShared {
72+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
73+ let render_scheduled = Cell::new(None);
74+ self.render_scheduled.swap(&render_scheduled);
75+ let render_scheduled = render_scheduled.into_inner();
76+ let r = f
77+ .debug_struct("VdomInnerShared")
78+ .field("render_scheduled", &render_scheduled)
79+ .finish();
80+ self.render_scheduled.set(render_scheduled);
81+ r
82+ }
83+}
84+
85+impl fmt::Debug for VdomInnerExclusive {
86+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
87+ f.debug_struct("VdomInnerExclusive")
88+ .field("component", &"..")
89+ .field("dom_buffers", &self.dom_buffers)
90+ .field("change_list", &self.change_list)
91+ .field("container", &self.container)
92+ .field("events_registry", &self.events_registry)
93+ .field("current_root", &self.current_root)
94+ .finish()
95+ }
96+}
97+
98+impl Drop for VdomInnerExclusive {
99+ fn drop(&mut self) {
100+ debug!("Dropping VdomInnerExclusive");
101+
102+ // Make sure that we clean up our JS listeners and all that before we
103+ // empty the container.
104+ unsafe {
105+ ManuallyDrop::drop(&mut self.change_list);
106+ }
107+
108+ let registry = self.events_registry.take().unwrap_throw();
109+ let mut registry = registry.borrow_mut();
110+ registry.clear_active_listeners();
111+
112+ self.container.set_inner_html("");
113+ }
114+}
115+
24−impl<R> Vdom<R>
116+impl Vdom {
117+ /// Mount a new `Vdom` in the given container element with the given root
118+ /// rendering component.
119+ ///
120+ /// This will box the given component into trait object.
121+ pub fn new<R>(container: &web_sys::Element, component: R) -> Vdom
25−where
122+ where
26− R: Render,
123+ R: RootRender,
27−{
124+ {
125+ Self::with_boxed_root_render(container, Box::new(component) as Box<RootRender>)
126+ }
127+
128+ /// Construct a `Vdom` with the already-boxed-as-a-trait-object root
129+ /// rendering component.
130+ pub fn with_boxed_root_render(
28− pub fn new(container: &web_sys::Element, component: R) -> Vdom<R> {
131+ container: &web_sys::Element,
132+ component: Box<RootRender>,
133+ ) -> Vdom {
29 let dom_buffers = [Bump::new(), Bump::new()];
134 let dom_buffers = [Bump::new(), Bump::new()];
30− let change_list = ChangeList::new(container);
135+ let change_list = ManuallyDrop::new(ChangeList::new(container));
31
136
32 // Ensure that the container is empty.
137 // Ensure that the container is empty.
33 container.set_inner_html("");
138 container.set_inner_html("");
34
139
35 // Create a dummy `<div/>` in our container.
140 // Create a dummy `<div/>` in our container.
36− let current_root = Node::element(&dom_buffers[0], "div", [], []);
141+ let current_root = Node::element(&dom_buffers[0], "div", [], [], []);
37 let current_root = Some(unsafe { extend_node_lifetime(current_root) });
142 let current_root = Some(unsafe { extend_node_lifetime(current_root) });
38 let window = web_sys::window().expect("should have acess to the Window");
143 let window = web_sys::window().expect("should have acess to the Window");
39 let document = window
144 let document = window
@@ -48,76 +153,114 @@ where
48 )
153 )
49 .expect("should append child OK");
154 .expect("should append child OK");
50
155
51− // Diff and apply the `contents` against our dummy `<div/>`.
52 let container = container.clone();
156 let container = container.clone();
53− let mut vdom = Vdom {
157+ let inner = Rc::new(VdomInner {
54− component,
158+ shared: VdomInnerShared {
159+ render_scheduled: Cell::new(None),
160+ },
161+ exclusive: RefCell::new(VdomInnerExclusive {
162+ component: Some(component),
55− dom_buffers,
163+ dom_buffers,
56− change_list,
164+ change_list,
57− container,
165+ container,
58− current_root,
166+ current_root,
167+ events_registry: None,
168+ }),
59− };
169+ });
170+
171+ let (events_registry, events_trampoline) = EventsRegistry::new(Rc::downgrade(&inner));
172+
173+ {
174+ let mut inner = inner.exclusive.borrow_mut();
175+ inner.events_registry = Some(events_registry);
176+ inner.change_list.init_events_trampoline(events_trampoline);
177+
178+ // Diff and apply the `contents` against our dummy `<div/>`.
60− vdom.render();
179+ inner.render();
61− vdom
180+ }
181+
182+ Vdom { inner }
62 }
183 }
63
184
64− /// Get a shared reference to the underlying render component.
185+ /// Run this virtual DOM and its listeners forever and never unmount it.
65 #[inline]
186 #[inline]
66− pub fn component(&self) -> &R {
187+ pub fn forget(self) {
67− &self.component
188+ mem::forget(self);
68 }
189 }
69
190
70− /// Get an exclusive reference to the underlying render component.
191+ /// Get a weak handle to this virtual DOM.
71 #[inline]
192 #[inline]
72− pub fn component_mut(&mut self) -> &mut R {
193+ pub fn weak(&self) -> VdomWeak {
73− &mut self.component
194+ VdomWeak::new(&self.inner)
74 }
195 }
75
196
76− /// Take the render component out of this virtual DOM.
197+ /// Unmount this virtual DOM, unregister its event listeners, and return its
198+ /// root render component.
77 #[inline]
199 #[inline]
78− pub fn into_component(self) -> R {
200+ pub fn unmount(self) -> Box<RootRender> {
201+ Rc::try_unwrap(self.inner.clone())
202+ .map_err(|_| ())
203+ .unwrap_throw()
204+ .exclusive
205+ .into_inner()
79− self.component
206+ .component
207+ .take()
208+ .unwrap_throw()
80 }
209 }
210+}
81
211
82− /// Get a reference to the physical DOM node that reflects this virtual DOM
212+impl VdomInnerExclusive {
83− /// tree.
213+ /// Get an exclusive reference to the underlying render component as a raw
84− pub fn container(&self) -> &web_sys::Element {
214+ /// trait object.
85− &self.container
215+ #[inline]
216+ pub(crate) fn component_raw_mut(&mut self) -> &mut dyn RootRender {
217+ &mut **self.component.as_mut().unwrap_throw()
86 }
218 }
87
219
88 /// Re-render this virtual dom's current component.
220 /// Re-render this virtual dom's current component.
89− pub fn render(&mut self) {
221+ pub(crate) fn render(&mut self) {
90 unsafe {
222 unsafe {
223+ let events_registry = self.events_registry.take().unwrap();
224+
225+ {
226+ // All the old listeners are no longer active. We will build a new
227+ // set of active listeners when diffing.
228+ //
229+ // NB: if we end up avoiding diffing cached renders (instead of just
230+ // avoiding re-rendering them) then we will need to maintain cached
231+ // active listeners, and can't just clear all active listeners and
232+ // rebuild them here.
233+ let mut registry = events_registry.borrow_mut();
234+ registry.clear_active_listeners();
235+
236+ // Reset the inactive bump arena's pointer.
91− self.dom_buffers[1].reset();
237+ self.dom_buffers[1].reset();
92− let new_contents = self.component.render(&self.dom_buffers[1]);
93− let new_contents = extend_node_lifetime(new_contents);
94
238
239+ // Render the new current contents into the inactive bump arena.
240+ let new_contents = self
241+ .component
242+ .as_ref()
243+ .unwrap_throw()
244+ .render(&self.dom_buffers[1]);
245+ let new_contents = extend_node_lifetime(new_contents);
246+
247+ // Diff the old contents with the new contents.
95− let old_contents = self.current_root.take().unwrap();
248+ let old_contents = self.current_root.take().unwrap();
96− self.diff(old_contents, new_contents.clone());
249+ self.diff(&mut registry, old_contents, new_contents.clone());
250+
251+ // Swap the buffers to make the bump arena with the new contents the
252+ // active arena, and the old one into the inactive arena.
253+ self.swap_buffers();
254+ self.set_current_root(new_contents);
255+ }
97
256
98− self.swap_buffers();
257+ self.events_registry = Some(events_registry);
99− self.set_current_root(new_contents);
100
258
259+ // Tell JS to apply our diff-generated changes to the physical DOM!
101 self.change_list.apply_changes();
260 self.change_list.apply_changes();
102 }
261 }
103 }
262 }
104
263
105− /// Render a new component.
106− pub fn render_component<S>(self, component: S) -> Vdom<S>
107− where
108− S: Render,
109− {
110− let mut vdom = Vdom {
111− component,
112− dom_buffers: self.dom_buffers,
113− change_list: self.change_list,
114− container: self.container,
115− current_root: self.current_root,
116− };
117− vdom.render();
118− vdom
119− }
120−
121 fn swap_buffers(&mut self) {
264 fn swap_buffers(&mut self) {
122 let (first, second) = self.dom_buffers.as_mut().split_at_mut(1);
265 let (first, second) = self.dom_buffers.as_mut().split_at_mut(1);
123 mem::swap(&mut first[0], &mut second[0]);
266 mem::swap(&mut first[0], &mut second[0]);
@@ -128,7 +271,7 @@ where
128 self.current_root = Some(current);
271 self.current_root = Some(current);
129 }
272 }
130
273
131− fn diff(&self, old: Node, new: Node) {
274+ fn diff<'a>(&self, registry: &mut EventsRegistry, old: Node<'a>, new: Node<'a>) {
132 // debug!("---------------------------------------------------------");
275 // debug!("---------------------------------------------------------");
133 // debug!("dodrio::Vdom::diff");
276 // debug!("dodrio::Vdom::diff");
134 // debug!(" old = {:#?}", old);
277 // debug!(" old = {:#?}", old);
@@ -143,22 +286,24 @@ where
143 }
286 }
144 (&Node::Text(TextNode { .. }), Node::Element(ElementNode { .. })) => {
287 (&Node::Text(TextNode { .. }), Node::Element(ElementNode { .. })) => {
145 debug!(" replacing a text node with an element");
288 debug!(" replacing a text node with an element");
146− self.create(new);
289+ self.create(registry, new);
147 self.change_list.emit_replace_with();
290 self.change_list.emit_replace_with();
148 }
291 }
149 (&Node::Element(ElementNode { .. }), Node::Text(TextNode { .. })) => {
292 (&Node::Element(ElementNode { .. }), Node::Text(TextNode { .. })) => {
150 debug!(" replacing an element with a text node");
293 debug!(" replacing an element with a text node");
151− self.create(new);
294+ self.create(registry, new);
152 self.change_list.emit_replace_with();
295 self.change_list.emit_replace_with();
153 }
296 }
154 (
297 (
155 &Node::Element(ElementNode {
298 &Node::Element(ElementNode {
156 tag_name: new_tag_name,
299 tag_name: new_tag_name,
300+ listeners: new_listeners,
157 attributes: new_attributes,
301 attributes: new_attributes,
158 children: new_children,
302 children: new_children,
159 }),
303 }),
160 Node::Element(ElementNode {
304 Node::Element(ElementNode {
161 tag_name: old_tag_name,
305 tag_name: old_tag_name,
306+ listeners: old_listeners,
162 attributes: old_attributes,
307 attributes: old_attributes,
163 children: old_children,
308 children: old_children,
164 }),
309 }),
@@ -166,16 +311,48 @@ where
166 debug!(" updating an element");
311 debug!(" updating an element");
167 if new_tag_name != old_tag_name {
312 if new_tag_name != old_tag_name {
168 debug!(" different tag names; creating new element and replacing old element");
313 debug!(" different tag names; creating new element and replacing old element");
169− self.create(new);
314+ self.create(registry, new);
170 self.change_list.emit_replace_with();
315 self.change_list.emit_replace_with();
171 return;
316 return;
172 }
317 }
318+ self.diff_listeners(registry, old_listeners, new_listeners);
173 self.diff_attributes(old_attributes, new_attributes);
319 self.diff_attributes(old_attributes, new_attributes);
174− self.diff_children(old_children, new_children);
320+ self.diff_children(registry, old_children, new_children);
175 }
321 }
176 }
322 }
177 }
323 }
178
324
325+ fn diff_listeners<'a>(
326+ &self,
327+ registry: &mut EventsRegistry,
328+ old: &'a [Listener<'a>],
329+ new: &'a [Listener<'a>],
330+ ) {
331+ debug!(" updating event listeners");
332+
333+ 'outer1: for new_l in new {
334+ unsafe {
335+ registry.add(new_l);
336+ }
337+ for old_l in old {
338+ if new_l.event == old_l.event {
339+ self.change_list.emit_update_event_listener(new_l);
340+ continue 'outer1;
341+ }
342+ }
343+ self.change_list.emit_new_event_listener(new_l);
344+ }
345+
346+ 'outer2: for old_l in old {
347+ for new_l in new {
348+ if new_l.event == old_l.event {
349+ continue 'outer2;
350+ }
351+ }
352+ self.change_list.emit_remove_event_listener(old_l.event);
353+ }
354+ }
355+
179 fn diff_attributes(&self, old: &[Attribute], new: &[Attribute]) {
356 fn diff_attributes(&self, old: &[Attribute], new: &[Attribute]) {
180 debug!(" updating attributes");
357 debug!(" updating attributes");
181
358
@@ -205,7 +382,12 @@ where
205 }
382 }
206 }
383 }
207
384
208− fn diff_children(&self, old: &[Node], new: &[Node]) {
385+ fn diff_children<'a>(
386+ &self,
387+ registry: &mut EventsRegistry,
388+ old: &'a [Node<'a>],
389+ new: &'a [Node<'a>],
390+ ) {
209 debug!(" updating children shared by old and new");
391 debug!(" updating children shared by old and new");
210
392
211 let num_children_to_diff = cmp::min(new.len(), old.len());
393 let num_children_to_diff = cmp::min(new.len(), old.len());
@@ -227,7 +409,7 @@ where
227 self.change_list.emit_pop_push_next_sibling();
409 self.change_list.emit_pop_push_next_sibling();
228 }
410 }
229
411
230− self.diff(old_child.clone(), new_child.clone());
412+ self.diff(registry, old_child.clone(), new_child.clone());
231 }
413 }
232
414
233 if old_children.next().is_some() {
415 if old_children.next().is_some() {
@@ -247,7 +429,7 @@ where
247 self.change_list.emit_pop();
429 self.change_list.emit_pop();
248 pushed = false;
430 pushed = false;
249 }
431 }
250− self.create(new_child.clone());
432+ self.create(registry, new_child.clone());
251 self.change_list.emit_append_child();
433 self.change_list.emit_append_child();
252 }
434 }
253 }
435 }
@@ -258,25 +440,191 @@ where
258 }
440 }
259 }
441 }
260
442
261− fn create(&self, node: Node) {
443+ fn create<'a>(&self, registry: &mut EventsRegistry, node: Node<'a>) {
262 match node {
444 match node {
263 Node::Text(TextNode { text }) => {
445 Node::Text(TextNode { text }) => {
264 self.change_list.emit_create_text_node(text);
446 self.change_list.emit_create_text_node(text);
265 }
447 }
266 Node::Element(ElementNode {
448 Node::Element(ElementNode {
267 tag_name,
449 tag_name,
450+ listeners,
268 attributes,
451 attributes,
269 children,
452 children,
270 }) => {
453 }) => {
271 self.change_list.emit_create_element(tag_name);
454 self.change_list.emit_create_element(tag_name);
455+ for l in listeners {
456+ unsafe {
457+ registry.add(l);
458+ }
459+ self.change_list.emit_new_event_listener(l);
460+ }
272 for attr in attributes {
461 for attr in attributes {
273 self.change_list.emit_set_attribute(&attr.name, &attr.value);
462 self.change_list.emit_set_attribute(&attr.name, &attr.value);
274 }
463 }
275 for child in children {
464 for child in children {
276− self.create(child.clone());
465+ self.create(registry, child.clone());
277 self.change_list.emit_append_child();
466 self.change_list.emit_append_child();
278 }
467 }
279 }
468 }
280 }
469 }
281 }
470 }
282}
471}
472+
473+fn request_animation_frame(f: &Closure<FnMut()>) {
474+ web_sys::window()
475+ .expect_throw("should have a window")
476+ .request_animation_frame(f.as_ref().unchecked_ref())
477+ .expect_throw("should register `requestAnimationFrame` OK");
478+}
479+
480+fn with_animation_frame<F>(mut f: F)
481+where
482+ F: 'static + FnMut(),
483+{
484+ let g = Rc::new(RefCell::new(None));
485+ let h = g.clone();
486+
487+ let f = Closure::wrap(Box::new(move || {
488+ *g.borrow_mut() = None;
489+ f();
490+ }) as Box<FnMut()>);
491+ request_animation_frame(&f);
492+
493+ *h.borrow_mut() = Some(f);
494+}
495+
496+trait CellOptionExt<T> {
497+ fn take(&self) -> Option<T>;
498+}
499+
500+impl<T> CellOptionExt<T> for Cell<Option<T>> {
501+ fn take(&self) -> Option<T> {
502+ let temp = Cell::new(None);
503+ self.swap(&temp);
504+ temp.into_inner()
505+ }
506+}
507+
508+/// An operation failed because the virtual DOM was already dropped and
509+/// unmounted.
510+#[derive(Debug)]
511+pub struct VdomDroppedError {}
512+
513+impl fmt::Display for VdomDroppedError {
514+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
515+ write!(f, "The virtual DOM was dropped.")
516+ }
517+}
518+
519+impl std::error::Error for VdomDroppedError {}
520+
521+impl VdomWeak {
522+ /// Construct a new weak handle to the given virtual DOM.
523+ #[inline]
524+ pub(crate) fn new(inner: &Rc<VdomInner>) -> VdomWeak {
525+ VdomWeak {
526+ inner: Rc::downgrade(inner),
527+ }
528+ }
529+
530+ /// TODO
531+ pub fn set_component(
532+ self,
533+ root: Box<dyn RootRender>,
534+ ) -> impl Future<Item = Box<dyn RootRender + 'static>, Error = VdomDroppedError> {
535+ futures::future::ok(self.inner.upgrade())
536+ .and_then(|inner| inner.ok_or(()))
537+ .map_err(|_| VdomDroppedError {})
538+ .and_then(|inner| {
539+ let promise = js_sys::Promise::resolve(&JsValue::null());
540+ JsFuture::from(promise)
541+ .map_err(|_| VdomDroppedError {})
542+ .and_then(move |_| {
543+ let old = {
544+ let mut exclusive = inner.exclusive.borrow_mut();
545+ mem::replace(&mut *exclusive.component.as_mut().unwrap_throw(), root)
546+ };
547+ VdomWeak::new(&inner).render().map(|_| old)
548+ })
549+ })
550+ }
551+
552+ /// Execute `f` with a reference to this virtual DOM's root rendering
553+ /// component.
554+ ///
555+ /// To ensure exclusive access to the root rendering component, the
556+ /// invocation takes place on a new tick of the micro-task queue.
557+ pub fn with_component<F, T>(&self, f: F) -> impl Future<Item = T, Error = VdomDroppedError>
558+ where
559+ F: 'static + FnOnce(&mut dyn RootRender) -> T,
560+ {
561+ futures::future::ok(self.inner.upgrade())
562+ .and_then(|inner| inner.ok_or(()))
563+ .map_err(|_| VdomDroppedError {})
564+ .and_then(|inner| {
565+ let mut f = Some(f);
566+ let promise = js_sys::Promise::resolve(&JsValue::null());
567+ JsFuture::from(promise)
568+ .map_err(|_| VdomDroppedError {})
569+ .map(move |_| {
570+ let f = f.take().unwrap_throw();
571+ let mut exclusive = inner.exclusive.borrow_mut();
572+ f(exclusive.component_raw_mut())
573+ })
574+ })
575+ }
576+
577+ /// Schedule a render to occur during the next animation frame.
578+ ///
579+ /// If you want a future that resolves after the render has finished, use
580+ /// `render` instead.
581+ pub fn schedule_render(&self) {
582+ debug!("VdomWeak::schedule_render");
583+ wasm_bindgen_futures::spawn_local(self.render().map_err(|_| ()));
584+ }
585+
586+ /// Schedule a render to occur during the next animation frame and return a
587+ /// future that will complete once the render has finished.
588+ ///
589+ /// If you don't want to do more things after the render completes, then use
590+ /// `schedule_render` instead of `render`.
591+ pub fn render(&self) -> impl Future<Item = (), Error = VdomDroppedError> {
592+ debug!("VdomWeak::render: initiating render in new animation frame");
593+ futures::future::ok(self.inner.upgrade())
594+ .and_then(|inner| inner.ok_or(()))
595+ .map_err(|_| VdomDroppedError {})
596+ .and_then(|inner| {
597+ let promise = inner.shared.render_scheduled.take().unwrap_or_else(|| {
598+ js_sys::Promise::new(&mut |resolve, reject| {
599+ let vdom = VdomWeak {
600+ inner: Rc::downgrade(&inner),
601+ };
602+ with_animation_frame(move || match vdom.inner.upgrade() {
603+ None => {
604+ warn!("VdomWeak::render: vdom unmounted before we could render");
605+ let r = reject.call0(&JsValue::null());
606+ debug_assert!(r.is_ok());
607+ }
608+ Some(inner) => {
609+ let mut exclusive = inner.exclusive.borrow_mut();
610+ exclusive.render();
611+
612+ // We did the render, so take the promise away
613+ // and let future `render` calls request new
614+ // animation frames.
615+ let _ = inner.shared.render_scheduled.take();
616+
617+ debug!("VdomWeak::render: finished rendering");
618+ let r = resolve.call0(&JsValue::null());
619+ debug_assert!(r.is_ok());
620+ }
621+ });
622+ })
623+ });
624+ inner.shared.render_scheduled.set(Some(promise.clone()));
625+ JsFuture::from(promise)
626+ .map(|_| ())
627+ .map_err(|_| VdomDroppedError {})
628+ })
629+ }
630+}
tests/web/cached.rs+65−17
@@ -1,7 +1,9 @@
1use super::{assert_rendered, create_element, RenderFn};
1use super::{assert_rendered, create_element, RenderFn};
2−use bumpalo::Bump;
3−use dodrio::{Cached, Node, Render, Vdom};
2+use dodrio::{bumpalo::Bump, Cached, Node, Render, Vdom};
3+use futures::prelude::*;
4use std::cell::Cell;
4use std::cell::Cell;
5+use std::rc::Rc;
6+use wasm_bindgen::*;
5use wasm_bindgen_test::*;
7use wasm_bindgen_test::*;
6
8
7pub struct CountRenders {
9pub struct CountRenders {
@@ -29,23 +31,69 @@ impl Render for CountRenders {
29 }
31 }
30}
32}
31
33
32−#[wasm_bindgen_test]
34+#[wasm_bindgen_test(async)]
33−fn uses_cached_render() {
35+fn uses_cached_render() -> impl Future<Item = (), Error = JsValue> {
34− let container = create_element("div");
35 let cached = Cached::new(CountRenders::new());
36 let cached = Cached::new(CountRenders::new());
36− let mut vdom = Vdom::new(&container, cached);
37
37
38− for _ in 0..10 {
38+ let container0 = create_element("div");
39− vdom.render();
39+ let container1 = container0.clone();
40− assert_eq!(vdom.component().render_count.get(), 1);
40+ let container2 = container0.clone();
41− assert_rendered(&container, &RenderFn(|_| Node::text("1")))
41+ let container3 = container0.clone();
42− }
42+ let container4 = container0.clone();
43
43
44− Cached::invalidate(vdom.component_mut());
44+ let vdom0 = Rc::new(Vdom::new(&container0, cached));
45+ let vdom1 = vdom0.clone();
46+ let vdom2 = vdom0.clone();
47+ let vdom3 = vdom0.clone();
48+ let vdom4 = vdom0.clone();
49+ let vdom5 = vdom0.clone();
50+ let vdom6 = vdom0.clone();
51+ let vdom7 = vdom0.clone();
45
52
46− for _ in 0..10 {
53+ vdom0
47− vdom.render();
54+ .weak()
55+ // We render, populate the cache, and get "1".
56+ .render()
57+ .and_then(move |_| {
58+ vdom0.weak().with_component(move |comp| {
59+ let comp = comp.unwrap_mut::<Cached<CountRenders>>();
60+ assert_eq!(comp.render_count.get(), 1);
61+ assert_rendered(&container1, &RenderFn(|_| Node::text("1")));
62+ })
63+ })
64+ // We re-render, re-use the cached node, and get "1" again.
65+ .and_then(move |_| vdom1.weak().render())
66+ .and_then(move |_| {
67+ vdom2.weak().with_component(move |comp| {
68+ let comp = comp.unwrap_mut::<Cached<CountRenders>>();
69+ assert_eq!(comp.render_count.get(), 1);
70+ assert_rendered(&container2, &RenderFn(|_| Node::text("1")));
71+ })
72+ })
73+ // We invalidate the cache, re-render, re-populate the cache, and should
74+ // now have "2".
75+ .and_then(move |_| {
76+ vdom3.weak().with_component(move |comp| {
77+ let comp = comp.unwrap_mut::<Cached<CountRenders>>();
78+ Cached::invalidate(comp);
79+ })
80+ })
81+ .and_then(move |_| vdom4.weak().render())
82+ .and_then(move |_| {
83+ vdom5.weak().with_component(move |comp| {
84+ let comp = comp.unwrap_mut::<Cached<CountRenders>>();
85+ assert_eq!(comp.render_count.get(), 2);
86+ assert_rendered(&container3, &RenderFn(|_| Node::text("2")));
87+ })
88+ })
89+ // We re-render, re-use the cached node, and get "2" again.
90+ .and_then(move |_| vdom6.weak().render())
91+ .and_then(move |_| {
92+ vdom7.weak().with_component(move |comp| {
93+ let comp = comp.unwrap_mut::<Cached<CountRenders>>();
48− assert_eq!(vdom.component().render_count.get(), 2);
94+ assert_eq!(comp.render_count.get(), 2);
49− assert_rendered(&container, &RenderFn(|_| Node::text("2")))
95+ assert_rendered(&container4, &RenderFn(|_| Node::text("2")));
96+ })
50− }
97+ })
98+ .map_err(|e| JsValue::from(e.to_string()))
51}
99}
tests/web/events.rsnew file+371
@@ -0,0 +1,371 @@
1+use super::create_element;
2+use bumpalo::Bump;
3+use dodrio::{on, Attribute, Node, Render, Vdom};
4+use futures::Future;
5+use std::cell::{Cell, RefCell};
6+use std::rc::Rc;
7+use wasm_bindgen::prelude::*;
8+use wasm_bindgen::JsCast;
9+use wasm_bindgen_test::*;
10+
11+struct EventContainer {
12+ event: &'static str,
13+ on_event: Box<FnMut()>,
14+}
15+
16+impl EventContainer {
17+ fn new<F>(event: &'static str, on_event: F) -> EventContainer
18+ where
19+ F: 'static + FnMut(),
20+ {
21+ EventContainer {
22+ event,
23+ on_event: Box::new(on_event),
24+ }
25+ }
26+}
27+
28+impl Render for EventContainer {
29+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
30+ where
31+ 'a: 'bump,
32+ {
33+ Node::element(
34+ bump,
35+ "div",
36+ [on(bump, self.event, |root, _scheduler, _event| {
37+ (root.unwrap_mut::<EventContainer>().on_event)();
38+ })],
39+ [Attribute {
40+ name: "id",
41+ value: "target",
42+ }],
43+ [],
44+ )
45+ }
46+}
47+
48+fn target(container: &web_sys::Element) -> web_sys::HtmlElement {
49+ container
50+ .query_selector("#target")
51+ .expect_throw("should querySelector OK")
52+ .expect_throw("should find `#target` in container")
53+ .unchecked_into()
54+}
55+
56+#[wasm_bindgen_test(async)]
57+fn click() -> impl Future<Item = (), Error = JsValue> {
58+ let container = create_element("div");
59+
60+ let (sender, receiver) = futures::sync::oneshot::channel();
61+ let mut sender = Some(sender);
62+
63+ let vdom = Vdom::new(
64+ &container,
65+ EventContainer::new("click", move || {
66+ sender
67+ .take()
68+ .expect_throw("should only call listener once")
69+ .send(())
70+ .expect_throw("should not have dropped the receiver");
71+ }),
72+ );
73+
74+ target(&container).click();
75+
76+ receiver
77+ .map(|_| {
78+ drop(vdom);
79+ })
80+ .map_err(|e| JsValue::from(e.to_string()))
81+}
82+
83+#[wasm_bindgen_test(async)]
84+fn updated_listener_is_called() -> impl Future<Item = (), Error = JsValue> {
85+ let container = create_element("div");
86+
87+ let (first_sender, first_receiver) = futures::sync::oneshot::channel();
88+ let mut first_sender = Some(first_sender);
89+
90+ let vdom = Vdom::new(
91+ &container,
92+ EventContainer::new("click", move || {
93+ first_sender
94+ .take()
95+ .expect_throw("should only call listener once")
96+ .send("first")
97+ .expect_throw("should not have dropped the receiver");
98+ }),
99+ );
100+
101+ let (second_sender, second_receiver) = futures::sync::oneshot::channel();
102+ let mut second_sender = Some(second_sender);
103+
104+ vdom.weak()
105+ .set_component(Box::new(EventContainer::new("click", move || {
106+ second_sender
107+ .take()
108+ .expect_throw("should only call listener once")
109+ .send("second")
110+ .expect_throw("should not have dropped the receiver");
111+ })))
112+ .map_err(|e| JsValue::from(e.to_string()))
113+ .and_then(move |_| {
114+ target(&container).click();
115+
116+ first_receiver
117+ .select(second_receiver)
118+ .map(|(which, _other)| which)
119+ .or_else(|(_error, other)| other)
120+ .map(|which| {
121+ assert_eq!(which, "second");
122+ drop(vdom);
123+ })
124+ .map_err(|e| JsValue::from(e.to_string()))
125+ })
126+}
127+
128+struct ListensOnlyOnFirstRender {
129+ count: Cell<usize>,
130+ callback: Box<FnMut()>,
131+}
132+
133+impl ListensOnlyOnFirstRender {
134+ fn new<F>(callback: F) -> ListensOnlyOnFirstRender
135+ where
136+ F: 'static + FnMut(),
137+ {
138+ ListensOnlyOnFirstRender {
139+ count: Cell::new(0),
140+ callback: Box::new(callback),
141+ }
142+ }
143+}
144+
145+impl Render for ListensOnlyOnFirstRender {
146+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
147+ where
148+ 'a: 'bump,
149+ {
150+ let count = self.count.get();
151+ self.count.set(count + 1);
152+ if count == 0 {
153+ Node::element(
154+ bump,
155+ "div",
156+ [on(bump, "click", |root, _scheduler, _event| {
157+ (root.unwrap_mut::<ListensOnlyOnFirstRender>().callback)();
158+ })],
159+ [Attribute {
160+ name: "id",
161+ value: "target",
162+ }],
163+ [],
164+ )
165+ } else {
166+ Node::element(
167+ bump,
168+ "div",
169+ [],
170+ [Attribute {
171+ name: "id",
172+ value: "target",
173+ }],
174+ [],
175+ )
176+ }
177+ }
178+}
179+
180+#[wasm_bindgen_test(async)]
181+fn removed_listener_is_not_called() -> impl Future<Item = (), Error = JsValue> {
182+ let container = create_element("div");
183+
184+ let (outer_sender, outer_receiver) = futures::sync::oneshot::channel();
185+ let outer_sender = Rc::new(RefCell::new(Some(outer_sender)));
186+ let outer_listener = Closure::wrap(Box::new(move |_| {
187+ outer_sender
188+ .borrow_mut()
189+ .take()
190+ .expect_throw("should only invoke outer_listener once")
191+ .send("outer")
192+ .expect_throw("should not have dropped receiver");
193+ }) as Box<FnMut(web_sys::Event)>);
194+
195+ container
196+ .add_event_listener_with_callback("click", outer_listener.as_ref().unchecked_ref())
197+ .unwrap();
198+
199+ let (vdom_sender, vdom_receiver) = futures::sync::oneshot::channel();
200+ let mut vdom_sender = Some(vdom_sender);
201+
202+ // Render a vdom with a listener into container.
203+ let vdom = Vdom::new(
204+ &container,
205+ ListensOnlyOnFirstRender::new(move || {
206+ vdom_sender
207+ .take()
208+ .expect_throw("should not invoke vdom listener more than once")
209+ .send("inner")
210+ .expect_throw("should not have dropped the receiver");
211+ }),
212+ );
213+
214+ vdom.weak()
215+ // Re-render, so we aren't listening anymore.
216+ .render()
217+ .map_err(|e| JsValue::from(e.to_string()))
218+ .and_then(move |_| {
219+ target(&container).click();
220+
221+ // We should get our container's event handler fired, and not the unmounted
222+ // vdom's event handler.
223+ outer_receiver
224+ .select(vdom_receiver)
225+ .map(|(which, _other)| which)
226+ .or_else(|(_error, other)| other)
227+ .map(|which| {
228+ assert_eq!(which, "outer");
229+ drop(outer_listener);
230+ drop(vdom)
231+ })
232+ .map_err(|e| JsValue::from(e.to_string()))
233+ })
234+}
235+
236+#[wasm_bindgen_test(async)]
237+fn event_does_not_fire_after_unmounting() -> impl Future<Item = (), Error = JsValue> {
238+ let container = create_element("div");
239+
240+ let (outer_sender, outer_receiver) = futures::sync::oneshot::channel();
241+ let outer_sender = Rc::new(RefCell::new(Some(outer_sender)));
242+ let outer_listener = Closure::wrap(Box::new(move |_| {
243+ outer_sender
244+ .borrow_mut()
245+ .take()
246+ .expect_throw("should only invoke outer_listener once")
247+ .send("outer")
248+ .expect_throw("should not have dropped receiver");
249+ }) as Box<FnMut(web_sys::Event)>);
250+
251+ container
252+ .add_event_listener_with_callback("click", outer_listener.as_ref().unchecked_ref())
253+ .unwrap();
254+
255+ let (vdom_sender, vdom_receiver) = futures::sync::oneshot::channel();
256+ let mut vdom_sender = Some(vdom_sender);
257+
258+ // Render a vdom with a listener into container.
259+ let vdom = Vdom::new(
260+ &container,
261+ EventContainer::new("click", move || {
262+ vdom_sender
263+ .take()
264+ .expect_throw("should not invoke vdom listener more than once")
265+ .send("vdom")
266+ .expect_throw("should not have dropped the receiver");
267+ }),
268+ );
269+
270+ // Grab a reference to the target before dropping clears the DOM.
271+ let target = target(&container);
272+
273+ // Unmount the vdom.
274+ drop(vdom);
275+
276+ // The target is no longer a child of the container, so re-append it so that
277+ // the click will bubble.
278+ container.append_child(&target).unwrap();
279+
280+ // Send a click to the target.
281+ target.click();
282+
283+ // We should get our container's event handler fired, and not the unmounted
284+ // vdom's event handler.
285+ outer_receiver
286+ .select(vdom_receiver)
287+ .map(|(which, _other)| which)
288+ .or_else(|(_error, other)| other)
289+ .map(|which| {
290+ assert_eq!(which, "outer");
291+ drop(outer_listener);
292+ })
293+ .map_err(|e| JsValue::from(e.to_string()))
294+}
295+
296+struct CountRendersAndListen {
297+ count: Cell<usize>,
298+ callback: Box<FnMut(usize)>,
299+}
300+
301+impl CountRendersAndListen {
302+ fn new<F>(callback: F) -> CountRendersAndListen
303+ where
304+ F: 'static + FnMut(usize),
305+ {
306+ CountRendersAndListen {
307+ count: Cell::new(0),
308+ callback: Box::new(callback),
309+ }
310+ }
311+}
312+
313+impl Render for CountRendersAndListen {
314+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
315+ where
316+ 'a: 'bump,
317+ {
318+ let count = self.count.get() + 1;
319+ self.count.set(count);
320+ Node::element(
321+ bump,
322+ "button",
323+ [on(bump, "click", |comp, _vdom, _event| {
324+ let comp: &mut _ = comp.unwrap_mut::<dodrio::Cached<CountRendersAndListen>>();
325+ let comp: &mut CountRendersAndListen = comp;
326+ let n = comp.count.get();
327+ (comp.callback)(n);
328+ })],
329+ [Attribute {
330+ name: "id",
331+ value: "target",
332+ }],
333+ [],
334+ )
335+ }
336+}
337+
338+#[wasm_bindgen_test(async)]
339+fn cached_listeners_work_correctly() -> impl Future<Item = (), Error = JsValue> {
340+ let container = create_element("div");
341+
342+ let (sender, receiver) = futures::sync::oneshot::channel();
343+ let mut sender = Some(sender);
344+ let vdom = Vdom::new(
345+ &container,
346+ dodrio::Cached::new(CountRendersAndListen::new(move |n| {
347+ sender.take().unwrap().send(n).unwrap();
348+ })),
349+ );
350+
351+ let weak0 = vdom.weak();
352+ let weak1 = vdom.weak();
353+ let weak2 = vdom.weak();
354+ vdom.weak()
355+ // Re-render a few times, so we are using the cached version.
356+ .render()
357+ .and_then(move |_| weak0.render())
358+ .and_then(move |_| weak1.render())
359+ .and_then(move |_| weak2.render())
360+ .map_err(|e| JsValue::from(e.to_string()))
361+ .and_then(move |_| {
362+ target(&container).click();
363+
364+ receiver
365+ .map(|n| {
366+ assert_eq!(n, 1);
367+ drop(vdom);
368+ })
369+ .map_err(|e| JsValue::from(e.to_string()))
370+ })
371+}
tests/web/main.rs+25−10
@@ -4,13 +4,17 @@
4
4
5use bumpalo::Bump;
5use bumpalo::Bump;
6use dodrio::{Attribute, Node, Render, Vdom};
6use dodrio::{Attribute, Node, Render, Vdom};
7+use futures::prelude::*;
7use log::*;
8use log::*;
9+use std::rc::Rc;
10+use wasm_bindgen::prelude::*;
8use wasm_bindgen::JsCast;
11use wasm_bindgen::JsCast;
9use wasm_bindgen_test::*;
12use wasm_bindgen_test::*;
10
13
11wasm_bindgen_test_configure!(run_in_browser);
14wasm_bindgen_test_configure!(run_in_browser);
12
15
13pub mod cached;
16pub mod cached;
17+pub mod events;
14pub mod render;
18pub mod render;
15
19
16pub fn window() -> web_sys::Window {
20pub fn window() -> web_sys::Window {
@@ -24,6 +28,7 @@ pub fn document() -> web_sys::Document {
24}
28}
25
29
26pub fn create_element(tag: &str) -> web_sys::Element {
30pub fn create_element(tag: &str) -> web_sys::Element {
31+ init_logging();
27 document()
32 document()
28 .create_element(tag)
33 .create_element(tag)
29 .expect("should create element OK")
34 .expect("should create element OK")
@@ -142,22 +147,32 @@ where
142/// Assert that if we start by rendering the `before` virtual DOM tree into a
147/// Assert that if we start by rendering the `before` virtual DOM tree into a
143/// physical DOM tree, and then diff it with the `after` virtual DOM tree, then
148/// physical DOM tree, and then diff it with the `after` virtual DOM tree, then
144/// the physical DOM tree correctly matches `after`.
149/// the physical DOM tree correctly matches `after`.
145−pub fn assert_before_after<R, S>(before: R, after: S)
150+pub fn assert_before_after<R, S>(before: R, after: S) -> impl Future<Item = (), Error = JsValue>
146where
151where
147− R: Render,
152+ R: 'static + Render,
148− S: Render,
153+ S: 'static + Render,
149{
154{
150 let container = create_element("div");
155 let container = create_element("div");
151
156
157+ let before = Rc::new(before);
158+ let after = Rc::new(after);
159+
152 debug!("====== Rendering the *before* DOM into the physical DOM ======");
160 debug!("====== Rendering the *before* DOM into the physical DOM ======");
153− let vdom = Vdom::new(&container, &before);
161+ let vdom1 = Rc::new(Vdom::new(&container, before.clone()));
162+ let vdom2 = vdom1.clone();
163+
154 debug!("====== Checking the *before* DOM against the physical DOM ======");
164 debug!("====== Checking the *before* DOM against the physical DOM ======");
155 assert_rendered(&container, &before);
165 assert_rendered(&container, &before);
156
166
157 debug!("====== Rendering the *after* DOM into the physical DOM ======");
167 debug!("====== Rendering the *after* DOM into the physical DOM ======");
158− vdom.render_component(&after);
168+ let weak = vdom1.weak();
169+ weak.set_component(Box::new(after.clone()))
170+ .map(move |_| {
159− debug!("====== Checking the *after* DOM against the physical DOM ======");
171+ debug!("====== Checking the *after* DOM against the physical DOM ======");
160− assert_rendered(&container, &after);
172+ assert_rendered(&container, &after);
173+ drop(vdom2);
174+ })
175+ .map_err(|e| JsValue::from(e.to_string()))
161}
176}
162
177
163/// A helper macro for decalaring a bunch of `assert_before_after` tests.
178/// A helper macro for decalaring a bunch of `assert_before_after` tests.
@@ -174,13 +189,13 @@ macro_rules! before_after {
174 }
189 }
175 )* ) => {
190 )* ) => {
176 $(
191 $(
177− #[wasm_bindgen_test]
192+ #[wasm_bindgen_test(async)]
178− fn $name() {
193+ fn $name() -> impl Future<Item = (), Error = wasm_bindgen::JsValue> {
179 use crate::{assert_before_after, RenderFn};
194 use crate::{assert_before_after, RenderFn};
180 assert_before_after(
195 assert_before_after(
181 RenderFn(|$before_bump| { $( $before )* }),
196 RenderFn(|$before_bump| { $( $before )* }),
182 RenderFn(|$after_bump| { $( $after )* })
197 RenderFn(|$after_bump| { $( $after )* })
183− );
198+ )
184 }
199 }
185 )*
200 )*
186 }
201 }
tests/web/render.rs+59−48
@@ -1,38 +1,49 @@
1use super::{assert_rendered, before_after, create_element, RenderFn};
1use super::{assert_rendered, before_after, create_element, RenderFn};
2use dodrio::{Attribute, Node, Vdom};
2use dodrio::{Attribute, Node, Vdom};
3+use futures::prelude::*;
4+use std::rc::Rc;
3use wasm_bindgen_test::*;
5use wasm_bindgen_test::*;
4
6
5#[wasm_bindgen_test]
7#[wasm_bindgen_test]
6fn render_initial_text() {
8fn render_initial_text() {
7− let hello = RenderFn(|_bump| Node::text("hello"));
9+ let hello = Rc::new(RenderFn(|_bump| Node::text("hello")));
8
10
9 let container = create_element("div");
11 let container = create_element("div");
10− Vdom::new(&container, &hello);
12+ let _vdom = Vdom::new(&container, hello.clone());
11 assert_rendered(&container, &hello);
13 assert_rendered(&container, &hello);
12}
14}
13
15
14#[wasm_bindgen_test]
16#[wasm_bindgen_test]
15fn render_initial_node() {
17fn render_initial_node() {
16− let hello = RenderFn(|bump| {
18+ let hello = Rc::new(RenderFn(|bump| {
17 Node::element(
19 Node::element(
18 bump,
20 bump,
19 "div",
21 "div",
22+ [],
20 [Attribute {
23 [Attribute {
21 name: "id",
24 name: "id",
22 value: "hello-world",
25 value: "hello-world",
23 }],
26 }],
24 [
27 [
25 Node::text("Hello "),
28 Node::text("Hello "),
26− Node::element(bump, "span", [], [Node::text("World!")]),
29+ Node::element(bump, "span", [], [], [Node::text("World!")]),
27 ],
30 ],
28 )
31 )
29− });
32+ }));
30
33
31 let container = create_element("div");
34 let container = create_element("div");
32− Vdom::new(&container, &hello);
35+ let _vdom = Vdom::new(&container, hello.clone());
33 assert_rendered(&container, &hello);
36 assert_rendered(&container, &hello);
34}
37}
35
38
39+#[wasm_bindgen_test]
40+fn container_is_emptied_upon_drop() {
41+ let container = create_element("div");
42+ let vdom = Vdom::new(&container, RenderFn(|_bump| Node::text("blah")));
43+ drop(vdom);
44+ assert!(container.first_child().is_none());
45+}
46+
36before_after! {
47before_after! {
37 same_text {
48 same_text {
38 before(_bump) {
49 before(_bump) {
@@ -57,13 +68,13 @@ before_after! {
57 Node::text("before")
68 Node::text("before")
58 }
69 }
59 after(bump) {
70 after(bump) {
60− Node::element(bump, "div", [], [])
71+ Node::element(bump, "div", [], [], [])
61 }
72 }
62 }
73 }
63
74
64 replace_elem_with_text {
75 replace_elem_with_text {
65 before(bump) {
76 before(bump) {
66− Node::element(bump, "div", [], [])
77+ Node::element(bump, "div", [], [], [])
67 }
78 }
68 after(_bump) {
79 after(_bump) {
69 Node::text("before")
80 Node::text("before")
@@ -72,52 +83,52 @@ before_after! {
72
83
73 same_elem {
84 same_elem {
74 before(bump) {
85 before(bump) {
75− Node::element(bump, "div", [], [])
86+ Node::element(bump, "div", [], [], [])
76 }
87 }
77 after(bump) {
88 after(bump) {
78− Node::element(bump, "div", [], [])
89+ Node::element(bump, "div", [], [], [])
79 }
90 }
80 }
91 }
81
92
82 elems_with_different_tag_names {
93 elems_with_different_tag_names {
83 before(bump) {
94 before(bump) {
84− Node::element(bump, "span", [], [])
95+ Node::element(bump, "span", [], [], [])
85 }
96 }
86 after(bump) {
97 after(bump) {
87− Node::element(bump, "div", [], [])
98+ Node::element(bump, "div", [], [], [])
88 }
99 }
89 }
100 }
90
101
91 same_tag_name_update_attribute {
102 same_tag_name_update_attribute {
92 before(bump) {
103 before(bump) {
93− Node::element(bump, "div", [Attribute { name: "value", value: "1" }], [])
104+ Node::element(bump, "div", [], [Attribute { name: "value", value: "1" }], [])
94 }
105 }
95 after(bump) {
106 after(bump) {
96− Node::element(bump, "div", [Attribute { name: "value", value: "2" }], [])
107+ Node::element(bump, "div", [], [Attribute { name: "value", value: "2" }], [])
97 }
108 }
98 }
109 }
99
110
100 same_tag_name_remove_attribute {
111 same_tag_name_remove_attribute {
101 before(bump) {
112 before(bump) {
102− Node::element(bump, "div", [Attribute { name: "value", value: "1" }], [])
113+ Node::element(bump, "div", [], [Attribute { name: "value", value: "1" }], [])
103 }
114 }
104 after(bump) {
115 after(bump) {
105− Node::element(bump, "div", [], [])
116+ Node::element(bump, "div", [], [], [])
106 }
117 }
107 }
118 }
108
119
109 same_tag_name_add_attribute {
120 same_tag_name_add_attribute {
110 before(bump) {
121 before(bump) {
111− Node::element(bump, "div", [], [])
122+ Node::element(bump, "div", [], [], [])
112 }
123 }
113 after(bump) {
124 after(bump) {
114− Node::element(bump, "div", [Attribute { name: "value", value: "2" }], [])
125+ Node::element(bump, "div", [], [Attribute { name: "value", value: "2" }], [])
115 }
126 }
116 }
127 }
117
128
118 same_tag_name_many_attributes {
129 same_tag_name_many_attributes {
119 before(bump) {
130 before(bump) {
120− Node::element(bump, "div", [
131+ Node::element(bump, "div", [], [
121 Attribute { name: "before-1", value: "1" },
132 Attribute { name: "before-1", value: "1" },
122 Attribute { name: "shared-1", value: "1" },
133 Attribute { name: "shared-1", value: "1" },
123 Attribute { name: "modified-1", value: "1" },
134 Attribute { name: "modified-1", value: "1" },
@@ -130,7 +141,7 @@ before_after! {
130 ], [])
141 ], [])
131 }
142 }
132 after(bump) {
143 after(bump) {
133− Node::element(bump, "div", [
144+ Node::element(bump, "div", [], [
134 Attribute { name: "after-1", value: "1" },
145 Attribute { name: "after-1", value: "1" },
135 Attribute { name: "shared-1", value: "1" },
146 Attribute { name: "shared-1", value: "1" },
136 Attribute { name: "modified-1", value: "100" },
147 Attribute { name: "modified-1", value: "100" },
@@ -146,12 +157,12 @@ before_after! {
146
157
147 same_tag_same_children {
158 same_tag_same_children {
148 before(bump) {
159 before(bump) {
149− Node::element(bump, "div", [], [
160+ Node::element(bump, "div", [], [], [
150 Node::text("child")
161 Node::text("child")
151 ])
162 ])
152 }
163 }
153 after(bump) {
164 after(bump) {
154− Node::element(bump, "div", [], [
165+ Node::element(bump, "div", [], [], [
155 Node::text("child")
166 Node::text("child")
156 ])
167 ])
157 }
168 }
@@ -159,12 +170,12 @@ before_after! {
159
170
160 same_tag_update_child {
171 same_tag_update_child {
161 before(bump) {
172 before(bump) {
162− Node::element(bump, "div", [], [
173+ Node::element(bump, "div", [], [], [
163 Node::text("before")
174 Node::text("before")
164 ])
175 ])
165 }
176 }
166 after(bump) {
177 after(bump) {
167− Node::element(bump, "div", [], [
178+ Node::element(bump, "div", [], [], [
168 Node::text("after")
179 Node::text("after")
169 ])
180 ])
170 }
181 }
@@ -172,10 +183,10 @@ before_after! {
172
183
173 same_tag_add_child {
184 same_tag_add_child {
174 before(bump) {
185 before(bump) {
175− Node::element(bump, "div", [], [])
186+ Node::element(bump, "div", [], [], [])
176 }
187 }
177 after(bump) {
188 after(bump) {
178− Node::element(bump, "div", [], [
189+ Node::element(bump, "div", [], [], [
179 Node::text("child")
190 Node::text("child")
180 ])
191 ])
181 }
192 }
@@ -183,58 +194,58 @@ before_after! {
183
194
184 same_tag_remove_child {
195 same_tag_remove_child {
185 before(bump) {
196 before(bump) {
186− Node::element(bump, "div", [], [
197+ Node::element(bump, "div", [], [], [
187 Node::text("child")
198 Node::text("child")
188 ])
199 ])
189 }
200 }
190 after(bump) {
201 after(bump) {
191− Node::element(bump, "div", [], [])
202+ Node::element(bump, "div", [], [], [])
192 }
203 }
193 }
204 }
194
205
195 same_tag_update_many_children {
206 same_tag_update_many_children {
196 before(bump) {
207 before(bump) {
197− Node::element(bump, "div", [], [
208+ Node::element(bump, "div", [], [], [
198− Node::element(bump, "div", [], []),
209+ Node::element(bump, "div", [], [], []),
199− Node::element(bump, "span", [], []),
210+ Node::element(bump, "span", [], [], []),
200− Node::element(bump, "p", [], []),
211+ Node::element(bump, "p", [], [], []),
201 ])
212 ])
202 }
213 }
203 after(bump) {
214 after(bump) {
204− Node::element(bump, "div", [], [
215+ Node::element(bump, "div", [], [], [
205− Node::element(bump, "span", [], []),
216+ Node::element(bump, "span", [], [], []),
206− Node::element(bump, "p", [], []),
217+ Node::element(bump, "p", [], [], []),
207− Node::element(bump, "div", [], []),
218+ Node::element(bump, "div", [], [], []),
208 ])
219 ])
209 }
220 }
210 }
221 }
211
222
212 same_tag_remove_many_children {
223 same_tag_remove_many_children {
213 before(bump) {
224 before(bump) {
214− Node::element(bump, "div", [], [
225+ Node::element(bump, "div", [], [], [
215− Node::element(bump, "div", [], []),
226+ Node::element(bump, "div", [], [], []),
216− Node::element(bump, "span", [], []),
227+ Node::element(bump, "span", [], [], []),
217− Node::element(bump, "p", [], []),
228+ Node::element(bump, "p", [], [], []),
218 ])
229 ])
219 }
230 }
220 after(bump) {
231 after(bump) {
221− Node::element(bump, "div", [], [
232+ Node::element(bump, "div", [], [], [
222− Node::element(bump, "div", [], []),
233+ Node::element(bump, "div", [], [], []),
223 ])
234 ])
224 }
235 }
225 }
236 }
226
237
227 same_tag_add_many_children {
238 same_tag_add_many_children {
228 before(bump) {
239 before(bump) {
229− Node::element(bump, "div", [], [
240+ Node::element(bump, "div", [], [], [
230− Node::element(bump, "div", [], []),
241+ Node::element(bump, "div", [], [], []),
231 ])
242 ])
232 }
243 }
233 after(bump) {
244 after(bump) {
234− Node::element(bump, "div", [], [
245+ Node::element(bump, "div", [], [], [
235− Node::element(bump, "div", [], []),
246+ Node::element(bump, "div", [], [], []),
236− Node::element(bump, "span", [], []),
247+ Node::element(bump, "span", [], [], []),
237− Node::element(bump, "p", [], []),
248+ Node::element(bump, "p", [], [], []),
238 ])
249 ])
239 }
250 }
240 }
251 }