on master help sign in
Add an "strace"-style DOM tracing feature
2c6e730Nick Fitzgerald
Cargo.toml+8
@@ -29,6 +29,14 @@ harness = false
29required-features = ["xxx-unstable-internal-use-only"]
29required-features = ["xxx-unstable-internal-use-only"]
30
30
31[features]
31[features]
32+# Don't rely on this feature! Its behavior will change and it could go away at
33+# any time. This enables monkey-patching DOM methods so that we wrap them and
34+# trace what DOM methods are used and how many times they are invoked when
35+# applying our emitted change lists to the physical DOM. This is useful for
36+# evaluating the differences between how Dodrio applies changes from virtual
37+# nodes to the physical DOM versus how other virtual DOM libraries do the same.
38+xxx-unstable-strace = []
39+
32# Don't use this feature! It only exists for exposing otherwise-internal APIs
40# Don't use this feature! It only exists for exposing otherwise-internal APIs
33# for testing and benchmarks.
41# for testing and benchmarks.
34xxx-unstable-internal-use-only = []
42xxx-unstable-internal-use-only = []
js/strace.jsnew file+71
@@ -0,0 +1,71 @@
1+export function initStrace() {
2+ instrumentAll(Element.prototype);
3+ instrumentAll(Document.prototype);
4+ instrumentAll(Node.prototype);
5+ instrumentAll(EventTarget.prototype);
6+}
7+
8+const counts = new Map;
9+let timer = null;
10+
11+function log(ctorName, kind, key) {
12+ const entry = `${kind} ${ctorName}#${key}`
13+ const count = counts.get(entry);
14+ if (count === undefined) {
15+ counts.set(entry, 1);
16+ } else {
17+ counts.set(entry, count + 1);
18+ }
19+
20+ if (timer !== null) {
21+ clearTimeout(timer);
22+ }
23+ timer = setTimeout(dumpAndReset, 100);
24+}
25+
26+function dumpAndReset() {
27+ const data = [...counts]
28+ .sort((a, b) => b[1] - a[1])
29+ .map(a => ({ "DOM Method": a[0], "Count": a[1] }));
30+ console.table(data, ["DOM Method", "Count"]);
31+ counts.clear();
32+}
33+
34+function instrument(proto, key, desc) {
35+ const ctorName = proto.constructor.name;
36+
37+ if (typeof desc.value == "function") {
38+ const f = desc.value;
39+ desc.value = function (...args) {
40+ log(ctorName, "call", key);
41+ return f.apply(this, args);
42+ };
43+ }
44+
45+ if (typeof desc.get == "function") {
46+ const get = desc.get;
47+ desc.get = function (...args) {
48+ log(ctorName, "get", key);
49+ return get.apply(this, args);
50+ };
51+ }
52+
53+ if (typeof desc.set == "function") {
54+ const set = desc.set;
55+ desc.set = function (...args) {
56+ log(ctorName, "set", key);
57+ return set.apply(this, args);
58+ };
59+ }
60+
61+ Object.defineProperty(proto, key, desc);
62+}
63+
64+function instrumentAll(proto) {
65+ for (const key of Object.keys(proto)) {
66+ const desc = Object.getOwnPropertyDescriptor(proto, key);
67+ if (desc.configurable) {
68+ instrument(proto, key, desc);
69+ }
70+ }
71+}
src/lib.rs+1
@@ -92,6 +92,7 @@ mod events;
92mod node;
92mod node;
93mod render;
93mod render;
94mod render_context;
94mod render_context;
95+mod strace;
95mod vdom;
96mod vdom;
96
97
97pub mod builder;
98pub mod builder;
src/strace.rsnew file+22
@@ -0,0 +1,22 @@
1+cfg_if::cfg_if! {
2+ if #[cfg(feature = "xxx-unstable-strace")] {
3+ use wasm_bindgen::prelude::*;
4+
5+ #[wasm_bindgen(module = "/js/strace.js")]
6+ extern "C" {
7+ #[wasm_bindgen(js_name = initStrace)]
8+ fn really_init_strace();
9+ }
10+
11+ pub fn init_strace() {
12+ use std::sync::Once;
13+ static STRACE: Once = Once::new();
14+ STRACE.call_once(|| {
15+ really_init_strace();
16+ });
17+ }
18+ } else {
19+ #[inline]
20+ pub fn init_strace() {}
21+ }
22+}
src/vdom.rs+2
@@ -161,6 +161,8 @@ impl Vdom {
161 /// Construct a `Vdom` with the already-boxed-as-a-trait-object root
161 /// Construct a `Vdom` with the already-boxed-as-a-trait-object root
162 /// rendering component.
162 /// rendering component.
163 pub fn with_boxed_root_render(container: &crate::Element, component: Box<RootRender>) -> Vdom {
163 pub fn with_boxed_root_render(container: &crate::Element, component: Box<RootRender>) -> Vdom {
164+ crate::strace::init_strace();
165+
164 let dom_buffers = [Bump::new(), Bump::new()];
166 let dom_buffers = [Bump::new(), Bump::new()];
165 let change_list = ManuallyDrop::new(ChangeListPersistentState::new(container));
167 let change_list = ManuallyDrop::new(ChangeListPersistentState::new(container));
166
168