1use crate::NodeId;
3use crate::events::{
4 BlitzKeyboardData, DocumentCommandQueue, NativeConverter, NativeFocusData, NativeFormData,
5 NativePointerData, NativeScrollData, NativeTouchData, NativeWheelData, NodeHandle,
6};
7use crate::mutation_writer::{DioxusState, MutationWriter};
8use crate::qual_name;
9use blitz_dom::{
10 Attribute, BaseDocument, DEFAULT_CSS, DocGuard, DocGuardMut, Document, DocumentConfig,
11 EventDriver, EventHandler, Node,
12};
13use blitz_traits::events::{DomEvent, DomEventData, EventState, UiEvent};
14use dioxus_core::{ElementId, Event, VirtualDom};
15use dioxus_html::{PlatformEventData, set_event_converter};
16use futures_util::task::noop_waker;
17use std::cell::RefCell;
18use std::future::Future;
19use std::mem;
20use std::pin::pin;
21use std::sync::LazyLock;
22use std::task::{Context as TaskContext, Waker};
23use std::{any::Any, rc::Rc};
24
25fn wrap_event_data<T: Any>(value: T) -> Rc<dyn Any> {
26 Rc::new(PlatformEventData::new(Box::new(value)))
27}
28
29fn get_dioxus_id(node: &Node) -> Option<ElementId> {
31 node.element_data()?
32 .attrs
33 .iter()
34 .find(|attr| *attr.name.local == *"data-dioxus-id")
35 .and_then(|attr| attr.value.parse::<usize>().ok())
36 .map(ElementId)
37}
38
39pub struct DioxusDocument {
68 pub inner: Rc<RefCell<BaseDocument>>,
69 pub vdom: VirtualDom,
70 pub vdom_state: DioxusState,
71 command_queue: DocumentCommandQueue,
72
73 #[allow(unused)]
74 pub(crate) html_element_id: NodeId,
75 #[allow(unused)]
76 pub(crate) head_element_id: NodeId,
77 #[allow(unused)]
78 pub(crate) body_element_id: NodeId,
79 #[allow(unused)]
80 pub(crate) main_element_id: NodeId,
81}
82
83impl DioxusDocument {
84 pub fn new(vdom: VirtualDom, mut config: DocumentConfig) -> Self {
86 set_event_converter(Box::new(NativeConverter {}));
88
89 config.base_url = Some(
90 config
91 .base_url
92 .unwrap_or_else(|| String::from("dioxus://index.html")),
93 );
94 let mut doc = BaseDocument::new(config);
95
96 doc.add_user_agent_stylesheet(DEFAULT_CSS);
98
99 let mut mutr = doc.mutate();
113 let html_element_id = mutr.create_element(qual_name("html", None), vec![]);
114 mutr.append_children(mutr.doc.root_node().id, &[html_element_id]);
115
116 let head_element_id = mutr.create_element(qual_name("head", None), vec![]);
118 mutr.append_children(html_element_id, &[head_element_id]);
119
120 let body_element_id = mutr.create_element(qual_name("body", None), vec![]);
122 mutr.append_children(html_element_id, &[body_element_id]);
123
124 let main_attr = blitz_dom::Attribute {
126 name: qual_name("id", None),
127 value: "main".into(),
128 };
129 let main_element_id = mutr.create_element(qual_name("main", None), vec![main_attr]);
130 mutr.append_children(body_element_id, &[main_element_id]);
131
132 drop(mutr);
133
134 let vdom_state = DioxusState::create(main_element_id);
135 Self {
136 vdom,
137 vdom_state,
138 inner: Rc::new(RefCell::new(doc)),
139 command_queue: Rc::new(RefCell::new(Vec::new())),
140 html_element_id,
141 head_element_id,
142 body_element_id,
143 main_element_id,
144 }
145 }
146
147 pub fn initial_build(&mut self) {
149 let mut inner = self.inner.borrow_mut();
150 let mut writer = MutationWriter::new(&mut inner, &mut self.vdom_state);
151 self.vdom.rebuild(&mut writer);
152 drop(writer);
153 drop(inner);
154 self.flush_queued_mounted_events();
155 }
156
157 #[doc(hidden)]
160 pub fn create_head_element(
161 &mut self,
162 name: &str,
163 attributes: &[(String, String)],
164 contents: &Option<String>,
165 ) {
166 let mut inner = self.inner.borrow_mut();
167 let mut mutr = inner.mutate();
168
169 let attributes = attributes
170 .iter()
171 .map(|(name, value)| Attribute {
172 name: qual_name(name, None),
173 value: value.as_str().into(),
174 })
175 .collect();
176
177 let new_elem_id = mutr.create_element(qual_name(name, None), attributes);
178 mutr.append_children(self.head_element_id, &[new_elem_id]);
179 if let Some(contents) = contents {
180 let text_node_id = mutr.create_text_node(contents);
181 mutr.append_children(new_elem_id, &[text_node_id]);
182 }
183 }
184
185 pub(crate) fn flush_queued_mounted_events(&mut self) {
186 let mut queued_mounted_events = mem::take(&mut self.vdom_state.queued_mounted_events);
187 for element_id in queued_mounted_events.drain(..) {
188 let node_id = self.vdom_state.element_to_node_id(element_id);
189
190 if self.inner.borrow().get_node(node_id).is_some() {
191 let event = Event::new(
192 Rc::new(PlatformEventData::new(Box::new(NodeHandle {
193 doc: Rc::clone(&self.inner),
194 command_queue: Rc::clone(&self.command_queue),
195 node_id,
196 }))) as Rc<dyn Any>,
197 false,
198 );
199 self.vdom
200 .runtime()
201 .handle_event("mounted", event, element_id);
202 }
203 }
204
205 self.vdom_state.queued_mounted_events = queued_mounted_events;
206 }
207}
208
209impl Document for DioxusDocument {
211 fn id(&self) -> usize {
212 self.inner.borrow().id()
213 }
214
215 fn inner(&self) -> DocGuard<'_> {
216 DocGuard::RefCell(self.inner.borrow())
217 }
218
219 fn inner_mut(&mut self) -> DocGuardMut<'_> {
220 DocGuardMut::RefCell(self.inner.borrow_mut())
221 }
222
223 fn poll(&mut self, cx: Option<TaskContext>) -> bool {
224 static NOOP_WAKER: LazyLock<Waker> = LazyLock::new(noop_waker);
225 let waker = cx
226 .as_ref()
227 .map(|cx| cx.waker().clone())
228 .unwrap_or_else(|| NOOP_WAKER.clone());
229
230 let subdoc_changes = self.inner.borrow_mut().poll_subdocuments(Some(&waker));
234
235 {
236 let fut = self.vdom.wait_for_work();
237 let mut pinned_fut = pin!(fut);
238
239 let mut cx = TaskContext::from_waker(&waker);
240 let ready = pinned_fut.as_mut().poll(&mut cx);
241 crate::events::flush_document_commands(&self.inner, &self.command_queue);
249 match ready {
250 std::task::Poll::Ready(_) => {}
251 std::task::Poll::Pending => return subdoc_changes,
252 }
253 }
254
255 let mut inner = self.inner.borrow_mut();
256 let mut writer = MutationWriter::new(&mut inner, &mut self.vdom_state);
257 self.vdom.render_immediate(&mut writer);
258 drop(writer);
259 drop(inner);
260 self.flush_queued_mounted_events();
261 crate::events::flush_document_commands(&self.inner, &self.command_queue);
264
265 true
266 }
267
268 fn handle_ui_event(&mut self, event: UiEvent) {
269 let handler = DioxusEventHandler {
270 vdom: &mut self.vdom,
271 vdom_state: &mut self.vdom_state,
272 };
273 let mut driver = EventDriver::new(&mut self.inner, handler);
274 driver.handle_ui_event(event);
275 }
276}
277
278pub struct DioxusEventHandler<'v> {
279 vdom: &'v mut VirtualDom,
280 vdom_state: &'v mut DioxusState,
281}
282
283impl EventHandler for DioxusEventHandler<'_> {
284 fn handle_event(
285 &mut self,
286 chain: &[NodeId],
287 event: &mut DomEvent,
288 doc: &mut dyn Document,
289 event_state: &mut EventState,
290 ) {
291 let event_kind_idx = event.data.discriminant() as usize;
294 let event_kind_count = self.vdom_state.event_handler_counts[event_kind_idx];
295 if event_kind_count == 0 {
296 return;
297 }
298
299 let event_data = match &event.data {
300 DomEventData::PointerMove(mevent)
301 | DomEventData::PointerDown(mevent)
302 | DomEventData::PointerUp(mevent)
303 | DomEventData::PointerCancel(mevent)
304 | DomEventData::PointerLeave(mevent)
305 | DomEventData::PointerEnter(mevent)
306 | DomEventData::PointerOver(mevent)
307 | DomEventData::PointerOut(mevent)
308 | DomEventData::MouseMove(mevent)
309 | DomEventData::MouseDown(mevent)
310 | DomEventData::MouseUp(mevent)
311 | DomEventData::MouseLeave(mevent)
312 | DomEventData::MouseEnter(mevent)
313 | DomEventData::MouseOver(mevent)
314 | DomEventData::MouseOut(mevent)
315 | DomEventData::Click(mevent)
316 | DomEventData::ContextMenu(mevent)
317 | DomEventData::DoubleClick(mevent) => {
318 Some(wrap_event_data(NativePointerData(mevent.clone())))
319 }
320
321 DomEventData::TouchStart(tevent)
322 | DomEventData::TouchMove(tevent)
323 | DomEventData::TouchEnd(tevent)
324 | DomEventData::TouchCancel(tevent) => {
325 Some(wrap_event_data(NativeTouchData(tevent.clone())))
326 }
327
328 DomEventData::Scroll(sevent) => Some(wrap_event_data(NativeScrollData(sevent.clone()))),
329 DomEventData::Wheel(wevent) => Some(wrap_event_data(NativeWheelData(wevent.clone()))),
330
331 DomEventData::Focus(_) => Some(wrap_event_data(NativeFocusData)),
332 DomEventData::Blur(_) => Some(wrap_event_data(NativeFocusData)),
333 DomEventData::FocusIn(_) => Some(wrap_event_data(NativeFocusData)),
334 DomEventData::FocusOut(_) => Some(wrap_event_data(NativeFocusData)),
335
336 DomEventData::KeyDown(kevent)
337 | DomEventData::KeyUp(kevent)
338 | DomEventData::KeyPress(kevent) => {
339 Some(wrap_event_data(BlitzKeyboardData(kevent.clone())))
340 }
341
342 DomEventData::Input(data) => Some(wrap_event_data(NativeFormData {
343 value: data.value.clone(),
344 values: vec![],
345 })),
346
347 DomEventData::Ime(_) => None,
349
350 DomEventData::AppleStandardKeybinding(_) => None,
352 DomEventData::Submit(_) => None,
356 };
357
358 let Some(event_data) = event_data else {
359 return;
360 };
361
362 let dioxus_id = chain
364 .iter()
365 .find_map(|node_id| doc.inner().get_node(*node_id).and_then(get_dioxus_id));
366 let Some(id) = dioxus_id else {
367 return;
368 };
369
370 let dx_event = Event::new(event_data.clone(), event.bubbles);
372 self.vdom
373 .runtime()
374 .handle_event(event.name(), dx_event.clone(), id);
375
376 if !dx_event.default_action_enabled() {
378 event_state.prevent_default();
379 }
380 if !dx_event.propagates() {
381 event_state.stop_propagation();
382 }
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389 use blitz_dom::DocumentConfig;
390 use dioxus::prelude::*;
391 use dioxus_core::ScopeId;
392 use std::cell::RefCell;
393 use std::collections::HashMap;
394
395 #[test]
396 fn keyed_nodes_do_not_crash() {
399 type SharedData = Rc<RefCell<HashMap<usize, usize>>>;
400 let data: SharedData = Rc::new(RefCell::new(HashMap::new()));
401
402 fn app(data: SharedData) -> Element {
403 let entries: Vec<usize> = data.borrow().keys().copied().collect();
404 rsx!(
405 for id in entries {
406 div {
407 key: "item_{id}",
408 "{id}"
409 }
410 }
411 )
412 }
413
414 let vdom = VirtualDom::new_with_props(app, Rc::clone(&data));
415 let mut doc = DioxusDocument::new(vdom, DocumentConfig::default());
416 doc.initial_build();
417
418 for i in 0..100 {
421 data.borrow_mut().insert(i, i);
422 doc.vdom.mark_dirty(ScopeId::APP);
423 doc.poll(None);
424 }
425
426 let inner = doc.inner.borrow();
429 let main = inner.get_node(doc.main_element_id).unwrap();
430 assert_eq!(main.children.len(), 100);
431 }
432}