Parcourir la source

Upgrade to Blitz 0.1.0-alpha.2 (#4227)

* Update to use latest Blitz net traits

* Pass url to flush_style_attribute

* Add system-fonts feature to dioxus-native

* Native: cleanup log output

* Make dioxus-native work with Blitz simplified Document and Renderer traits

* Native: upgrade to event refactor

* Upgrade to blitz 0.1.0-alpha.2

* Add typo exception

Signed-off-by: Nico Burns <nico@nicoburns.com>

* Ensure root_id is set to <main> element

---------

Signed-off-by: Nico Burns <nico@nicoburns.com>
Nico Burns il y a 3 semaines
Parent
commit
51d7affc54

+ 182 - 173
Cargo.lock

@@ -379,6 +379,47 @@ version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c"
 
+[[package]]
+name = "anyrender"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fff8da5f4a3e508ed376118a7e04ed992df07630fb47d7b3dce8d1f92427975d"
+dependencies = [
+ "kurbo",
+ "peniko",
+ "raw-window-handle 0.6.2",
+]
+
+[[package]]
+name = "anyrender_svg"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4197139c97b2d204e44fd79c58e16f1c6023d18fe3db1e3eebfa335f78e39072"
+dependencies = [
+ "anyrender",
+ "image",
+ "kurbo",
+ "peniko",
+ "thiserror 2.0.12",
+ "usvg",
+]
+
+[[package]]
+name = "anyrender_vello"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83e99c7f0936960037aa11d64bf7c9c5cf2f624dd9faf8c57a6581b056e08852"
+dependencies = [
+ "anyrender",
+ "futures-intrusive",
+ "kurbo",
+ "peniko",
+ "pollster",
+ "raw-window-handle 0.6.2",
+ "vello",
+ "wgpu 24.0.5",
+]
+
 [[package]]
 name = "app_units"
 version = "0.7.8"
@@ -1563,24 +1604,26 @@ dependencies = [
 
 [[package]]
 name = "blitz-dom"
-version = "0.1.0-alpha.1"
+version = "0.1.0-alpha.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eae8889dde80e2aaceec27455c63446ca841cb7217a4f5489691295da922792b"
+checksum = "7dc174ecca5c3e94b162335be4c9e17b35a0b421898ea559cb7f5926ff0fe5c6"
 dependencies = [
  "accesskit",
  "app_units",
  "atomic_refcell",
  "blitz-traits",
- "color 0.2.4",
+ "color",
  "cursor-icon",
  "euclid",
  "html-escape",
  "image",
  "keyboard-types",
- "markup5ever 0.15.0",
+ "markup5ever 0.16.1",
+ "objc2 0.6.1",
  "parley",
- "peniko 0.3.2",
- "selectors 0.27.0",
+ "peniko",
+ "percent-encoding",
+ "selectors 0.28.0",
  "slab",
  "smallvec",
  "string_cache",
@@ -1597,49 +1640,49 @@ dependencies = [
 
 [[package]]
 name = "blitz-net"
-version = "0.1.0-alpha.1"
+version = "0.1.0-alpha.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efc80023b8f6758d7f61bda68440917b72cbe2fe6187cbe1b7837e73b2522dfe"
+checksum = "96b2788c54be9856d8a13a8bfcf0f184573d7169fdb6bf3f867cef87eca48874"
 dependencies = [
  "blitz-traits",
  "data-url 0.3.1",
  "reqwest 0.12.15",
- "thiserror 1.0.69",
  "tokio",
 ]
 
 [[package]]
-name = "blitz-renderer-vello"
-version = "0.1.0-alpha.1"
+name = "blitz-paint"
+version = "0.1.0-alpha.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44fdafaf87eb502da99667fad2ae9757d01c8bd83772f6b7b6a78c48c3ab6ff5"
+checksum = "3dd45f19bf9db46e67282bd24e764904c9624458fc4f71ec6566cc916e43ef36"
 dependencies = [
+ "anyrender",
+ "anyrender_svg",
  "blitz-dom",
  "blitz-traits",
- "color 0.2.4",
+ "color",
  "euclid",
- "futures-intrusive",
- "image",
+ "kurbo",
  "parley",
- "pollster",
- "raw-window-handle 0.6.2",
+ "peniko",
  "stylo",
  "taffy",
- "vello",
- "vello_svg",
- "wgpu 23.0.1",
+ "tracing",
+ "usvg",
 ]
 
 [[package]]
 name = "blitz-shell"
-version = "0.1.0-alpha.1"
+version = "0.1.0-alpha.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef0a6122f3619a746dddd21883edaae5f791d6e9f401dbdf7f7756caa4f69453"
+checksum = "ac5e9acd11cd37fee6819686eb4893ae175e2c2563865fb823a9811418a90541"
 dependencies = [
  "accesskit",
  "accesskit_winit",
  "android-activity",
+ "anyrender",
  "blitz-dom",
+ "blitz-paint",
  "blitz-traits",
  "futures-util",
  "keyboard-types",
@@ -1650,15 +1693,15 @@ dependencies = [
 
 [[package]]
 name = "blitz-traits"
-version = "0.1.0-alpha.1"
+version = "0.1.0-alpha.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9972442314e8bf9dd65af7aee0634a2d575972587d404520c7c6986abbd523d3"
+checksum = "1d0ce7b7e5e2f49321dc4cec28ea7e83b7af0a287eb705674e36a9ac8f9691b9"
 dependencies = [
  "bitflags 2.9.1",
  "bytes",
+ "cursor-icon",
  "http 1.3.1",
  "keyboard-types",
- "raw-window-handle 0.6.2",
  "smol_str",
  "url",
 ]
@@ -2457,12 +2500,6 @@ dependencies = [
  "unicode-width 0.2.0",
 ]
 
-[[package]]
-name = "color"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa7f99105610438d4b3ee7ae8e453c2990e325c806a14d71e8ea937d584c5289"
-
 [[package]]
 name = "color"
 version = "0.3.1"
@@ -4300,9 +4337,11 @@ dependencies = [
 name = "dioxus-native"
 version = "0.7.0-alpha.0"
 dependencies = [
+ "anyrender",
+ "anyrender_vello",
  "blitz-dom",
  "blitz-net",
- "blitz-renderer-vello",
+ "blitz-paint",
  "blitz-shell",
  "blitz-traits",
  "dioxus-asset-resolver",
@@ -5343,15 +5382,6 @@ version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
 
-[[package]]
-name = "font-types"
-version = "0.8.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fa6a5e5a77b5f3f7f9e32879f484aa5b3632ddfbe568a16266c904a6f32cdaf"
-dependencies = [
- "bytemuck",
-]
-
 [[package]]
 name = "font-types"
 version = "0.9.0"
@@ -5396,20 +5426,21 @@ dependencies = [
 
 [[package]]
 name = "fontique"
-version = "0.3.0"
+version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b5fcb214137f01bc842c4fd633236255c51f8a24c6d3923eb8361c6d0940737"
+checksum = "64763d1f274c8383333851435b6cdf071c31cfcdb39fd5860d20943205a007a7"
 dependencies = [
  "bytemuck",
  "fontconfig-cache-parser",
  "hashbrown 0.15.3",
  "icu_locid",
  "memmap2",
+ "objc2 0.6.1",
  "objc2-core-foundation",
  "objc2-core-text",
  "objc2-foundation 0.3.1",
- "peniko 0.3.2",
- "read-fonts 0.25.3",
+ "peniko",
+ "read-fonts",
  "roxmltree",
  "smallvec",
  "windows 0.58.0",
@@ -6469,9 +6500,9 @@ dependencies = [
 
 [[package]]
 name = "glow"
-version = "0.14.2"
+version = "0.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483"
+checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08"
 dependencies = [
  "js-sys",
  "slotmap",
@@ -8676,16 +8707,13 @@ dependencies = [
 
 [[package]]
 name = "markup5ever"
-version = "0.15.0"
+version = "0.16.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03a7b81dfb91586d0677086d40a6d755070e0799b71bb897485bac408dfd5c69"
+checksum = "d0a8096766c229e8c88a3900c9b44b7e06aa7f7343cc229158c3e58ef8f9973a"
 dependencies = [
  "log",
- "phf 0.11.3",
- "phf_codegen 0.11.3",
- "string_cache",
- "string_cache_codegen",
  "tendril",
+ "web_atoms",
 ]
 
 [[package]]
@@ -8824,9 +8852,9 @@ dependencies = [
 
 [[package]]
 name = "metal"
-version = "0.29.0"
+version = "0.31.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21"
+checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e"
 dependencies = [
  "bitflags 2.9.1",
  "block",
@@ -9014,22 +9042,23 @@ dependencies = [
 
 [[package]]
 name = "naga"
-version = "23.1.0"
+version = "24.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f"
+checksum = "e380993072e52eef724eddfcde0ed013b0c023c3f0417336ed041aa9f076994e"
 dependencies = [
  "arrayvec",
  "bit-set 0.8.0",
  "bitflags 2.9.1",
- "cfg_aliases 0.1.1",
+ "cfg_aliases 0.2.1",
  "codespan-reporting 0.11.1",
  "hexf-parse",
  "indexmap 2.9.0",
  "log",
  "rustc-hash 1.1.0",
  "spirv",
+ "strum 0.26.3",
  "termcolor",
- "thiserror 1.0.69",
+ "thiserror 2.0.12",
  "unicode-xid",
 ]
 
@@ -9900,6 +9929,15 @@ dependencies = [
  "num-traits",
 ]
 
+[[package]]
+name = "ordered-float"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951"
+dependencies = [
+ "num-traits",
+]
+
 [[package]]
 name = "ordered-stream"
 version = "0.2.0"
@@ -10123,14 +10161,14 @@ dependencies = [
 
 [[package]]
 name = "parley"
-version = "0.3.0"
+version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d1c2b33b240c246f06cfceac48dc6c96040cb177d2aa5348899982b298b5577"
+checksum = "e28dadbe655332fd7d996794ec8d0c376695f6ca47bc75aa01e0967c7f28e42a"
 dependencies = [
  "fontique",
  "hashbrown 0.15.3",
- "peniko 0.3.2",
- "skrifa 0.26.6",
+ "peniko",
+ "skrifa",
  "swash",
 ]
 
@@ -10234,25 +10272,13 @@ dependencies = [
  "base64ct",
 ]
 
-[[package]]
-name = "peniko"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c1f594c54ccdc9bd177a726885f066bf28d20e17169e31a8a1456217b1316b4"
-dependencies = [
- "color 0.2.4",
- "kurbo",
- "peniko 0.4.0",
- "smallvec",
-]
-
 [[package]]
 name = "peniko"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1f9529efd019889b2a205193c14ffb6e2839b54ed9d2720674f10f4b04d87ac9"
 dependencies = [
- "color 0.3.1",
+ "color",
  "kurbo",
  "smallvec",
 ]
@@ -11379,16 +11405,6 @@ dependencies = [
  "cipher",
 ]
 
-[[package]]
-name = "read-fonts"
-version = "0.25.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6f9e8a4f503e5c8750e4cd3b32a4e090035c46374b305a15c70bad833dca05f"
-dependencies = [
- "bytemuck",
- "font-types 0.8.4",
-]
-
 [[package]]
 name = "read-fonts"
 version = "0.29.1"
@@ -11396,7 +11412,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4e85935612710191461ec9df47b4b5880dd6359d4fad3b2f2ed696215f6f3146"
 dependencies = [
  "bytemuck",
- "font-types 0.9.0",
+ "font-types",
 ]
 
 [[package]]
@@ -12238,9 +12254,9 @@ dependencies = [
 
 [[package]]
 name = "selectors"
-version = "0.27.0"
+version = "0.28.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b75e048a93e14929e68e37b82e207db957cbb368375a80ed3ca28ac75080856"
+checksum = "db3079aef7a4383aff1e60eca2818995d3de8168e85ae4b6ea8fb2804b182c54"
 dependencies = [
  "bitflags 2.9.1",
  "cssparser 0.35.0",
@@ -12342,7 +12358,7 @@ version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
 dependencies = [
- "ordered-float",
+ "ordered-float 2.10.1",
  "serde",
 ]
 
@@ -12743,16 +12759,6 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
 
-[[package]]
-name = "skrifa"
-version = "0.26.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8cc1aa86c26dbb1b63875a7180aa0819709b33348eb5b1491e4321fae388179d"
-dependencies = [
- "bytemuck",
- "read-fonts 0.25.3",
-]
-
 [[package]]
 name = "skrifa"
 version = "0.31.1"
@@ -12760,7 +12766,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b9c3bb8cab5196b98d70c866ce1ea81ab516104d5b396f84ae80f8766b5d5b4e"
 dependencies = [
  "bytemuck",
- "read-fonts 0.29.1",
+ "read-fonts",
 ]
 
 [[package]]
@@ -13393,9 +13399,9 @@ dependencies = [
 
 [[package]]
 name = "stylo"
-version = "0.2.1"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bfacf8821bb157fbd6cebaa682f22bcebddc38e4165cdedba0061dae24b241c2"
+checksum = "d6034deda3e062865c4011d70a97ba8691bb00777f14c4575bee41451e46594d"
 dependencies = [
  "app_units",
  "arrayvec",
@@ -13409,12 +13415,11 @@ dependencies = [
  "fxhash",
  "icu_segmenter",
  "indexmap 2.9.0",
- "itertools 0.10.5",
+ "itertools 0.14.0",
  "itoa 1.0.15",
  "lazy_static",
  "log",
  "malloc_size_of_derive",
- "markup5ever 0.15.0",
  "matches",
  "mime",
  "new_debug_unreachable",
@@ -13426,7 +13431,7 @@ dependencies = [
  "precomputed-hash",
  "rayon",
  "rayon-core",
- "selectors 0.27.0",
+ "selectors 0.28.0",
  "serde",
  "servo_arc 0.4.0",
  "smallbitvec",
@@ -13448,13 +13453,14 @@ dependencies = [
  "url",
  "void",
  "walkdir",
+ "web_atoms",
 ]
 
 [[package]]
 name = "stylo_atoms"
-version = "0.2.0"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b943c983729930ee70141ab6515604bb33ff5de8d7626024072c38a27c8023bb"
+checksum = "4b7dacdd62700078300263cece93a9c41823305d461ee5b3b65a51d6cdce3d38"
 dependencies = [
  "string_cache",
  "string_cache_codegen",
@@ -13462,15 +13468,15 @@ dependencies = [
 
 [[package]]
 name = "stylo_config"
-version = "0.2.0"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2e53afe5289d75063564e60aa59c591a2c496d3425f2bb2a3002251785e2058"
+checksum = "3fe07c673f8989e237e544c17723a8831d37d80b1a55437ac67bec8fe877e3cb"
 
 [[package]]
 name = "stylo_derive"
-version = "0.2.0"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e745b58d581ba8eb932825e684634eae9be848fd49c086b3f17ccb7d6c417594"
+checksum = "9a2c2bf677324ef587e97b8f5fc055f97a50bd26493411100e1442824ab289b8"
 dependencies = [
  "darling",
  "proc-macro2",
@@ -13481,9 +13487,9 @@ dependencies = [
 
 [[package]]
 name = "stylo_dom"
-version = "0.2.0"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06606e83c4feb986ea601a2c7d625aa5c303ecfc9526494480f6e599b8d6956e"
+checksum = "183a89dc60a136308a1c83056660692bb598b3a99db2ac79b1b53f935cf3c87d"
 dependencies = [
  "bitflags 2.9.1",
  "stylo_malloc_size_of",
@@ -13491,14 +13497,14 @@ dependencies = [
 
 [[package]]
 name = "stylo_malloc_size_of"
-version = "0.2.0"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93db6fdbcde7f037f7810873374d85a2b3d37686b046577943818167a6e52d3f"
+checksum = "150243bffdabaeafc7370354f6102559fec103b63d84875383fb657dd9c8549c"
 dependencies = [
  "app_units",
  "cssparser 0.35.0",
  "euclid",
- "selectors 0.27.0",
+ "selectors 0.28.0",
  "servo_arc 0.4.0",
  "smallbitvec",
  "smallvec",
@@ -13509,15 +13515,15 @@ dependencies = [
 
 [[package]]
 name = "stylo_static_prefs"
-version = "0.2.0"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "decb57071c4b4d5690a9719fb04a07cf2fab0fa3df99a830ef735192a1a98e5d"
+checksum = "9600d11cdad6c275426272f35fbe085c4362be1d67bb9775f378fd2be7942d3a"
 
 [[package]]
 name = "stylo_taffy"
-version = "0.1.0-alpha.1"
+version = "0.1.0-alpha.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a6da84b2bae9d25a2156b7cf89acab90683ac4d25dcb3fe1817dc9e77a20ce2f"
+checksum = "1c0f66aad13b557aafcf971220a04150228604da2b377f0b2aa6a03113adfac7"
 dependencies = [
  "stylo",
  "taffy",
@@ -13525,16 +13531,16 @@ dependencies = [
 
 [[package]]
 name = "stylo_traits"
-version = "0.2.0"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f611ebeee90c0d255bf4d4fadc05f964ef019f8aaed307108fa859c826f2753"
+checksum = "2a24b6eb257a1e68e184e113a3a426dd9e38e4a0017b805fcf43a44c939e5642"
 dependencies = [
  "app_units",
  "bitflags 2.9.1",
  "cssparser 0.35.0",
  "euclid",
  "malloc_size_of_derive",
- "selectors 0.27.0",
+ "selectors 0.28.0",
  "serde",
  "servo_arc 0.4.0",
  "stylo_atoms",
@@ -13634,7 +13640,7 @@ version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f745de914febc7c9ab4388dfaf94bbc87e69f57bb41133a9b0c84d4be49856f3"
 dependencies = [
- "skrifa 0.31.1",
+ "skrifa",
  "yazi",
  "zeno",
 ]
@@ -15635,60 +15641,48 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
 
 [[package]]
 name = "vello"
-version = "0.4.1"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d5b0bafa35e0c2e4132104576d6bcec4bf7cd0044f1760e92ecae0d4d9bc0e7"
+checksum = "df026e62e8b0d12d55ff5e91ae9114a20f82d9b856bedfdbf3abd5d1472dceed"
 dependencies = [
  "bytemuck",
  "futures-intrusive",
  "log",
- "peniko 0.3.2",
+ "peniko",
  "png",
- "skrifa 0.26.6",
+ "skrifa",
  "static_assertions",
  "thiserror 2.0.12",
  "vello_encoding",
  "vello_shaders",
- "wgpu 23.0.1",
+ "wgpu 24.0.5",
 ]
 
 [[package]]
 name = "vello_encoding"
-version = "0.4.1"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbbdec68dea2b39ece9f82ab15ec4cf2c4f8600ce6926df0638290702d95b3f7"
+checksum = "3c5702642f6ea77eedc12d119e1eebead0dba3cf91fe5c5d1f3efc12bf0cfaf1"
 dependencies = [
  "bytemuck",
  "guillotiere",
- "peniko 0.3.2",
- "skrifa 0.26.6",
+ "peniko",
+ "skrifa",
  "smallvec",
 ]
 
 [[package]]
 name = "vello_shaders"
-version = "0.4.1"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e0179d74cf9131dfd7882323751d2544f3aefdfda9d16c39bbe2729799410d2"
+checksum = "381790a3779021edd9f88267c1b13b49546cb0fb164f329ee2f2587869ddf459"
 dependencies = [
  "bytemuck",
- "naga 23.1.0",
+ "naga 24.0.0",
  "thiserror 2.0.12",
  "vello_encoding",
 ]
 
-[[package]]
-name = "vello_svg"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a0fd265f55b496a8048a43c769bf15487f2be9f6642086b3468c64677cc8c87"
-dependencies = [
- "image",
- "thiserror 2.0.12",
- "usvg",
- "vello",
-]
-
 [[package]]
 name = "version-compare"
 version = "0.2.0"
@@ -16245,6 +16239,18 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "web_atoms"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b9c5f0bc545ea3b20b423e33b9b457764de0b3730cd957f6c6aa6c301785f6e"
+dependencies = [
+ "phf 0.11.3",
+ "phf_codegen 0.11.3",
+ "string_cache",
+ "string_cache_codegen",
+]
+
 [[package]]
 name = "webbrowser"
 version = "1.0.4"
@@ -16399,16 +16405,17 @@ dependencies = [
 
 [[package]]
 name = "wgpu"
-version = "23.0.1"
+version = "24.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "80f70000db37c469ea9d67defdc13024ddf9a5f1b89cb2941b812ad7cde1735a"
+checksum = "6b0b3436f0729f6cdf2e6e9201f3d39dc95813fad61d826c1ed07918b4539353"
 dependencies = [
  "arrayvec",
- "cfg_aliases 0.1.1",
+ "bitflags 2.9.1",
+ "cfg_aliases 0.2.1",
  "document-features",
  "js-sys",
  "log",
- "naga 23.1.0",
+ "naga 24.0.0",
  "parking_lot",
  "profiling",
  "raw-window-handle 0.6.2",
@@ -16417,9 +16424,9 @@ dependencies = [
  "wasm-bindgen",
  "wasm-bindgen-futures",
  "web-sys",
- "wgpu-core 23.0.1",
- "wgpu-hal 23.0.1",
- "wgpu-types 23.0.0",
+ "wgpu-core 24.0.5",
+ "wgpu-hal 24.0.4",
+ "wgpu-types 24.0.0",
 ]
 
 [[package]]
@@ -16450,27 +16457,27 @@ dependencies = [
 
 [[package]]
 name = "wgpu-core"
-version = "23.0.1"
+version = "24.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a"
+checksum = "7f0aa306497a238d169b9dc70659105b4a096859a34894544ca81719242e1499"
 dependencies = [
  "arrayvec",
  "bit-vec 0.8.0",
  "bitflags 2.9.1",
- "cfg_aliases 0.1.1",
+ "cfg_aliases 0.2.1",
  "document-features",
  "indexmap 2.9.0",
  "log",
- "naga 23.1.0",
+ "naga 24.0.0",
  "once_cell",
  "parking_lot",
  "profiling",
  "raw-window-handle 0.6.2",
  "rustc-hash 1.1.0",
  "smallvec",
- "thiserror 1.0.69",
- "wgpu-hal 23.0.1",
- "wgpu-types 23.0.0",
+ "thiserror 2.0.12",
+ "wgpu-hal 24.0.4",
+ "wgpu-types 24.0.0",
 ]
 
 [[package]]
@@ -16520,9 +16527,9 @@ dependencies = [
 
 [[package]]
 name = "wgpu-hal"
-version = "23.0.1"
+version = "24.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89364b8a0b211adc7b16aeaf1bd5ad4a919c1154b44c9ce27838213ba05fd821"
+checksum = "f112f464674ca69f3533248508ee30cb84c67cf06c25ff6800685f5e0294e259"
 dependencies = [
  "android_system_properties",
  "arrayvec",
@@ -16531,9 +16538,9 @@ dependencies = [
  "bitflags 2.9.1",
  "block",
  "bytemuck",
- "cfg_aliases 0.1.1",
+ "cfg_aliases 0.2.1",
  "core-graphics-types 0.1.3",
- "glow 0.14.2",
+ "glow 0.16.0",
  "glutin_wgl_sys 0.6.1",
  "gpu-alloc",
  "gpu-allocator 0.27.0",
@@ -16543,11 +16550,12 @@ dependencies = [
  "libc",
  "libloading 0.8.7",
  "log",
- "metal 0.29.0",
- "naga 23.1.0",
+ "metal 0.31.0",
+ "naga 24.0.0",
  "ndk-sys 0.5.0+25.2.9519653",
  "objc",
  "once_cell",
+ "ordered-float 4.6.0",
  "parking_lot",
  "profiling",
  "range-alloc",
@@ -16555,10 +16563,10 @@ dependencies = [
  "renderdoc-sys",
  "rustc-hash 1.1.0",
  "smallvec",
- "thiserror 1.0.69",
+ "thiserror 2.0.12",
  "wasm-bindgen",
  "web-sys",
- "wgpu-types 23.0.0",
+ "wgpu-types 24.0.0",
  "windows 0.58.0",
  "windows-core 0.58.0",
 ]
@@ -16576,12 +16584,13 @@ dependencies = [
 
 [[package]]
 name = "wgpu-types"
-version = "23.0.0"
+version = "24.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068"
+checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c"
 dependencies = [
  "bitflags 2.9.1",
  "js-sys",
+ "log",
  "web-sys",
 ]
 

+ 2 - 0
_typos.toml

@@ -8,6 +8,8 @@ seeked = "seeked"
 # https://developer.apple.com/forums/thread/108953
 # udid = unique device identifier
 udid = "udid"
+# Part of Blitz's API
+unparented = "unparented"
 
 [files]
 extend-exclude = ["translations/*", "CHANGELOG.md", "*.js"]

+ 10 - 12
packages/native/Cargo.toml

@@ -11,27 +11,25 @@ keywords = ["dom", "ui", "gui", "react"]
 
 
 [features]
-default = ["accessibility", "hot-reload", "menu", "tracing", "net", "svg"]
-svg = ["blitz-dom/svg", "blitz-renderer-vello/svg"]
+default = ["accessibility", "hot-reload", "menu", "tracing", "net", "svg", "system-fonts"]
+svg = ["blitz-dom/svg", "blitz-paint/svg"]
 net = ["dep:tokio", "dep:blitz-net"]
 accessibility = ["blitz-shell/accessibility", "blitz-dom/accessibility"]
 menu = ["blitz-shell/menu"]
 tracing = ["dep:tracing", "blitz-shell/tracing", "blitz-dom/tracing"]
 hot-reload = ["dep:dioxus-cli-config", "dep:dioxus-devtools"]
+system-fonts = ["blitz-dom/system_fonts"]
 autofocus = []
 
 [dependencies]
 # Blitz dependencies
-blitz-renderer-vello = { version = "0.1.0-alpha.1", default-features = false }
-blitz-dom = { version = "0.1.0-alpha.1", default-features = false }
-blitz-net = { version = "0.1.0-alpha.1", optional = true }
-blitz-traits = { version = "0.1.0-alpha.1" }
-blitz-shell = { version = "0.1.0-alpha.1", default-features = false }
-# blitz-renderer-vello = { git = "https://github.com/dioxuslabs/blitz", rev="c1a7ecf06d1760a268e0046dc0e43f6c796ddc3c", default-features = false }
-# blitz-dom = { git = "https://github.com/dioxuslabs/blitz", rev="c1a7ecf06d1760a268e0046dc0e43f6c796ddc3c", default-features = false }
-# blitz-net = { git = "https://github.com/dioxuslabs/blitz", rev="c1a7ecf06d1760a268e0046dc0e43f6c796ddc3c", optional = true }
-# blitz-traits = { git = "https://github.com/dioxuslabs/blitz", rev="c1a7ecf06d1760a268e0046dc0e43f6c796ddc3c" }
-# blitz-shell = { git = "https://github.com/dioxuslabs/blitz", rev="c1a7ecf06d1760a268e0046dc0e43f6c796ddc3c", default-features = false }
+anyrender = { version = "0.1", default-features = false }
+anyrender_vello = { version = "0.1", default-features = false }
+blitz-dom = { version = "0.1.0-alpha.2", default-features = false }
+blitz-net = { version = "0.1.0-alpha.2", optional = true }
+blitz-paint = { version = "0.1.0-alpha.2", optional = true }
+blitz-traits = { version = "0.1.0-alpha.2" }
+blitz-shell = { version = "0.1.0-alpha.2", default-features = false }
 
 # DioxusLabs dependencies
 dioxus-core = { workspace = true }

+ 7 - 38
packages/native/src/assets.rs

@@ -1,24 +1,18 @@
 use blitz_shell::BlitzShellNetCallback;
 use std::sync::Arc;
 
-use blitz_dom::{
-    net::{CssHandler, ImageHandler, Resource},
-    util::ImageType,
-    BaseDocument,
-};
+use blitz_dom::net::Resource;
 use blitz_shell::BlitzShellEvent;
 use blitz_traits::net::{NetCallback, NetProvider};
 use winit::event_loop::EventLoopProxy;
 
-use crate::NodeId;
-
 pub struct DioxusNativeNetProvider {
-    callback: Arc<dyn NetCallback<Data = Resource> + 'static>,
-    inner_net_provider: Arc<dyn NetProvider<Data = Resource> + 'static>,
+    callback: Arc<dyn NetCallback<Resource> + 'static>,
+    inner_net_provider: Arc<dyn NetProvider<Resource> + 'static>,
 }
 impl DioxusNativeNetProvider {
-    pub fn shared(proxy: EventLoopProxy<BlitzShellEvent>) -> Arc<dyn NetProvider<Data = Resource>> {
-        Arc::new(Self::new(proxy)) as Arc<dyn NetProvider<Data = Resource>>
+    pub fn shared(proxy: EventLoopProxy<BlitzShellEvent>) -> Arc<dyn NetProvider<Resource>> {
+        Arc::new(Self::new(proxy)) as Arc<dyn NetProvider<Resource>>
     }
 
     pub fn new(proxy: EventLoopProxy<BlitzShellEvent>) -> Self {
@@ -31,14 +25,12 @@ impl DioxusNativeNetProvider {
     }
 }
 
-impl NetProvider for DioxusNativeNetProvider {
-    type Data = Resource;
-
+impl NetProvider<Resource> for DioxusNativeNetProvider {
     fn fetch(
         &self,
         doc_id: usize,
         request: blitz_traits::net::Request,
-        handler: blitz_traits::net::BoxedHandler<Self::Data>,
+        handler: blitz_traits::net::BoxedHandler<Resource>,
     ) {
         if request.url.scheme() == "dioxus" {
             match dioxus_asset_resolver::serve_asset_from_raw_path(request.url.path()) {
@@ -55,26 +47,3 @@ impl NetProvider for DioxusNativeNetProvider {
         }
     }
 }
-
-pub fn fetch_linked_stylesheet(doc: &BaseDocument, node_id: NodeId, queued_url: String) {
-    let url = doc.resolve_url(&queued_url);
-    doc.net_provider.fetch(
-        doc.id(),
-        blitz_traits::net::Request::get(url.clone()),
-        Box::new(CssHandler {
-            node: node_id,
-            source_url: url,
-            guard: doc.guard.clone(),
-            provider: doc.net_provider.clone(),
-        }),
-    );
-}
-
-pub fn fetch_image(doc: &BaseDocument, node_id: usize, queued_image: String) {
-    let src = doc.resolve_url(&queued_image);
-    doc.net_provider.fetch(
-        doc.id(),
-        blitz_traits::net::Request::get(src),
-        Box::new(ImageHandler::new(node_id, ImageType::Image)),
-    );
-}

+ 36 - 14
packages/native/src/dioxus_application.rs

@@ -1,4 +1,4 @@
-use blitz_renderer_vello::BlitzVelloRenderer;
+use anyrender_vello::VelloWindowRenderer;
 use blitz_shell::BlitzApplication;
 use dioxus_core::{ScopeId, VirtualDom};
 use dioxus_history::{History, MemoryHistory};
@@ -10,13 +10,34 @@ use winit::window::WindowId;
 
 use crate::{
     assets::DioxusNativeNetProvider, contexts::DioxusNativeDocument,
-    mutation_writer::MutationWriter, BlitzShellEvent, DioxusDocument, DioxusNativeEvent,
-    WindowConfig,
+    mutation_writer::MutationWriter, BlitzShellEvent, DioxusDocument, WindowConfig,
 };
 
+/// Dioxus-native specific event type
+pub enum DioxusNativeEvent {
+    /// A hotreload event, basically telling us to update our templates.
+    #[cfg(all(
+        feature = "hot-reload",
+        debug_assertions,
+        not(target_os = "android"),
+        not(target_os = "ios")
+    ))]
+    DevserverEvent(dioxus_devtools::DevserverMsg),
+
+    /// Create a new head element from the Link and Title elements
+    ///
+    /// todo(jon): these should probabkly be synchronous somehow
+    CreateHeadElement {
+        window: WindowId,
+        name: String,
+        attributes: Vec<(String, String)>,
+        contents: Option<String>,
+    },
+}
+
 pub struct DioxusNativeApplication {
     pending_vdom: Option<VirtualDom>,
-    inner: BlitzApplication<DioxusDocument, BlitzVelloRenderer>,
+    inner: BlitzApplication<VelloWindowRenderer>,
     proxy: EventLoopProxy<BlitzShellEvent>,
 }
 
@@ -29,7 +50,7 @@ impl DioxusNativeApplication {
         }
     }
 
-    pub fn add_window(&mut self, window_config: WindowConfig<DioxusDocument, BlitzVelloRenderer>) {
+    pub fn add_window(&mut self, window_config: WindowConfig<VelloWindowRenderer>) {
         self.inner.add_window(window_config);
     }
 
@@ -48,7 +69,8 @@ impl DioxusNativeApplication {
             DioxusNativeEvent::DevserverEvent(event) => match event {
                 dioxus_devtools::DevserverMsg::HotReload(hotreload_message) => {
                     for window in self.inner.windows.values_mut() {
-                        dioxus_devtools::apply_changes(&window.doc.vdom, hotreload_message);
+                        let doc = window.downcast_doc_mut::<DioxusDocument>();
+                        dioxus_devtools::apply_changes(&doc.vdom, hotreload_message);
                         window.poll();
                     }
                 }
@@ -66,7 +88,8 @@ impl DioxusNativeApplication {
                 window,
             } => {
                 if let Some(window) = self.inner.windows.get_mut(window) {
-                    window.doc.create_head_element(name, attributes, contents);
+                    let doc = window.downcast_doc_mut::<DioxusDocument>();
+                    doc.create_head_element(name, attributes, contents);
                     window.poll();
                 }
             }
@@ -103,7 +126,7 @@ impl ApplicationHandler<BlitzShellEvent> for DioxusNativeApplication {
 
         // Create document + window from the baked virtualdom
         let doc = DioxusDocument::new(vdom, net_provider);
-        let window = WindowConfig::new(doc);
+        let window = WindowConfig::new(Box::new(doc) as _);
 
         // little hack since View::init is not public - fix this once alpha-2 is out
         let old_windows = self.inner.windows.keys().copied().collect::<HashSet<_>>();
@@ -114,7 +137,8 @@ impl ApplicationHandler<BlitzShellEvent> for DioxusNativeApplication {
         // todo(jon): we should actually mess with the pending windows instead of passing along the contexts
         for window_id in new_windows.difference(&old_windows) {
             let window = self.inner.windows.get_mut(window_id).unwrap();
-            window.doc.vdom.in_runtime(|| {
+            let doc = window.downcast_doc_mut::<DioxusDocument>();
+            doc.vdom.in_runtime(|| {
                 let shared: Rc<dyn dioxus_document::Document> =
                     Rc::new(DioxusNativeDocument::new(self.proxy.clone(), *window_id));
                 ScopeId::ROOT.provide_context(shared);
@@ -122,14 +146,12 @@ impl ApplicationHandler<BlitzShellEvent> for DioxusNativeApplication {
 
             // Add history
             let history_provider: Rc<dyn History> = Rc::new(MemoryHistory::default());
-            window
-                .doc
-                .vdom
+            doc.vdom
                 .in_runtime(|| ScopeId::ROOT.provide_context(history_provider));
 
             // Queue rebuild
-            let mut writer = MutationWriter::new(&mut window.doc.inner, &mut window.doc.vdom_state);
-            window.doc.vdom.rebuild(&mut writer);
+            let mut writer = MutationWriter::new(&mut doc.inner, &mut doc.vdom_state);
+            doc.vdom.rebuild(&mut writer);
             drop(writer);
 
             // And then request redraw

+ 173 - 478
packages/native/src/dioxus_document.rs

@@ -1,29 +1,36 @@
 //! Integration between Dioxus and Blitz
-
+use blitz_dom::Attribute;
+use futures_util::{pin_mut, FutureExt};
+use std::ops::{Deref, DerefMut};
 use std::{any::Any, collections::HashMap, rc::Rc, sync::Arc};
 
 use blitz_dom::{
-    local_name, namespace_url,
-    net::Resource,
-    node::{Attribute, NodeSpecificData},
-    ns, Atom, BaseDocument, ElementNodeData, Node, NodeData, QualName, DEFAULT_CSS,
+    net::Resource, BaseDocument, Document, EventDriver, EventHandler, Node, DEFAULT_CSS,
+};
+use blitz_traits::{
+    events::UiEvent, net::NetProvider, ColorScheme, DomEvent, DomEventData, EventState, Viewport,
 };
 
-use blitz_traits::{net::NetProvider, ColorScheme, Document, DomEvent, DomEventData, Viewport};
 use dioxus_core::{ElementId, Event, VirtualDom};
-use dioxus_html::{set_event_converter, FormValue, PlatformEventData};
-use futures_util::{pin_mut, FutureExt};
+use dioxus_html::{set_event_converter, PlatformEventData};
 
-use super::event_handler::{BlitzKeyboardData, NativeClickData, NativeConverter, NativeFormData};
+use crate::events::{BlitzKeyboardData, NativeClickData, NativeConverter, NativeFormData};
 use crate::mutation_writer::{DioxusState, MutationWriter};
+use crate::qual_name;
 use crate::NodeId;
 
-pub(crate) fn qual_name(local_name: &str, namespace: Option<&str>) -> QualName {
-    QualName {
-        prefix: None,
-        ns: namespace.map(Atom::from).unwrap_or(ns!(html)),
-        local: Atom::from(local_name),
-    }
+fn wrap_event_data<T: Any>(value: T) -> Rc<dyn Any> {
+    Rc::new(PlatformEventData::new(Box::new(value)))
+}
+
+/// Get the value of the "dioxus-data-id" attribute parsed aa usize
+fn get_dioxus_id(node: &Node) -> Option<ElementId> {
+    node.element_data()?
+        .attrs
+        .iter()
+        .find(|attr| *attr.name.local == *"data-dioxus-id")
+        .and_then(|attr| attr.value.parse::<usize>().ok())
+        .map(ElementId)
 }
 
 pub struct DioxusDocument {
@@ -41,340 +48,8 @@ pub struct DioxusDocument {
     pub(crate) main_element_id: NodeId,
 }
 
-// Implement DocumentLike and required traits for DioxusDocument
-
-impl AsRef<BaseDocument> for DioxusDocument {
-    fn as_ref(&self) -> &BaseDocument {
-        &self.inner
-    }
-}
-impl AsMut<BaseDocument> for DioxusDocument {
-    fn as_mut(&mut self) -> &mut BaseDocument {
-        &mut self.inner
-    }
-}
-impl From<DioxusDocument> for BaseDocument {
-    fn from(doc: DioxusDocument) -> BaseDocument {
-        doc.inner
-    }
-}
-impl Document for DioxusDocument {
-    type Doc = BaseDocument;
-
-    fn poll(&mut self, mut cx: std::task::Context) -> bool {
-        {
-            let fut = self.vdom.wait_for_work();
-            pin_mut!(fut);
-
-            match fut.poll_unpin(&mut cx) {
-                std::task::Poll::Ready(_) => {}
-                std::task::Poll::Pending => return false,
-            }
-        }
-
-        let mut writer = MutationWriter::new(&mut self.inner, &mut self.vdom_state);
-        self.vdom.render_immediate(&mut writer);
-
-        true
-    }
-
-    fn id(&self) -> usize {
-        self.inner.id()
-    }
-
-    fn handle_event(&mut self, event: &mut DomEvent) {
-        let chain = self.inner.node_chain(event.target);
-
-        set_event_converter(Box::new(NativeConverter {}));
-
-        let renderer_event = event.clone();
-
-        let mut prevent_default = false;
-        let mut stop_propagation = false;
-
-        match &event.data {
-            DomEventData::MouseMove(data)
-            | DomEventData::MouseDown(data)
-            | DomEventData::MouseUp(data) => {
-                let click_event_data = wrap_event_data(NativeClickData(data.clone()));
-
-                for node_id in chain.clone().into_iter() {
-                    let node = &self.inner.tree()[node_id];
-                    let dioxus_id = node.element_data().and_then(DioxusDocument::dioxus_id);
-
-                    if let Some(id) = dioxus_id {
-                        let click_event = Event::new(click_event_data.clone(), true);
-                        self.vdom
-                            .runtime()
-                            .handle_event(event.name(), click_event.clone(), id);
-                        prevent_default |= !click_event.default_action_enabled();
-                        stop_propagation |= !click_event.propagates();
-                    }
-
-                    if !event.bubbles || stop_propagation {
-                        break;
-                    }
-                }
-            }
-
-            DomEventData::Click(data) => {
-                let click_event_data = wrap_event_data(NativeClickData(data.clone()));
-
-                for node_id in chain.clone().into_iter() {
-                    let node = &self.inner.tree()[node_id];
-                    let dioxus_id = node.element_data().and_then(DioxusDocument::dioxus_id);
-                    let mut trigger_label = false;
-
-                    if let Some(id) = dioxus_id {
-                        // Trigger click event
-                        let click_event = Event::new(click_event_data.clone(), true);
-                        self.vdom
-                            .runtime()
-                            .handle_event("click", click_event.clone(), id);
-                        prevent_default |= !click_event.default_action_enabled();
-                        stop_propagation |= !click_event.propagates();
-
-                        if !prevent_default {
-                            let mut default_event =
-                                DomEvent::new(node_id, renderer_event.data.clone());
-                            self.inner.as_mut().handle_event(&mut default_event);
-                            prevent_default = true;
-
-                            let element = self.inner.tree()[node_id].element_data().unwrap();
-                            trigger_label = element.name.local == *"label";
-
-                            //TODO Check for other inputs which trigger input event on click here, eg radio
-                            let triggers_input_event = element.name.local == local_name!("input")
-                                && element.attr(local_name!("type")) == Some("checkbox");
-                            if triggers_input_event {
-                                let form_data =
-                                    wrap_event_data(self.input_event_form_data(&chain, element));
-                                let input_event = Event::new(form_data, true);
-                                self.vdom.runtime().handle_event("input", input_event, id);
-                            }
-                        }
-                    }
-
-                    //Clicking labels triggers click, and possibly input event, of bound input
-                    if trigger_label {
-                        if let Some((dioxus_id, node_id)) = self.label_bound_input_element(node_id)
-                        {
-                            let click_event = Event::new(click_event_data.clone(), true);
-                            self.vdom.runtime().handle_event(
-                                "click",
-                                click_event.clone(),
-                                dioxus_id,
-                            );
-
-                            // Handle default DOM event
-                            if click_event.default_action_enabled() {
-                                let DomEventData::Click(event) = &renderer_event.data else {
-                                    unreachable!();
-                                };
-                                let input_click_data = self
-                                    .inner
-                                    .get_node(node_id)
-                                    .unwrap()
-                                    .synthetic_click_event(event.mods);
-                                let mut default_event = DomEvent::new(node_id, input_click_data);
-                                self.inner.as_mut().handle_event(&mut default_event);
-                                prevent_default = true;
-
-                                // TODO: Generated click events should bubble separately
-                                // prevent_default |= !click_event.default_action_enabled();
-
-                                //TODO Check for other inputs which trigger input event on click here, eg radio
-                                let element_data = self
-                                    .inner
-                                    .get_node(node_id)
-                                    .unwrap()
-                                    .element_data()
-                                    .unwrap();
-                                let triggers_input_event =
-                                    element_data.attr(local_name!("type")) == Some("checkbox");
-                                let form_data = wrap_event_data(
-                                    self.input_event_form_data(&chain, element_data),
-                                );
-                                if triggers_input_event {
-                                    let input_event = Event::new(form_data, true);
-                                    self.vdom.runtime().handle_event(
-                                        "input",
-                                        input_event,
-                                        dioxus_id,
-                                    );
-                                }
-                            }
-                        }
-                    }
-
-                    if !event.bubbles || stop_propagation {
-                        break;
-                    }
-                }
-            }
-
-            DomEventData::KeyPress(kevent) => {
-                let key_event_data = wrap_event_data(BlitzKeyboardData(kevent.clone()));
-
-                for node_id in chain.clone().into_iter() {
-                    let node = &self.inner.tree()[node_id];
-                    let dioxus_id = node.element_data().and_then(DioxusDocument::dioxus_id);
-                    println!("{} {:?}", node_id, dioxus_id);
-
-                    if let Some(id) = dioxus_id {
-                        if kevent.state.is_pressed() {
-                            // Handle keydown event
-                            let event = Event::new(key_event_data.clone(), true);
-                            self.vdom
-                                .runtime()
-                                .handle_event("keydown", event.clone(), id);
-                            prevent_default |= !event.default_action_enabled();
-                            stop_propagation |= !event.propagates();
-
-                            if !prevent_default && kevent.text.is_some() {
-                                // Handle keypress event
-                                let event = Event::new(key_event_data.clone(), true);
-                                self.vdom
-                                    .runtime()
-                                    .handle_event("keypress", event.clone(), id);
-                                prevent_default |= !event.default_action_enabled();
-                                stop_propagation |= !event.propagates();
-
-                                if !prevent_default {
-                                    // Handle default DOM event
-                                    let mut default_event =
-                                        DomEvent::new(node_id, renderer_event.data.clone());
-                                    self.inner.as_mut().handle_event(&mut default_event);
-                                    prevent_default = true;
-
-                                    // Handle input event
-                                    let element =
-                                        self.inner.tree()[node_id].element_data().unwrap();
-                                    let triggers_input_event = &element.name.local == "input"
-                                        && matches!(
-                                            element.attr(local_name!("type")),
-                                            None | Some("text" | "password" | "email" | "search")
-                                        );
-                                    if triggers_input_event {
-                                        let form_data = wrap_event_data(dbg!(
-                                            self.input_event_form_data(&chain, element)
-                                        ));
-                                        let input_event = Event::new(form_data, true);
-                                        self.vdom.runtime().handle_event("input", input_event, id);
-                                    }
-                                }
-                            }
-                        } else {
-                            // Handle keyup event
-                            let event = Event::new(key_event_data.clone(), true);
-                            self.vdom.runtime().handle_event("keyup", event.clone(), id);
-                            prevent_default |= !event.default_action_enabled();
-                            stop_propagation |= !event.propagates();
-                        }
-                    }
-
-                    if !event.bubbles || stop_propagation {
-                        break;
-                    }
-                }
-            }
-
-            DomEventData::Hover => {}
-
-            // TODO: Implement IME and Hover events handling
-            DomEventData::Ime(_) => {}
-        }
-
-        if !event.cancelable || !prevent_default {
-            self.inner.as_mut().handle_event(event);
-        }
-    }
-}
-
-fn wrap_event_data<T: Any>(value: T) -> Rc<dyn Any> {
-    Rc::new(PlatformEventData::new(Box::new(value)))
-}
-
 impl DioxusDocument {
-    /// Generate the FormData from an input event
-    /// Currently only cares about input checkboxes
-    pub fn input_event_form_data(
-        &self,
-        parent_chain: &[usize],
-        element_node_data: &ElementNodeData,
-    ) -> NativeFormData {
-        let parent_form = parent_chain.iter().find_map(|id| {
-            let node = self.inner.get_node(*id)?;
-            let element_data = node.element_data()?;
-            if element_data.name.local == local_name!("form") {
-                Some(node)
-            } else {
-                None
-            }
-        });
-        let values = if let Some(parent_form) = parent_form {
-            let mut values = HashMap::<String, FormValue>::new();
-            for form_input in self.input_descendents(parent_form).into_iter() {
-                // Match html behaviour here. To be included in values:
-                // - input must have a name
-                // - if its an input, we only include it if checked
-                // - if value is not specified, it defaults to 'on'
-                if let Some(name) = form_input.attr(local_name!("name")) {
-                    if form_input.attr(local_name!("type")) == Some("checkbox")
-                        && form_input
-                            .element_data()
-                            .and_then(|data| data.checkbox_input_checked())
-                            .unwrap_or(false)
-                    {
-                        let value = form_input
-                            .attr(local_name!("value"))
-                            .unwrap_or("on")
-                            .to_string();
-                        values.insert(name.to_string(), FormValue(vec![value]));
-                    }
-                }
-            }
-            values
-        } else {
-            Default::default()
-        };
-        let value = match &element_node_data.node_specific_data {
-            NodeSpecificData::CheckboxInput(checked) => checked.to_string(),
-            NodeSpecificData::TextInput(input_data) => input_data.editor.text().to_string(),
-            _ => element_node_data
-                .attr(local_name!("value"))
-                .unwrap_or_default()
-                .to_string(),
-        };
-
-        NativeFormData { value, values }
-    }
-
-    /// Collect all the inputs which are descendents of a given node
-    fn input_descendents(&self, node: &Node) -> Vec<&Node> {
-        node.children
-            .iter()
-            .flat_map(|id| {
-                let mut res = Vec::<&Node>::new();
-                let Some(n) = self.inner.get_node(*id) else {
-                    return res;
-                };
-                let Some(element_data) = n.element_data() else {
-                    return res;
-                };
-                if element_data.name.local == local_name!("input") {
-                    res.push(n);
-                }
-                res.extend(self.input_descendents(n).iter());
-                res
-            })
-            .collect()
-    }
-
-    pub fn new(
-        vdom: VirtualDom,
-        net_provider: Option<Arc<dyn NetProvider<Data = Resource>>>,
-    ) -> Self {
+    pub fn new(vdom: VirtualDom, net_provider: Option<Arc<dyn NetProvider<Resource>>>) -> Self {
         let viewport = Viewport::new(0, 0, 1.0, ColorScheme::Light);
         let mut doc = BaseDocument::new(viewport);
 
@@ -383,58 +58,38 @@ impl DioxusDocument {
             doc.set_net_provider(net_provider);
         }
 
-        // Create a virtual "html" element to act as the root element, as we won't necessarily
-        // have a single root otherwise, while the rest of blitz requires that we do
-        let html_element_id = doc.create_node(NodeData::Element(ElementNodeData::new(
-            qual_name("html", None),
-            Vec::new(),
-        )));
-        let root_node_id = doc.root_node().id;
-        let html_element = doc.get_node_mut(html_element_id).unwrap();
-        html_element.parent = Some(root_node_id);
-        let root_node = doc.get_node_mut(root_node_id).unwrap();
-        root_node.children.push(html_element_id);
+        // Create some minimal HTML to render the app into.
+
+        // Create the html element
+        let mut mutr = doc.mutate();
+        let html_element_id = mutr.create_element(qual_name("html", None), vec![]);
+        mutr.append_children(mutr.doc.root_node().id, &[html_element_id]);
+
+        // Create the body element
+        let head_element_id = mutr.create_element(qual_name("head", None), vec![]);
+        mutr.append_children(html_element_id, &[head_element_id]);
 
         // Create the body element
-        let body_element_id = doc.create_node(NodeData::Element(ElementNodeData::new(
-            qual_name("body", None),
-            Vec::new(),
-        )));
-        let html_element = doc.get_node_mut(html_element_id).unwrap();
-        html_element.children.push(body_element_id);
-        let body_element = doc.get_node_mut(body_element_id).unwrap();
-        body_element.parent = Some(html_element_id);
+        let body_element_id = mutr.create_element(qual_name("body", None), vec![]);
+        mutr.append_children(html_element_id, &[body_element_id]);
 
         // Create another virtual element to hold the root <div id="main"></div> under the html element
-        let main_element_id = doc.create_node(NodeData::Element(ElementNodeData::new(
-            qual_name("div", Some("id")),
-            vec![blitz_dom::node::Attribute {
-                name: qual_name("id", None),
-                value: "main".to_string(),
-            }],
-        )));
-        let body_element = doc.get_node_mut(body_element_id).unwrap();
-        body_element.children.push(main_element_id);
-        let main_element = doc.get_node_mut(main_element_id).unwrap();
-        main_element.parent = Some(body_element_id);
-
-        // Create the head element
-        let head_element_id = doc.create_node(NodeData::Element(ElementNodeData::new(
-            qual_name("head", None),
-            Vec::new(),
-        )));
-        let head_element = doc.get_node_mut(head_element_id).unwrap();
-        head_element.parent = Some(html_element_id);
-        let html_element = doc.get_node_mut(html_element_id).unwrap();
-        html_element.children.push(head_element_id);
+        let main_attr = blitz_dom::Attribute {
+            name: qual_name("id", None),
+            value: "main".to_string(),
+        };
+        let main_element_id = mutr.create_element(qual_name("main", None), vec![main_attr]);
+        mutr.append_children(body_element_id, &[main_element_id]);
+
+        drop(mutr);
 
         // Include default and user-specified stylesheets
         doc.add_user_agent_stylesheet(DEFAULT_CSS);
 
-        let state = DioxusState::create(&mut doc, main_element_id);
+        let vdom_state = DioxusState::create(main_element_id);
         let mut doc = Self {
             vdom,
-            vdom_state: state,
+            vdom_state,
             inner: doc,
             html_element_id,
             head_element_id,
@@ -443,45 +98,15 @@ impl DioxusDocument {
         };
 
         doc.inner.set_base_url("dioxus://index.html");
-
-        // doc.initial_build();
-
-        // doc.inner.print_tree();
+        //doc.initial_build();
+        doc.inner.print_tree();
 
         doc
     }
 
     pub fn initial_build(&mut self) {
-        // let mut writer = MutationWriter::new(&mut self.inner, &mut self.vdom_state);
-
-        // self.vdom.rebuild(&mut writer);
-        // dbg!(self.vdom.rebuild_to_vec());
-        // std::process::exit(0);
-        // dbg!(writer.state);
-    }
-
-    pub fn label_bound_input_element(&self, label_node_id: NodeId) -> Option<(ElementId, NodeId)> {
-        let bound_input_elements = self.inner.label_bound_input_elements(label_node_id);
-
-        // Filter down bound elements to those which have dioxus id
-        bound_input_elements.into_iter().find_map(|n| {
-            let target_element_data = n.element_data()?;
-            let node_id = n.id;
-            let dioxus_id = DioxusDocument::dioxus_id(target_element_data)?;
-            Some((dioxus_id, node_id))
-        })
-    }
-
-    fn dioxus_id(element_node_data: &ElementNodeData) -> Option<ElementId> {
-        Some(ElementId(
-            element_node_data
-                .attrs
-                .iter()
-                .find(|attr| *attr.name.local == *"data-dioxus-id")?
-                .value
-                .parse::<usize>()
-                .ok()?,
-        ))
+        let mut writer = MutationWriter::new(&mut self.inner, &mut self.vdom_state);
+        self.vdom.rebuild(&mut writer);
     }
 
     pub fn create_head_element(
@@ -490,26 +115,7 @@ impl DioxusDocument {
         attributes: &[(String, String)],
         contents: &Option<String>,
     ) {
-        let mut stylesheet = None;
-        let mut title = None;
-        if name == "link" {
-            let is_stylesheet = attributes
-                .iter()
-                .any(|(name, value)| name == "rel" && value == "stylesheet");
-            if is_stylesheet {
-                stylesheet = attributes
-                    .iter()
-                    .find(|(name, _value)| name == "href")
-                    .map(|(_name, value)| value.clone());
-            }
-        }
-
-        if name == "title" {
-            title = attributes
-                .iter()
-                .find(|(name, _value)| name == "text")
-                .and_then(|(_name, _value)| contents.clone());
-        }
+        let mut mutr = self.inner.mutate();
 
         let attributes = attributes
             .iter()
@@ -519,46 +125,135 @@ impl DioxusDocument {
             })
             .collect();
 
-        let new_element = self
-            .inner
-            .create_node(NodeData::Element(ElementNodeData::new(
-                qual_name(name, None),
-                attributes,
-            )));
-
+        let new_elem_id = mutr.create_element(qual_name(name, None), attributes);
+        mutr.append_children(self.head_element_id, &[new_elem_id]);
         if let Some(contents) = contents {
-            let text_node = self.inner.create_text_node(contents);
-            self.inner
-                .get_node_mut(new_element)
-                .unwrap()
-                .children
-                .push(text_node);
+            mutr.append_text_to_node(new_elem_id, contents).unwrap();
         }
 
-        self.inner
-            .get_node_mut(self.head_element_id)
-            .unwrap()
-            .children
-            .push(new_element);
+        // TODO: set title (we may also wish to have this built-in to the document?)
+        // if name == "title" {
+        //     let title = mutr.doc.nodes[new_elem_id].text_content();
+        //
+        // }
+    }
+}
 
-        if let Some(url) = stylesheet {
-            crate::assets::fetch_linked_stylesheet(&self.inner, new_element, url);
-        }
+// Implement DocumentLike and required traits for DioxusDocument
+impl Document for DioxusDocument {
+    fn id(&self) -> usize {
+        self.inner.id()
+    }
+
+    fn as_any_mut(&mut self) -> &mut dyn Any {
+        self
+    }
 
-        if let Some(_title) = title {
-            println!("todo: set title");
+    fn poll(&mut self, mut cx: std::task::Context) -> bool {
+        {
+            let fut = self.vdom.wait_for_work();
+            pin_mut!(fut);
+
+            match fut.poll_unpin(&mut cx) {
+                std::task::Poll::Ready(_) => {}
+                std::task::Poll::Pending => return false,
+            }
         }
+
+        let mut writer = MutationWriter::new(&mut self.inner, &mut self.vdom_state);
+        self.vdom.render_immediate(&mut writer);
+
+        true
     }
 
-    // pub fn apply_mutations(&mut self) {
-    //     // Apply the mutations to the actual dom
-    //     let mut writer = MutationWriter {
-    //         doc: &mut self.inner,
-    //         state: &mut self.vdom_state,
-    //     };
-    //     self.vdom.render_immediate(&mut writer);
-
-    //     println!("APPLY MUTATIONS");
-    //     self.inner.print_tree();
-    // }
+    fn handle_event(&mut self, event: UiEvent) {
+        set_event_converter(Box::new(NativeConverter {}));
+        let handler = DioxusEventHandler {
+            vdom: &mut self.vdom,
+            vdom_state: &mut self.vdom_state,
+        };
+        let mut driver = EventDriver::new(self.inner.mutate(), handler);
+        driver.handle_ui_event(event);
+    }
+}
+
+impl Deref for DioxusDocument {
+    type Target = BaseDocument;
+    fn deref(&self) -> &BaseDocument {
+        &self.inner
+    }
+}
+impl DerefMut for DioxusDocument {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.inner
+    }
+}
+impl From<DioxusDocument> for BaseDocument {
+    fn from(doc: DioxusDocument) -> BaseDocument {
+        doc.inner
+    }
+}
+
+pub struct DioxusEventHandler<'v> {
+    vdom: &'v mut VirtualDom,
+    #[allow(dead_code, reason = "WIP")]
+    vdom_state: &'v mut DioxusState,
+}
+
+impl EventHandler for DioxusEventHandler<'_> {
+    fn handle_event(
+        &mut self,
+        chain: &[usize],
+        event: &mut DomEvent,
+        mutr: &mut blitz_dom::DocumentMutator<'_>,
+        event_state: &mut EventState,
+    ) {
+        let event_data = match &event.data {
+            DomEventData::MouseMove(mevent)
+            | DomEventData::MouseDown(mevent)
+            | DomEventData::MouseUp(mevent)
+            | DomEventData::Click(mevent) => Some(wrap_event_data(NativeClickData(mevent.clone()))),
+
+            DomEventData::KeyDown(kevent)
+            | DomEventData::KeyUp(kevent)
+            | DomEventData::KeyPress(kevent) => {
+                Some(wrap_event_data(BlitzKeyboardData(kevent.clone())))
+            }
+
+            DomEventData::Input(data) => Some(wrap_event_data(NativeFormData {
+                value: data.value.clone(),
+                values: HashMap::new(),
+            })),
+
+            // TODO: Implement IME handling
+            DomEventData::Ime(_) => None,
+        };
+
+        let Some(event_data) = event_data else {
+            return;
+        };
+
+        for &node_id in chain {
+            // Get dioxus vdom id for node
+            let dioxus_id = mutr.doc.get_node(node_id).and_then(get_dioxus_id);
+            let Some(id) = dioxus_id else {
+                return;
+            };
+
+            // Handle event in vdom
+            let dx_event = Event::new(event_data.clone(), event.bubbles);
+            self.vdom
+                .runtime()
+                .handle_event(event.name(), dx_event.clone(), id);
+
+            // Update event state
+            if !dx_event.default_action_enabled() {
+                event_state.prevent_default();
+            }
+            if !dx_event.propagates() {
+                event_state.stop_propagation();
+                break;
+            }
+        }
+    }
 }

+ 0 - 23
packages/native/src/event.rs

@@ -1,23 +0,0 @@
-use winit::window::WindowId;
-
-/// Dioxus-native specific event type
-pub enum DioxusNativeEvent {
-    /// A hotreload event, basically telling us to update our templates.
-    #[cfg(all(
-        feature = "hot-reload",
-        debug_assertions,
-        not(target_os = "android"),
-        not(target_os = "ios")
-    ))]
-    DevserverEvent(dioxus_devtools::DevserverMsg),
-
-    /// Create a new head element from the Link and Title elements
-    ///
-    /// todo(jon): these should probabkly be synchronous somehow
-    CreateHeadElement {
-        window: WindowId,
-        name: String,
-        attributes: Vec<(String, String)>,
-        contents: Option<String>,
-    },
-}

+ 0 - 0
packages/native/src/event_handler.rs → packages/native/src/events.rs


+ 36 - 5
packages/native/src/lib.rs

@@ -13,13 +13,12 @@ mod assets;
 mod contexts;
 mod dioxus_application;
 mod dioxus_document;
-mod event;
-mod event_handler;
+mod events;
 mod mutation_writer;
 
-pub use dioxus_application::DioxusNativeApplication;
+use blitz_dom::{ns, Atom, QualName};
+pub use dioxus_application::{DioxusNativeApplication, DioxusNativeEvent};
 pub use dioxus_document::DioxusDocument;
-pub use event::DioxusNativeEvent;
 
 use blitz_shell::{create_default_event_loop, BlitzShellEvent, Config, WindowConfig};
 use dioxus_core::{ComponentFunction, Element, VirtualDom};
@@ -68,7 +67,6 @@ pub fn launch_cfg_with_props<P: Clone + 'static, M: 'static>(
         not(target_os = "ios")
     ))]
     {
-        use crate::event::DioxusNativeEvent;
         let proxy = event_loop.create_proxy();
         dioxus_devtools::connect(move |event| {
             let dxn_event = DioxusNativeEvent::DevserverEvent(event);
@@ -92,3 +90,36 @@ pub fn launch_cfg_with_props<P: Clone + 'static, M: 'static>(
     // Run event loop
     event_loop.run_app(&mut application).unwrap();
 }
+
+pub(crate) fn qual_name(local_name: &str, namespace: Option<&str>) -> QualName {
+    QualName {
+        prefix: None,
+        ns: namespace.map(Atom::from).unwrap_or(ns!(html)),
+        local: Atom::from(local_name),
+    }
+}
+
+// Syntax sugar to make tracing calls less noisy in function below
+macro_rules! trace {
+    ($pattern:literal) => {{
+        #[cfg(feature = "tracing")]
+        tracing::info!($pattern);
+    }};
+    ($pattern:literal, $item1:expr) => {{
+        #[cfg(feature = "tracing")]
+        tracing::info!($pattern, $item1);
+    }};
+    ($pattern:literal, $item1:expr, $item2:expr) => {{
+        #[cfg(feature = "tracing")]
+        tracing::info!($pattern, $item1, $item2);
+    }};
+    ($pattern:literal, $item1:expr, $item2:expr, $item3:expr) => {{
+        #[cfg(feature = "tracing")]
+        tracing::info!($pattern, $item1, $item2);
+    }};
+    ($pattern:literal, $item1:expr, $item2:expr, $item3:expr, $item4:expr) => {{
+        #[cfg(feature = "tracing")]
+        tracing::info!($pattern, $item1, $item2, $item3, $item4);
+    }};
+}
+pub(crate) use trace;

+ 184 - 455
packages/native/src/mutation_writer.rs

@@ -1,14 +1,10 @@
-use crate::{dioxus_document::qual_name, NodeId};
-use blitz_dom::{
-    local_name, namespace_url,
-    node::{Attribute, NodeSpecificData},
-    ns, BaseDocument, ElementNodeData, NodeData, QualName, RestyleHint,
-};
+//! Integration between Dioxus and Blitz
+use crate::{qual_name, trace, NodeId};
+use blitz_dom::{Attribute, BaseDocument, DocumentMutator};
 use dioxus_core::{
     AttributeValue, ElementId, Template, TemplateAttribute, TemplateNode, WriteMutations,
 };
 use rustc_hash::FxHashMap;
-use std::collections::HashSet;
 
 /// The state of the Dioxus integration with the RealDom
 #[derive(Debug)]
@@ -21,79 +17,13 @@ pub struct DioxusState {
     node_id_mapping: Vec<Option<NodeId>>,
 }
 
-/// A writer for mutations that can be used with the RealDom.
-pub struct MutationWriter<'a> {
-    /// The realdom associated with this writer
-    pub doc: &'a mut BaseDocument,
-
-    /// The state associated with this writer
-    pub state: &'a mut DioxusState,
-
-    pub style_nodes: HashSet<usize>,
-
-    /// The (latest) node which has been mounted in and had autofocus=true, if any
-    #[cfg(feature = "autofocus")]
-    pub node_to_autofocus: Option<usize>,
-}
-
-impl<'a> MutationWriter<'a> {
-    pub fn new(doc: &'a mut BaseDocument, state: &'a mut DioxusState) -> Self {
-        MutationWriter {
-            doc,
-            state,
-            style_nodes: HashSet::new(),
-
-            #[cfg(feature = "autofocus")]
-            node_to_autofocus: None,
-        }
-    }
-
-    fn is_style_node(&self, node_id: NodeId) -> bool {
-        self.doc
-            .get_node(node_id)
-            .unwrap()
-            .data
-            .is_element_with_tag_name(&local_name!("style"))
-    }
-
-    fn maybe_push_style_node(&mut self, node_id: impl Into<Option<NodeId>>) {
-        if let Some(node_id) = node_id.into() {
-            if self.is_style_node(node_id) {
-                self.style_nodes.insert(node_id);
-            }
-        }
-    }
-
-    #[track_caller]
-    fn maybe_push_parent_style_node(&mut self, node_id: NodeId) {
-        let parent_id = self.doc.get_node(node_id).unwrap().parent;
-        self.maybe_push_style_node(parent_id);
-    }
-}
-
-impl Drop for MutationWriter<'_> {
-    fn drop(&mut self) {
-        // Add/Update inline stylesheets (<style> elements)
-        for &id in &self.style_nodes {
-            self.doc.upsert_stylesheet_for_node(id);
-        }
-
-        #[cfg(feature = "autofocus")]
-        if let Some(node_id) = self.node_to_autofocus {
-            if self.doc.get_node(node_id).is_some() {
-                self.doc.set_focus_to(node_id);
-            }
-        }
-    }
-}
-
 impl DioxusState {
     /// Initialize the DioxusState in the RealDom
-    pub fn create(_doc: &mut BaseDocument, mount_node: NodeId) -> Self {
+    pub fn create(root_id: usize) -> Self {
         Self {
             templates: FxHashMap::default(),
-            stack: vec![mount_node],
-            node_id_mapping: vec![Some(mount_node)],
+            stack: vec![root_id],
+            node_id_mapping: vec![Some(root_id)],
         }
     }
 
@@ -106,6 +36,33 @@ impl DioxusState {
     pub fn try_element_to_node_id(&self, element_id: ElementId) -> Option<NodeId> {
         self.node_id_mapping.get(element_id.0).copied().flatten()
     }
+
+    pub(crate) fn anchor_and_nodes(&mut self, id: ElementId, m: usize) -> (usize, Vec<usize>) {
+        let anchor_node_id = self.element_to_node_id(id);
+        let new_nodes = self.m_stack_nodes(m);
+        (anchor_node_id, new_nodes)
+    }
+
+    pub(crate) fn m_stack_nodes(&mut self, m: usize) -> Vec<usize> {
+        self.stack.split_off(self.stack.len() - m)
+    }
+}
+
+/// A writer for mutations that can be used with the RealDom.
+pub struct MutationWriter<'a> {
+    /// The realdom associated with this writer
+    pub docm: DocumentMutator<'a>,
+    /// The state associated with this writer
+    pub state: &'a mut DioxusState,
+}
+
+impl<'a> MutationWriter<'a> {
+    pub fn new(doc: &'a mut BaseDocument, state: &'a mut DioxusState) -> Self {
+        MutationWriter {
+            docm: doc.mutate(),
+            state,
+        }
+    }
 }
 
 impl MutationWriter<'_> {
@@ -122,454 +79,226 @@ impl MutationWriter<'_> {
         self.state.node_id_mapping[element_id] = Some(node_id);
     }
 
-    /// Find a child in the document by child index path
-    fn load_child(&self, path: &[u8]) -> NodeId {
-        let mut current = self
-            .doc
-            .get_node(*self.state.stack.last().unwrap())
-            .unwrap();
-        for i in path {
-            let new_id = current.children[*i as usize];
-            current = self.doc.get_node(new_id).unwrap();
-        }
-        current.id
+    /// Create a ElementId -> NodeId mapping and push the node to the stack
+    fn map_new_node(&mut self, node_id: NodeId, element_id: ElementId) {
+        self.set_id_mapping(node_id, element_id);
+        self.state.stack.push(node_id);
     }
 
-    fn create_template_node(&mut self, node: &TemplateNode) -> NodeId {
-        match node {
-            TemplateNode::Element {
-                tag,
-                namespace,
-                attrs,
-                children,
-            } => {
-                let name = qual_name(tag, *namespace);
-                let attrs = attrs
-                    .iter()
-                    .filter_map(|attr| match attr {
-                        TemplateAttribute::Static {
-                            name,
-                            value,
-                            namespace,
-                        } => Some(Attribute {
-                            name: qual_name(name, *namespace),
-                            value: value.to_string(),
-                        }),
-                        TemplateAttribute::Dynamic { .. } => None,
-                    })
-                    .collect();
-
-                let mut data = ElementNodeData::new(name, attrs);
-                data.flush_style_attribute(self.doc.guard());
-
-                let child_ids: Vec<NodeId> = children
-                    .iter()
-                    .map(|child| self.create_template_node(child))
-                    .collect();
-
-                let id = self.doc.create_node(NodeData::Element(data));
-                let node = self.doc.get_node(id).unwrap();
-
-                // Initialise style data
-                *node.stylo_element_data.borrow_mut() = Some(Default::default());
-
-                if let Some(src_attr) = node.attr(local_name!("src")) {
-                    crate::assets::fetch_image(self.doc, id, src_attr.to_string());
-                }
-
-                let rel_attr = node.attr(local_name!("rel"));
-                let href_attr = node.attr(local_name!("href"));
-                if let (Some("stylesheet"), Some(href)) = (rel_attr, href_attr) {
-                    crate::assets::fetch_linked_stylesheet(self.doc, id, href.to_string());
-                }
-
-                // If the node has an "id" attribute, store it in the ID map.
-                if let Some(id_attr) = node.attr(local_name!("id")) {
-                    self.doc.nodes_to_id.insert(id_attr.to_string(), id);
-                }
-
-                for &child_id in &child_ids {
-                    self.doc.get_node_mut(child_id).unwrap().parent = Some(id);
-                }
-                self.doc.get_node_mut(id).unwrap().children = child_ids;
-
-                id
-            }
-            TemplateNode::Text { text } => self.doc.create_text_node(text),
-            TemplateNode::Dynamic { .. } => self.doc.create_node(NodeData::Comment),
-        }
+    /// Find a child in the document by child index path
+    fn load_child(&self, path: &[u8]) -> NodeId {
+        let top_of_stack_node_id = *self.state.stack.last().unwrap();
+        self.docm.node_at_path(top_of_stack_node_id, path)
     }
 }
 
 impl WriteMutations for MutationWriter<'_> {
-    fn append_children(&mut self, id: ElementId, m: usize) {
-        #[cfg(feature = "tracing")]
-        tracing::info!("append_children id:{} m:{}", id.0, m);
-
-        let children = self.state.stack.split_off(self.state.stack.len() - m);
-        let parent = self.state.element_to_node_id(id);
-        for child in children {
-            self.doc.get_node_mut(parent).unwrap().children.push(child);
-            self.doc.get_node_mut(child).unwrap().parent = Some(parent);
-        }
-
-        self.maybe_push_style_node(parent);
-    }
-
     fn assign_node_id(&mut self, path: &'static [u8], id: ElementId) {
-        #[cfg(feature = "tracing")]
-        tracing::info!("assign_node_id path:{:?} id:{}", path, id.0);
+        trace!("assign_node_id path:{:?} id:{}", path, id.0);
 
-        // If there is an existing node already mapped to that ID and
-        // it has no parent, then drop it
+        // If there is an existing node already mapped to that ID and it has no parent, then drop it
+        // TODO: more automated GC/ref-counted semantics for node lifetimes
         if let Some(node_id) = self.state.try_element_to_node_id(id) {
-            if let Some(node) = self.doc.get_node(node_id) {
-                if node.parent.is_none() {
-                    self.doc.remove_and_drop_node(node_id);
-                }
-            }
+            self.docm.remove_node_if_unparented(node_id);
         }
 
-        let node_id = self.load_child(path);
-        self.set_id_mapping(node_id, id);
+        // Map the node at specified path
+        self.set_id_mapping(self.load_child(path), id);
     }
 
     fn create_placeholder(&mut self, id: ElementId) {
-        #[cfg(feature = "tracing")]
-        tracing::info!("create_placeholder id:{}", id.0);
-
-        let node_id = self.doc.create_node(NodeData::Comment);
-        self.set_id_mapping(node_id, id);
-        self.state.stack.push(node_id);
+        trace!("create_placeholder id:{}", id.0);
+        let node_id = self.docm.create_comment_node();
+        self.map_new_node(node_id, id);
     }
 
     fn create_text_node(&mut self, value: &str, id: ElementId) {
-        #[cfg(feature = "tracing")]
-        tracing::info!("create_text_node id:{} text:{}", id.0, value);
-
-        let node_id = self.doc.create_text_node(value);
-        self.set_id_mapping(node_id, id);
-        self.state.stack.push(node_id);
+        trace!("create_text_node id:{} text:{}", id.0, value);
+        let node_id = self.docm.create_text_node(value);
+        self.map_new_node(node_id, id);
     }
 
-    #[allow(clippy::map_entry)]
-    fn load_template(&mut self, template: Template, index: usize, id: ElementId) {
-        if !self.state.templates.contains_key(&template) {
-            let template_root_ids: Vec<NodeId> = template
-                .roots
-                .iter()
-                .map(|root| self.create_template_node(root))
-                .collect();
-
-            self.state.templates.insert(template, template_root_ids);
-        }
-
-        let template_entry = &self.state.templates[&template];
-
-        let template_node_id = template_entry[index];
-        let clone_id = self.doc.deep_clone_node(template_node_id);
+    fn append_children(&mut self, id: ElementId, m: usize) {
+        trace!("append_children id:{} m:{}", id.0, m);
+        let (parent_id, child_node_ids) = self.state.anchor_and_nodes(id, m);
+        self.docm.append_children(parent_id, &child_node_ids);
+    }
 
-        #[cfg(feature = "autofocus")]
-        autofocus_node_recursive(self.doc, &mut self.node_to_autofocus, clone_id);
+    fn insert_nodes_after(&mut self, id: ElementId, m: usize) {
+        trace!("insert_nodes_after id:{} m:{}", id.0, m);
+        let (anchor_node_id, new_node_ids) = self.state.anchor_and_nodes(id, m);
+        self.docm.insert_nodes_after(anchor_node_id, &new_node_ids);
+    }
 
-        self.set_id_mapping(clone_id, id);
-        self.state.stack.push(clone_id);
+    fn insert_nodes_before(&mut self, id: ElementId, m: usize) {
+        trace!("insert_nodes_before id:{} m:{}", id.0, m);
+        let (anchor_node_id, new_node_ids) = self.state.anchor_and_nodes(id, m);
+        self.docm.insert_nodes_before(anchor_node_id, &new_node_ids);
     }
 
     fn replace_node_with(&mut self, id: ElementId, m: usize) {
-        #[cfg(feature = "tracing")]
-        tracing::info!("replace_node_with id:{} m:{}", id.0, m);
-
-        let new_nodes = self.state.stack.split_off(self.state.stack.len() - m);
-        let anchor_node_id = self.state.element_to_node_id(id);
-        self.maybe_push_parent_style_node(anchor_node_id);
-        self.doc.insert_before(anchor_node_id, &new_nodes);
-        self.doc.remove_node(anchor_node_id);
+        trace!("replace_node_with id:{} m:{}", id.0, m);
+        let (anchor_node_id, new_node_ids) = self.state.anchor_and_nodes(id, m);
+        self.docm.replace_node_with(anchor_node_id, &new_node_ids);
     }
 
     fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize) {
-        #[cfg(feature = "tracing")]
-        tracing::info!("replace_placeholder_with_nodes path:{:?} m:{}", path, m);
-
-        let new_nodes = self.state.stack.split_off(self.state.stack.len() - m);
+        trace!("replace_placeholder_with_nodes path:{:?} m:{}", path, m);
+        // WARNING: DO NOT REORDER
+        // The order of the following two lines is very important as "m_stack_nodes" mutates
+        // the stack and then "load_child" reads from the top of the stack.
+        let new_node_ids = self.state.m_stack_nodes(m);
         let anchor_node_id = self.load_child(path);
-        self.maybe_push_parent_style_node(anchor_node_id);
-        self.doc.insert_before(anchor_node_id, &new_nodes);
-        self.doc.remove_node(anchor_node_id);
+        self.docm
+            .replace_placeholder_with_nodes(anchor_node_id, &new_node_ids);
     }
 
-    fn insert_nodes_after(&mut self, id: ElementId, m: usize) {
-        #[cfg(feature = "tracing")]
-        tracing::info!("insert_nodes_after id:{} m:{}", id.0, m);
-
-        let new_nodes = self.state.stack.split_off(self.state.stack.len() - m);
-        let anchor_node_id = self.state.element_to_node_id(id);
-        let next_sibling_id = self
-            .doc
-            .get_node(anchor_node_id)
-            .unwrap()
-            .forward(1)
-            .map(|node| node.id);
-
-        match next_sibling_id {
-            Some(anchor_node_id) => {
-                self.doc.insert_before(anchor_node_id, &new_nodes);
-            }
-            None => self.doc.append(anchor_node_id, &new_nodes),
-        }
-
-        self.maybe_push_parent_style_node(anchor_node_id);
+    fn remove_node(&mut self, id: ElementId) {
+        trace!("remove_node id:{}", id.0);
+        let node_id = self.state.element_to_node_id(id);
+        self.docm.remove_node(node_id);
     }
 
-    fn insert_nodes_before(&mut self, id: ElementId, m: usize) {
-        #[cfg(feature = "tracing")]
-        tracing::info!("insert_nodes_before id:{} m:{}", id.0, m);
-
-        let new_nodes = self.state.stack.split_off(self.state.stack.len() - m);
-        let anchor_node_id = self.state.element_to_node_id(id);
-        self.doc.insert_before(anchor_node_id, &new_nodes);
+    fn push_root(&mut self, id: ElementId) {
+        trace!("push_root id:{}", id.0);
+        let node_id = self.state.element_to_node_id(id);
+        self.state.stack.push(node_id);
+    }
 
-        self.maybe_push_parent_style_node(anchor_node_id);
+    fn set_node_text(&mut self, value: &str, id: ElementId) {
+        trace!("set_node_text id:{} value:{}", id.0, value);
+        let node_id = self.state.element_to_node_id(id);
+        self.docm.set_node_text(node_id, value);
     }
 
     fn set_attribute(
         &mut self,
-        name: &'static str,
+        local_name: &'static str,
         ns: Option<&'static str>,
         value: &AttributeValue,
         id: ElementId,
     ) {
         let node_id = self.state.element_to_node_id(id);
+        trace!("set_attribute node_id:{node_id} ns: {ns:?} name:{local_name}, value:{value:?}");
 
-        #[cfg(feature = "tracing")]
-        tracing::info!(
-            "set_attribute node_id:{} ns: {:?} name:{}, value:{:?}",
-            node_id,
-            ns,
-            name,
-            value
-        );
-
-        self.doc.snapshot_node(node_id);
-
-        let mut queued_image = None;
-        let mut queued_stylesheet = None;
-
-        {
-            let node = &mut self.doc.nodes[node_id];
+        // Dioxus has overloaded the style namespace to accumulate style attributes without a `style` block
+        // TODO: accumulate style attributes into a single style element.
+        if ns == Some("style") {
+            return;
+        }
 
-            let stylo_element_data = &mut *node.stylo_element_data.borrow_mut();
-            if let Some(data) = stylo_element_data {
-                data.hint |= RestyleHint::restyle_subtree();
+        fn is_falsy(val: &AttributeValue) -> bool {
+            match val {
+                AttributeValue::None => true,
+                AttributeValue::Text(val) => val == "false",
+                AttributeValue::Bool(val) => !val,
+                AttributeValue::Int(val) => *val == 0,
+                AttributeValue::Float(val) => *val == 0.0,
+                _ => false,
             }
+        }
 
-            if let NodeData::Element(ref mut element) = node.data {
-                if element.name.local == local_name!("input") && name == "checked" {
-                    set_input_checked_state(element, value);
+        let name = qual_name(local_name, ns);
+
+        // FIXME: more principled handling of special case attributes
+        if value == &AttributeValue::None || (local_name == "checked" && is_falsy(value)) {
+            self.docm.clear_attribute(node_id, name);
+        } else {
+            match value {
+                AttributeValue::Text(value) => self.docm.set_attribute(node_id, name, value),
+                AttributeValue::Float(value) => {
+                    let value = value.to_string();
+                    self.docm.set_attribute(node_id, name, &value);
                 }
-                // FIXME: support other non-text attributes
-                else if let AttributeValue::Text(val) = value {
-                    if name == "value" {
-                        // Update text input value
-                        if let Some(input_data) = element.text_input_data_mut() {
-                            input_data.set_text(
-                                &mut self.doc.font_ctx,
-                                &mut self.doc.layout_ctx,
-                                val,
-                            );
-                        }
-                    }
-
-                    // FIXME check namespace
-                    let existing_attr = element
-                        .attrs
-                        .iter_mut()
-                        .find(|attr| attr.name.local == *name);
-
-                    if let Some(existing_attr) = existing_attr {
-                        existing_attr.value.clear();
-                        existing_attr.value.push_str(val);
-                    } else {
-                        // we have overloaded the style namespace to accumulate style attributes without a `style` block
-                        if ns == Some("style") {
-                            // todo: need to accumulate style attributes into a single style
-                            //
-                            // element.
-                        } else {
-                            element.attrs.push(Attribute {
-                                name: qual_name(name, ns),
-                                value: val.to_string(),
-                            });
-                        }
-                    }
-
-                    if name == "style" {
-                        element.flush_style_attribute(&self.doc.guard);
-                    }
-
-                    if name == "src" && !val.is_empty() {
-                        element.node_specific_data = NodeSpecificData::None;
-                        queued_image = Some(val.to_string());
-                    }
-
-                    if element.name.local == local_name!("link")
-                        && name == "href"
-                        && !val.is_empty()
-                    {
-                        queued_stylesheet = Some(val.to_string());
-                    }
+                AttributeValue::Int(value) => {
+                    let value = value.to_string();
+                    self.docm.set_attribute(node_id, name, &value);
                 }
-
-                if let AttributeValue::None = value {
-                    // Update text input value
-                    if name == "value" {
-                        if let Some(input_data) = element.text_input_data_mut() {
-                            input_data.set_text(
-                                &mut self.doc.font_ctx,
-                                &mut self.doc.layout_ctx,
-                                "",
-                            );
-                        }
-                    }
-
-                    // FIXME: check namespace
-                    element.attrs.retain(|attr| attr.name.local != *name);
+                AttributeValue::Bool(value) => {
+                    let value = value.to_string();
+                    self.docm.set_attribute(node_id, name, &value);
                 }
-            }
+                _ => {
+                    // FIXME: support all attribute types
+                }
+            };
         }
+    }
 
-        if let Some(queued_image) = queued_image {
-            crate::assets::fetch_image(self.doc, node_id, queued_image);
-        }
+    fn load_template(&mut self, template: Template, index: usize, id: ElementId) {
+        // TODO: proper template node support
+        let template_entry = self.state.templates.entry(template).or_insert_with(|| {
+            let template_root_ids: Vec<NodeId> = template
+                .roots
+                .iter()
+                .map(|root| create_template_node(&mut self.docm, root))
+                .collect();
 
-        if let Some(queued_stylesheet) = queued_stylesheet {
-            crate::assets::fetch_linked_stylesheet(self.doc, node_id, queued_stylesheet);
-        }
-    }
+            template_root_ids
+        });
 
-    fn set_node_text(&mut self, value: &str, id: ElementId) {
-        #[cfg(feature = "tracing")]
-        tracing::info!("set_node_text id:{} value:{}", id.0, value);
+        let template_node_id = template_entry[index];
+        let clone_id = self.docm.deep_clone_node(template_node_id);
 
-        let node_id = self.state.element_to_node_id(id);
-        let node = self.doc.get_node_mut(node_id).unwrap();
-
-        let text = match node.data {
-            NodeData::Text(ref mut text) => text,
-            // todo: otherwise this is basically element.textContent which is a bit different - need to parse as html
-            _ => return,
-        };
-
-        let changed = text.content != value;
-        if changed {
-            text.content.clear();
-            text.content.push_str(value);
-            let parent = node.parent;
-            self.maybe_push_style_node(parent);
-        }
+        trace!("load_template template_node_id:{template_node_id} clone_id:{clone_id}");
+        self.map_new_node(clone_id, id);
     }
 
-    fn create_event_listener(&mut self, _name: &'static str, _id: ElementId) {
-        // we're going to actually set the listener here as a placeholder - in JS this would also be a placeholder
+    fn create_event_listener(&mut self, name: &'static str, id: ElementId) {
+        // We're going to actually set the listener here as a placeholder - in JS this would also be a placeholder
         // we might actually just want to attach the attribute to the root element (delegation)
-        self.set_attribute(
-            _name,
-            None,
-            &AttributeValue::Text("<rust func>".into()),
-            _id,
-        );
-
-        // also set the data-dioxus-id attribute so we can find the element later
-        self.set_attribute(
-            "data-dioxus-id",
-            None,
-            &AttributeValue::Text(_id.0.to_string()),
-            _id,
-        );
-
-        // let node_id = self.state.element_to_node_id(id);
-        // let mut node = self.rdom.get_mut(node_id).unwrap();
+        let value = AttributeValue::Text("<rust func>".into());
+        self.set_attribute(name, None, &value, id);
+
+        // Also set the data-dioxus-id attribute so we can find the element later
+        let value = AttributeValue::Text(id.0.to_string());
+        self.set_attribute("data-dioxus-id", None, &value, id);
+
         // node.add_event_listener(name);
     }
 
     fn remove_event_listener(&mut self, _name: &'static str, _id: ElementId) {
-        // let node_id = self.state.element_to_node_id(id);
-        // let mut node = self.rdom.get_mut(node_id).unwrap();
         // node.remove_event_listener(name);
     }
+}
 
-    fn remove_node(&mut self, id: ElementId) {
-        #[cfg(feature = "tracing")]
-        tracing::info!("remove_node id:{}", id.0);
-
-        let node_id = self.state.element_to_node_id(id);
-        self.doc.remove_node(node_id);
-    }
-
-    fn push_root(&mut self, id: ElementId) {
-        #[cfg(feature = "tracing")]
-        tracing::info!("push_root id:{}", id.0,);
+fn create_template_node(docm: &mut DocumentMutator<'_>, node: &TemplateNode) -> NodeId {
+    match node {
+        TemplateNode::Element {
+            tag,
+            namespace,
+            attrs,
+            children,
+        } => {
+            let name = qual_name(tag, *namespace);
+            let attrs = attrs.iter().filter_map(map_template_attr).collect();
+            let node_id = docm.create_element(name, attrs);
+
+            let child_ids: Vec<NodeId> = children
+                .iter()
+                .map(|child| create_template_node(docm, child))
+                .collect();
 
-        let node_id = self.state.element_to_node_id(id);
-        self.state.stack.push(node_id);
-    }
-}
+            docm.append_children(node_id, &child_ids);
 
-/// Set 'checked' state on an input based on given attributevalue
-fn set_input_checked_state(element: &mut ElementNodeData, value: &AttributeValue) {
-    let checked: bool;
-    match value {
-        AttributeValue::Bool(checked_bool) => {
-            checked = *checked_bool;
-        }
-        AttributeValue::Text(val) => {
-            if let Ok(checked_bool) = val.parse() {
-                checked = checked_bool;
-            } else {
-                return;
-            };
-        }
-        _ => {
-            return;
+            node_id
         }
-    };
-    match element.node_specific_data {
-        NodeSpecificData::CheckboxInput(ref mut checked_mut) => *checked_mut = checked,
-        // If we have just constructed the element, set the node attribute,
-        // and NodeSpecificData will be created from that later
-        // this simulates the checked attribute being set in html,
-        // and the element's checked property being set from that
-        NodeSpecificData::None => element.attrs.push(Attribute {
-            name: QualName {
-                prefix: None,
-                ns: ns!(html),
-                local: local_name!("checked"),
-            },
-            value: checked.to_string(),
-        }),
-        _ => {}
+        TemplateNode::Text { text } => docm.create_text_node(text),
+        TemplateNode::Dynamic { .. } => docm.create_comment_node(),
     }
 }
 
-#[cfg(feature = "autofocus")]
-fn autofocus_node_recursive(
-    doc: &BaseDocument,
-    node_to_autofocus: &mut Option<usize>,
-    node_id: usize,
-) {
-    if let Some(node) = doc.get_node(node_id) {
-        if node.is_focussable() {
-            if let NodeData::Element(ref element) = node.data {
-                if let Some(value) = element.attr(local_name!("autofocus")) {
-                    if value == "true" {
-                        *node_to_autofocus = Some(node_id);
-                    }
-                }
-            }
-        }
+fn map_template_attr(attr: &TemplateAttribute) -> Option<Attribute> {
+    let TemplateAttribute::Static {
+        name,
+        value,
+        namespace,
+    } = attr
+    else {
+        return None;
+    };
 
-        for child_node_id in &node.children {
-            autofocus_node_recursive(doc, node_to_autofocus, *child_node_id);
-        }
-    }
+    let name = qual_name(name, *namespace);
+    let value = value.to_string();
+    Some(Attribute { name, value })
 }