1use blitz_traits::node_id::NodeId;
2use std::collections::HashSet;
3use std::mem;
4use std::ops::{Deref, DerefMut};
5
6use crate::document::make_device;
7use crate::layout::damage::ALL_DAMAGE;
8use crate::net::{ImageHandler, ResourceHandler, StylesheetHandler};
9use crate::node::{CanvasData, NodeFlags, SpecialElementData};
10use crate::util::ImageType;
11use crate::{
12 Attribute, BaseDocument, Document, ElementData, Node, NodeData, QualName, local_name, qual_name,
13};
14use blitz_traits::shell::Viewport;
15use style::Atom;
16use style::invalidation::element::restyle_hints::RestyleHint;
17use style::stylesheets::OriginSet;
18use thin_vec::ThinVec;
19
20macro_rules! tag_and_attr {
21 ($tag:tt, $attr:tt) => {
22 (&local_name!($tag), &local_name!($attr))
23 };
24}
25
26#[derive(Debug, Clone)]
27pub enum AppendTextErr {
28 NotTextNode,
30}
31
32enum SpecialOp {
35 LoadImage(NodeId),
36 LoadIframe(NodeId),
37 LoadStylesheet(NodeId),
38 UnloadStylesheet(NodeId),
39 LoadCustomPaintSource(NodeId),
40 ProcessButtonInput(NodeId),
41 UnloadSubDocument(NodeId),
42 #[cfg(feature = "custom-widget")]
43 UnloadCustomWidget(NodeId),
44}
45
46pub struct DocumentMutator<'doc> {
47 pub doc: &'doc mut BaseDocument,
50
51 eager_op_queue: Vec<SpecialOp>,
52
53 title_node: Option<NodeId>,
55 style_nodes: HashSet<NodeId>,
56 form_nodes: HashSet<NodeId>,
57
58 recompute_is_animating: bool,
60
61 mutations_occurred: bool,
63
64 #[cfg(feature = "autofocus")]
66 node_to_autofocus: Option<NodeId>,
67}
68
69impl Drop for DocumentMutator<'_> {
70 fn drop(&mut self) {
71 self.flush(); if self.mutations_occurred {
73 self.doc.shell_provider.request_redraw();
74 }
75 }
76}
77
78impl DocumentMutator<'_> {
79 pub fn new<'doc>(doc: &'doc mut BaseDocument) -> DocumentMutator<'doc> {
80 DocumentMutator {
81 doc,
82 eager_op_queue: Vec::new(),
83 title_node: None,
84 style_nodes: HashSet::new(),
85 form_nodes: HashSet::new(),
86 recompute_is_animating: false,
87 mutations_occurred: false,
88 #[cfg(feature = "autofocus")]
89 node_to_autofocus: None,
90 }
91 }
92
93 pub fn node_has_parent(&self, node_id: NodeId) -> bool {
96 self.doc.nodes[node_id].parent.is_some()
97 }
98
99 pub fn previous_sibling_id(&self, node_id: NodeId) -> Option<NodeId> {
100 self.doc.nodes[node_id].backward(1).map(|node| node.id)
101 }
102
103 pub fn next_sibling_id(&self, node_id: NodeId) -> Option<NodeId> {
104 self.doc.nodes[node_id].forward(1).map(|node| node.id)
105 }
106
107 pub fn parent_id(&self, node_id: NodeId) -> Option<NodeId> {
108 self.doc.nodes[node_id].parent
109 }
110
111 pub fn last_child_id(&self, node_id: NodeId) -> Option<NodeId> {
112 self.doc.nodes[node_id].children.last().copied()
113 }
114
115 pub fn child_ids(&self, node_id: NodeId) -> ThinVec<NodeId> {
116 self.doc.nodes[node_id].children.clone()
117 }
118
119 pub fn element_name(&self, node_id: NodeId) -> Option<&QualName> {
120 self.doc.nodes[node_id].element_data().map(|el| &el.name)
121 }
122
123 pub fn node_at_path(&self, start_node_id: NodeId, path: &[u8]) -> NodeId {
124 let mut current = &self.doc.nodes[start_node_id];
125 for i in path {
126 let new_id = current.children[*i as usize];
127 current = &self.doc.nodes[new_id];
128 }
129 current.id
130 }
131
132 pub fn create_comment_node(&mut self, contents: &str) -> NodeId {
135 self.doc.create_node(NodeData::Comment {
136 contents: contents.to_string(),
137 })
138 }
139
140 pub fn create_text_node(&mut self, text: &str) -> NodeId {
141 self.doc.create_text_node(text)
142 }
143
144 pub fn create_element(&mut self, name: QualName, attrs: Vec<Attribute>) -> NodeId {
145 let mut data = ElementData::new(name, attrs);
146 data.flush_style_attribute(self.doc.guard(), &self.doc.url.url_extra_data());
147
148 let id = self.doc.create_node(NodeData::Element(Box::new(data)));
149 let node = self.doc.get_node_mut(id).unwrap();
150
151 *node.stylo_element_data_mut().ensure_init_mut() = style::data::ElementData {
153 damage: ALL_DAMAGE,
154 ..Default::default()
155 };
156
157 id
158 }
159
160 pub fn deep_clone_node(&mut self, node_id: NodeId) -> NodeId {
161 self.doc.deep_clone_node(node_id)
162 }
163
164 pub fn set_node_text(&mut self, node_id: NodeId, value: &str) {
167 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
168 let node = &mut self.doc.nodes[node_id];
169
170 let text = match node.data {
171 NodeData::Text(ref mut text) => text,
172 _ => return,
174 };
175
176 let changed = text.content != value;
177 if changed {
178 self.mutations_occurred |= node_is_in_document;
179 text.content.clear();
180 text.content.push_str(value);
181 node.insert_damage(ALL_DAMAGE);
182 node.mark_ancestors_dirty();
185 let parent_id = node.parent;
186
187 if let Some(parent_id) = parent_id {
190 let parent = &mut self.doc.nodes[parent_id];
191 parent.insert_damage(ALL_DAMAGE);
192 }
193
194 self.maybe_record_node(parent_id);
195 }
196 }
197
198 pub fn append_text_to_node(
199 &mut self,
200 node_id: NodeId,
201 text: &str,
202 ) -> Result<(), AppendTextErr> {
203 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
204 let node = &mut self.doc.nodes[node_id];
205 node.insert_damage(ALL_DAMAGE);
206 node.mark_ancestors_dirty();
207 match node.text_data_mut() {
208 Some(data) => {
209 data.content += text;
210 self.mutations_occurred |= node_is_in_document;
211 Ok(())
212 }
213 None => Err(AppendTextErr::NotTextNode),
214 }
215 }
216
217 pub fn add_attrs_if_missing(&mut self, node_id: NodeId, attrs: Vec<Attribute>) {
218 let node = &mut self.doc.nodes[node_id];
219 node.insert_damage(ALL_DAMAGE);
220 let element_data = node.element_data_mut().expect("Not an element");
221
222 let existing_names = element_data
223 .attrs
224 .iter()
225 .map(|e| e.name.clone())
226 .collect::<HashSet<_>>();
227
228 for attr in attrs
229 .into_iter()
230 .filter(|attr| !existing_names.contains(&attr.name))
231 {
232 self.set_attribute(node_id, attr.name, &attr.value);
233 }
234 }
235
236 pub fn set_attribute(&mut self, node_id: NodeId, name: QualName, value: &str) {
237 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
238 if node_is_in_document {
239 self.doc.snapshot_node(node_id);
240
241 let node = &mut self.doc.nodes[node_id];
242 if let Some(mut data) = node.stylo_element_data_opt_mut().and_then(|s| s.get_mut()) {
243 data.hint |= RestyleHint::restyle_subtree();
244 data.damage.insert(ALL_DAMAGE);
245 }
246 node.mark_damaged();
247
248 let parent = node.parent;
250 if let Some(parent_id) = parent {
251 let parent = &mut self.doc.nodes[parent_id];
252 if let Some(mut data) = parent
253 .stylo_element_data_opt_mut()
254 .and_then(|s| s.get_mut())
255 {
256 data.hint |= RestyleHint::restyle_subtree();
257 }
258 }
259
260 self.doc.nodes[node_id].mark_ancestors_dirty();
264 }
265
266 if name.local == local_name!("id") && node_is_in_document {
267 if let Some(old_id) = self.doc.nodes[node_id]
268 .element_data()
269 .map(|element| element.id.clone())
270 {
271 if let Some(old_id) = old_id {
272 self.doc.remove_from_id_map(&old_id, node_id);
273 }
274 self.doc.add_to_id_map(value, node_id);
275 }
276 }
277
278 let node = &mut self.doc.nodes[node_id];
279
280 let NodeData::Element(ref mut element) = node.data else {
281 return;
282 };
283
284 self.mutations_occurred |= node_is_in_document;
285 #[cfg(feature = "custom-widget")]
287 if let SpecialElementData::CustomWidget(widget_data) = &mut element.special_data {
288 let old_value = element.attrs.get(&name).as_ref().map(|attr| &*attr.value);
289 widget_data
290 .widget
291 .attribute_changed(&name.local, old_value, Some(value));
292 }
293
294 element.attrs.set(name.clone(), value);
295
296 if name.local == local_name!("tabindex")
301 || name.local == local_name!("href")
302 || name.local == local_name!("disabled")
303 {
304 element.flush_is_focussable();
305 }
306
307 if name.local == local_name!("href") {
308 element.flush_link_state();
309 }
310
311 let tag = &element.name.local;
312 let attr = &name.local;
313
314 if *attr == local_name!("id") {
315 element.id = Some(Atom::from(value))
316 }
317
318 if *attr == local_name!("value") {
319 if let Some(input_data) = element.text_input_data_mut() {
320 input_data.set_text(
322 &mut self.doc.font_ctx.lock().unwrap(),
323 &mut self.doc.layout_ctx,
324 value,
325 );
326 }
327 return;
328 }
329
330 if *attr == local_name!("style") {
331 element.flush_style_attribute(&self.doc.guard, &self.doc.url.url_extra_data());
332 node.mark_style_attr_updated();
333 return;
334 }
335
336 if *attr == local_name!("disabled") && element.can_be_disabled() {
337 node.disable();
338 return;
339 }
340
341 if !node.flags.is_in_document() {
344 return;
345 }
346
347 if (tag, attr) == tag_and_attr!("input", "checked") {
348 set_input_checked_state(element, value.to_string());
349 } else if (tag, attr) == tag_and_attr!("img", "src") {
350 self.load_image(node_id);
351 } else if (tag, attr) == tag_and_attr!("canvas", "src") {
352 self.load_custom_paint_src(node_id);
353 } else if (tag, attr) == tag_and_attr!("link", "href") {
354 self.load_linked_stylesheet(node_id);
355 } else if (tag, attr) == tag_and_attr!("iframe", "src")
356 || (tag, attr) == tag_and_attr!("iframe", "srcdoc")
357 {
358 self.load_iframe(node_id);
359 }
360 }
361
362 pub fn clear_attribute(&mut self, node_id: NodeId, name: QualName) {
363 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
364 if node_is_in_document {
365 self.doc.snapshot_node(node_id);
366
367 let node = &mut self.doc.nodes[node_id];
368
369 if let Some(mut data) = node.stylo_element_data_opt_mut().and_then(|s| s.get_mut()) {
370 data.hint |= RestyleHint::restyle_subtree();
371 data.damage.insert(ALL_DAMAGE);
372 }
373 node.mark_damaged();
374
375 node.mark_ancestors_dirty();
378 }
379
380 if name.local == local_name!("id") && node_is_in_document {
381 if let Some(old_id) = self.doc.nodes[node_id]
382 .element_data()
383 .and_then(|element| element.id.clone())
384 {
385 self.doc.remove_from_id_map(&old_id, node_id);
386 }
387 }
388
389 let node = &mut self.doc.nodes[node_id];
390
391 let Some(element) = node.element_data_mut() else {
392 return;
393 };
394
395 let removed_attr = element.attrs.remove(&name);
396 let had_attr = removed_attr.is_some();
397 if !had_attr {
398 return;
399 }
400 self.mutations_occurred |= node_is_in_document;
401
402 #[cfg(feature = "custom-widget")]
404 if let SpecialElementData::CustomWidget(widget_data) = &mut element.special_data {
405 let old_value = removed_attr.as_ref().map(|attr| &*attr.value);
406 widget_data
407 .widget
408 .attribute_changed(&name.local, old_value, None);
409 }
410
411 if name.local == local_name!("id") {
412 element.id = None;
413 }
414
415 if name.local == local_name!("tabindex")
418 || name.local == local_name!("href")
419 || name.local == local_name!("disabled")
420 {
421 element.flush_is_focussable();
422 }
423
424 if name.local == local_name!("href") {
425 element.flush_link_state();
426 }
427
428 if name.local == local_name!("value") {
430 if let Some(input_data) = element.text_input_data_mut() {
431 input_data.set_text(
432 &mut self.doc.font_ctx.lock().unwrap(),
433 &mut self.doc.layout_ctx,
434 "",
435 );
436 }
437 }
438
439 let tag = &element.name.local;
440 let attr = &name.local;
441
442 if *attr == local_name!("disabled") && element.can_be_disabled() {
443 node.enable();
444 return;
445 }
446
447 if *attr == local_name!("style") {
448 element.flush_style_attribute(&self.doc.guard, &self.doc.url.url_extra_data());
449 node.mark_style_attr_updated();
450 } else if (tag, attr) == tag_and_attr!("canvas", "src") {
451 self.recompute_is_animating = true;
452 } else if (tag, attr) == tag_and_attr!("link", "href") {
453 self.unload_stylesheet(node_id);
454 } else if (tag, attr) == tag_and_attr!("iframe", "srcdoc") && node_is_in_document {
455 self.load_iframe(node_id);
457 }
458 }
459
460 pub fn set_style_property(&mut self, node_id: NodeId, name: &str, value: &str) {
461 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
462 self.doc.set_style_property(node_id, name, value);
463 self.mutations_occurred |= node_is_in_document;
464 }
465
466 pub fn remove_style_property(&mut self, node_id: NodeId, name: &str) {
467 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
468 self.doc.remove_style_property(node_id, name);
469 self.mutations_occurred |= node_is_in_document;
470 }
471
472 pub fn set_sub_document(&mut self, node_id: NodeId, sub_document: Box<dyn Document>) {
473 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
474 self.doc.set_sub_document(node_id, sub_document);
475 self.mutations_occurred |= node_is_in_document;
476 }
477
478 pub fn remove_sub_document(&mut self, node_id: NodeId) {
479 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
480 self.doc.remove_sub_document(node_id);
481 self.mutations_occurred |= node_is_in_document;
482 }
483
484 #[cfg(feature = "custom-widget")]
485 pub fn set_custom_widget(&mut self, node_id: NodeId, widget: Box<dyn crate::Widget>) {
486 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
487 self.doc.set_custom_widget(node_id, widget);
488 self.mutations_occurred |= node_is_in_document;
489 }
490
491 #[cfg(feature = "custom-widget")]
492 pub fn remove_custom_widget(&mut self, node_id: NodeId) {
493 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
494 self.doc.remove_custom_widget(node_id);
495 self.mutations_occurred |= node_is_in_document;
496 }
497
498 pub fn remove_node(&mut self, node_id: NodeId) {
500 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
501 self.process_removed_subtree(node_id);
505
506 let node = &mut self.doc.nodes[node_id];
507
508 if let Some(parent_id) = node.parent.take() {
510 self.mutations_occurred |= node_is_in_document;
511 let parent = &mut self.doc.nodes[parent_id];
512 parent.insert_damage(ALL_DAMAGE);
513 parent.mark_ancestors_dirty();
515 parent.children.retain(|id| *id != node_id);
516 self.maybe_record_node(parent_id);
517 }
518 }
519
520 pub fn remove_and_drop_node(&mut self, node_id: NodeId) -> Option<Node> {
521 self.remove_and_drop_node_with(node_id, &mut |_| {})
522 }
523
524 pub fn remove_and_drop_node_with(
527 &mut self,
528 node_id: NodeId,
529 on_drop: &mut dyn FnMut(NodeId),
530 ) -> Option<Node> {
531 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
532 self.process_removed_subtree(node_id);
533
534 let node = self.doc.drop_node_ignoring_parent_with(node_id, on_drop);
535 self.mutations_occurred |= node_is_in_document;
536
537 if let Some(parent_id) = node.as_ref().and_then(|node| node.parent) {
539 let parent = &mut self.doc.nodes[parent_id];
540 parent.insert_damage(ALL_DAMAGE);
541 let parent_is_in_doc = parent.flags.is_in_document();
542
543 if parent_is_in_doc {
545 if let Some(mut data) = parent
546 .stylo_element_data_opt_mut()
547 .and_then(|s| s.get_mut())
548 {
549 data.hint |= RestyleHint::restyle_subtree();
550 }
551 parent.mark_ancestors_dirty();
553 }
554
555 parent.children.retain(|id| *id != node_id);
556 self.maybe_record_node(parent_id);
557 }
558
559 node
560 }
561
562 pub fn remove_and_drop_all_children(&mut self, node_id: NodeId) {
563 let parent = &mut self.doc.nodes[node_id];
564 let parent_is_in_doc = parent.flags.is_in_document();
565
566 if parent_is_in_doc {
568 if let Some(mut data) = parent
569 .stylo_element_data_opt_mut()
570 .and_then(|s| s.get_mut())
571 {
572 data.hint |= RestyleHint::restyle_subtree();
573 }
574 parent.mark_ancestors_dirty();
576 }
577
578 let children = mem::take(&mut parent.children);
579 self.mutations_occurred |= parent_is_in_doc && !children.is_empty();
580 for child_id in children {
581 self.process_removed_subtree(child_id);
582 let _ = self.doc.drop_node_ignoring_parent(child_id);
583 }
584 self.maybe_record_node(node_id);
585 }
586
587 pub fn remove_node_if_unparented(&mut self, node_id: NodeId) {
589 self.remove_node_if_unparented_with(node_id, &mut |_| {});
590 }
591
592 pub fn remove_node_if_unparented_with(
595 &mut self,
596 node_id: NodeId,
597 on_drop: &mut dyn FnMut(NodeId),
598 ) {
599 if let Some(node) = self.doc.get_node(node_id) {
600 if node.parent.is_none() {
601 self.remove_and_drop_node_with(node_id, on_drop);
602 }
603 }
604 }
605
606 pub fn append_children(&mut self, parent_id: NodeId, child_ids: &[NodeId]) {
608 self.add_children_to_parent(parent_id, child_ids, &|parent, child_ids| {
609 parent.children.extend_from_slice(child_ids);
610 });
611 }
612
613 pub fn insert_nodes_before(&mut self, anchor_node_id: NodeId, new_node_ids: &[NodeId]) {
614 let parent_id = self.doc.nodes[anchor_node_id].parent.unwrap();
615 self.add_children_to_parent(parent_id, new_node_ids, &|parent, child_ids| {
616 let node_child_idx = parent.index_of_child(anchor_node_id).unwrap();
617 parent
618 .children
619 .splice(node_child_idx..node_child_idx, child_ids.iter().copied());
620 });
621 }
622
623 fn add_children_to_parent(
624 &mut self,
625 parent_id: NodeId,
626 child_ids: &[NodeId],
627 insert_children_fn: &dyn Fn(&mut Node, &[NodeId]),
628 ) {
629 let new_parent_is_in_document = self.doc.nodes[parent_id].flags.is_in_document();
630 self.mutations_occurred |= new_parent_is_in_document && !child_ids.is_empty();
631 for child_id in child_ids.iter().copied() {
638 let child = &mut self.doc.nodes[child_id];
639 let child_was_in_doc = child.flags.is_in_document();
640 self.mutations_occurred |= child_was_in_doc;
641 let Some(old_parent_id) = child.parent.take() else {
642 continue;
643 };
644
645 let old_parent = &mut self.doc.nodes[old_parent_id];
646 old_parent.insert_damage(ALL_DAMAGE);
647
648 if child_was_in_doc {
650 if let Some(mut data) = old_parent
651 .stylo_element_data_opt_mut()
652 .and_then(|s| s.get_mut())
653 {
654 data.hint |= RestyleHint::restyle_subtree();
655 }
656 old_parent.mark_ancestors_dirty();
658 }
659
660 old_parent.children.retain(|id| *id != child_id);
661 self.maybe_record_node(old_parent_id);
662 }
663
664 let new_parent = &mut self.doc.nodes[parent_id];
665 new_parent.insert_damage(ALL_DAMAGE);
666
667 if new_parent_is_in_document {
669 if let Some(mut data) = new_parent
670 .stylo_element_data_opt_mut()
671 .and_then(|s| s.get_mut())
672 {
673 data.hint |= RestyleHint::restyle_subtree();
674 }
675 new_parent.mark_ancestors_dirty();
677 }
678
679 insert_children_fn(new_parent, child_ids);
680
681 for child_id in child_ids.iter().copied() {
682 let child = &mut self.doc.nodes[child_id];
683 let child_was_in_doc = child.flags.is_in_document();
684 child.parent = Some(parent_id);
685
686 if new_parent_is_in_document && !child_was_in_doc {
687 self.process_added_subtree(child_id);
688 } else if !new_parent_is_in_document && child_was_in_doc {
689 self.process_removed_subtree(child_id);
690 }
691 }
692
693 self.maybe_record_node(parent_id);
694 }
695
696 pub fn insert_nodes_after(&mut self, anchor_node_id: NodeId, new_node_ids: &[NodeId]) {
698 match self.next_sibling_id(anchor_node_id) {
699 Some(id) => self.insert_nodes_before(id, new_node_ids),
700 None => {
701 let parent_id = self.parent_id(anchor_node_id).unwrap();
702 self.append_children(parent_id, new_node_ids)
703 }
704 }
705 }
706
707 pub fn reparent_children(&mut self, old_parent_id: NodeId, new_parent_id: NodeId) {
708 let child_ids = std::mem::take(&mut self.doc.nodes[old_parent_id].children);
709 self.maybe_record_node(old_parent_id);
710 self.append_children(new_parent_id, &child_ids);
711 }
712
713 pub fn replace_node_with(&mut self, anchor_node_id: NodeId, new_node_ids: &[NodeId]) {
714 self.insert_nodes_before(anchor_node_id, new_node_ids);
715 self.remove_node(anchor_node_id);
716 }
717
718 fn detach_all(&mut self, node_ids: &[NodeId]) {
730 for node_id in node_ids {
731 if self.node_has_parent(*node_id) {
732 self.remove_node(*node_id);
733 }
734 }
735 }
736
737 fn viable_sibling(
741 &self,
742 anchor_id: NodeId,
743 offset: isize,
744 excluded: &[NodeId],
745 ) -> Option<NodeId> {
746 let node = self.doc.get_node(anchor_id)?;
747 let parent = self.doc.get_node(node.parent?)?;
748 let mut index = parent.index_of_child(anchor_id)?;
749 loop {
750 index = index.checked_add_signed(offset)?;
751 let sibling_id = *parent.children.get(index)?;
752 if !excluded.contains(&sibling_id) {
753 return Some(sibling_id);
754 }
755 }
756 }
757
758 pub fn prepend_nodes(&mut self, parent_id: NodeId, node_ids: &[NodeId]) {
761 self.detach_all(node_ids);
762 match self.child_ids(parent_id).first().copied() {
763 Some(first_child_id) => self.insert_nodes_before(first_child_id, node_ids),
764 None => self.append_children(parent_id, node_ids),
765 }
766 }
767
768 pub fn replace_children(&mut self, parent_id: NodeId, node_ids: &[NodeId]) {
772 self.detach_all(node_ids);
773 for child_id in self.child_ids(parent_id) {
774 self.remove_node(child_id);
775 }
776 self.append_children(parent_id, node_ids);
777 }
778
779 pub fn before_node(&mut self, anchor_id: NodeId, node_ids: &[NodeId]) {
784 let Some(parent_id) = self.parent_id(anchor_id) else {
785 return;
786 };
787 let viable_prev = self.viable_sibling(anchor_id, -1, node_ids);
788 self.detach_all(node_ids);
789 match viable_prev {
790 Some(prev_id) => self.insert_nodes_after(prev_id, node_ids),
791 None => match self.child_ids(parent_id).first().copied() {
792 Some(first_child_id) => self.insert_nodes_before(first_child_id, node_ids),
793 None => self.append_children(parent_id, node_ids),
794 },
795 }
796 }
797
798 pub fn after_node(&mut self, anchor_id: NodeId, node_ids: &[NodeId]) {
803 let Some(parent_id) = self.parent_id(anchor_id) else {
804 return;
805 };
806 let viable_next = self.viable_sibling(anchor_id, 1, node_ids);
807 self.detach_all(node_ids);
808 match viable_next {
809 Some(next_id) => self.insert_nodes_before(next_id, node_ids),
810 None => self.append_children(parent_id, node_ids),
811 }
812 }
813
814 pub fn replace_with_nodes(&mut self, anchor_id: NodeId, node_ids: &[NodeId]) {
820 let Some(parent_id) = self.parent_id(anchor_id) else {
821 return;
822 };
823 let viable_next = self.viable_sibling(anchor_id, 1, node_ids);
824 self.detach_all(node_ids);
825 if self.node_has_parent(anchor_id) {
826 self.replace_node_with(anchor_id, node_ids);
827 } else {
828 match viable_next {
829 Some(next_id) => self.insert_nodes_before(next_id, node_ids),
830 None => self.append_children(parent_id, node_ids),
831 }
832 }
833 }
834}
835
836impl<'doc> DocumentMutator<'doc> {
837 pub fn flush(&mut self) {
838 if self.recompute_is_animating {
839 self.doc.has_canvas = self.doc.compute_has_canvas();
840 }
841
842 if let Some(id) = self.title_node {
843 let title = self.doc.nodes[id].text_content();
844 self.doc.shell_provider.set_window_title(title);
845 }
846
847 for id in self.style_nodes.drain() {
849 self.doc.process_style_element(id);
850 }
851
852 for id in self.form_nodes.drain() {
853 self.doc.reset_form_owner(id);
854 }
855
856 #[cfg(feature = "autofocus")]
857 if let Some(node_id) = self.node_to_autofocus.take() {
858 if self.doc.get_node(node_id).is_some() {
859 self.doc.set_focus_to(node_id);
860 }
861 }
862 }
863
864 pub fn set_inner_html(&mut self, node_id: NodeId, html: &str) {
865 self.remove_and_drop_all_children(node_id);
866 self.doc
867 .html_parser_provider
868 .clone()
869 .parse_inner_html(self, node_id, html);
870 }
871
872 fn flush_eager_ops(&mut self) {
873 let mut ops = mem::take(&mut self.eager_op_queue);
874 for op in ops.drain(0..) {
875 match op {
876 SpecialOp::LoadImage(node_id) => self.load_image(node_id),
877 SpecialOp::LoadIframe(node_id) => self.load_iframe(node_id),
878 SpecialOp::LoadStylesheet(node_id) => self.load_linked_stylesheet(node_id),
879 SpecialOp::UnloadStylesheet(node_id) => self.unload_stylesheet(node_id),
880 SpecialOp::LoadCustomPaintSource(node_id) => self.load_custom_paint_src(node_id),
881 SpecialOp::ProcessButtonInput(node_id) => self.process_button_input(node_id),
882 SpecialOp::UnloadSubDocument(node_id) => self.remove_sub_document(node_id),
883 #[cfg(feature = "custom-widget")]
884 SpecialOp::UnloadCustomWidget(node_id) => self.remove_custom_widget(node_id),
885 }
886 }
887
888 self.eager_op_queue = ops;
890 }
891
892 fn process_added_subtree(&mut self, node_id: NodeId) {
893 self.doc.iter_subtree_mut(node_id, |node_id, doc| {
894 let node = &mut doc.nodes[node_id];
895 node.flags.set(NodeFlags::IS_IN_DOCUMENT, true);
896 node.insert_damage(ALL_DAMAGE);
897
898 if let Some(id_attr) = node.attr(local_name!("id")).map(ToString::to_string) {
900 doc.add_to_id_map(&id_attr, node_id);
901 }
902
903 let node = &mut doc.nodes[node_id];
904 let NodeData::Element(ref mut element) = node.data else {
905 return;
906 };
907
908 let tag = element.name.local.as_ref();
910 match tag {
911 "title" => self.title_node = Some(node_id),
912 "link" => self.eager_op_queue.push(SpecialOp::LoadStylesheet(node_id)),
913 "img" => self.eager_op_queue.push(SpecialOp::LoadImage(node_id)),
914 "iframe" => self.eager_op_queue.push(SpecialOp::LoadIframe(node_id)),
915 "canvas" => self
916 .eager_op_queue
917 .push(SpecialOp::LoadCustomPaintSource(node_id)),
918 "style" => {
919 self.style_nodes.insert(node_id);
920 }
921 "button" | "fieldset" | "input" | "select" | "textarea" | "object" | "output" => {
922 self.eager_op_queue
923 .push(SpecialOp::ProcessButtonInput(node_id));
924 self.form_nodes.insert(node_id);
925 }
926 _ => {}
927 }
928
929 #[cfg(feature = "autofocus")]
930 if node.is_focussable() {
931 if let NodeData::Element(ref element) = node.data {
932 if let Some(value) = element.attr(local_name!("autofocus")) {
933 if value == "true" {
934 self.node_to_autofocus = Some(node_id);
935 }
936 }
937 }
938 }
939 });
940
941 self.flush_eager_ops();
942 }
943
944 fn process_removed_subtree(&mut self, node_id: NodeId) {
945 self.doc.iter_subtree_mut(node_id, |node_id, doc| {
946 doc.nodes[node_id]
947 .flags
948 .set(NodeFlags::IS_IN_DOCUMENT, false);
949
950 doc.clear_interaction_state_for_removed_node(node_id);
954
955 let node = &mut doc.nodes[node_id];
956
957 if doc.text_selection.anchor.node_or_parent == Some(node_id)
960 || doc.text_selection.focus.node_or_parent == Some(node_id)
961 {
962 doc.text_selection.clear();
963 }
964
965 if node.has_snapshot() {
968 let opaque_id = style::dom::TNode::opaque(&&*node);
969 doc.snapshots.remove(&opaque_id);
970 node.set_has_snapshot(false);
971 }
972
973 if let Some(id_attr) = node.attr(local_name!("id")).map(ToString::to_string) {
975 doc.remove_from_id_map(&id_attr, node_id);
976 }
977
978 let node = &mut doc.nodes[node_id];
979 let NodeData::Element(ref mut element) = node.data else {
980 return;
981 };
982
983 match &element.special_data {
984 SpecialElementData::SubDocument(_) => {
985 self.eager_op_queue
986 .push(SpecialOp::UnloadSubDocument(node_id));
987 }
988 #[cfg(feature = "custom-widget")]
989 SpecialElementData::CustomWidget(_) => {
990 self.eager_op_queue
991 .push(SpecialOp::UnloadCustomWidget(node_id));
992 }
993 SpecialElementData::Stylesheet(_) => self
994 .eager_op_queue
995 .push(SpecialOp::UnloadStylesheet(node_id)),
996 SpecialElementData::Image(_) => {}
997 SpecialElementData::Canvas(_) => {
998 self.recompute_is_animating = true;
999 }
1000 SpecialElementData::TableRoot(_) => {}
1001 SpecialElementData::TextInput(_) => {}
1002 SpecialElementData::CheckboxInput(_) => {}
1003 #[cfg(feature = "file-input")]
1004 SpecialElementData::FileInput(_) => {}
1005 SpecialElementData::None => {}
1006 }
1007 });
1008
1009 self.flush_eager_ops();
1010 }
1011
1012 fn maybe_record_node(&mut self, node_id: impl Into<Option<NodeId>>) {
1013 let Some(node_id) = node_id.into() else {
1014 return;
1015 };
1016
1017 let Some(tag_name) = self.doc.nodes[node_id]
1018 .data
1019 .downcast_element()
1020 .map(|elem| &elem.name.local)
1021 else {
1022 return;
1023 };
1024
1025 match tag_name.as_ref() {
1026 "title" => self.title_node = Some(node_id),
1027 "style" => {
1028 self.style_nodes.insert(node_id);
1029 }
1030 _ => {}
1031 }
1032 }
1033
1034 fn load_linked_stylesheet(&mut self, target_id: NodeId) {
1035 let node = &self.doc.nodes[target_id];
1036
1037 let mut is_in_head = false;
1038 let mut parent_id = node.parent;
1039 while let Some(id) = parent_id
1040 && !is_in_head
1041 {
1042 let parent = &self.doc.nodes[id];
1043 is_in_head |= parent.data.is_element_with_tag_name(&local_name!("head"));
1044 parent_id = parent.parent;
1045 }
1046
1047 let rel_attr = node.attr(local_name!("rel"));
1048 let href_attr = node.attr(local_name!("href"));
1049
1050 let (Some(rels), Some(href)) = (rel_attr, href_attr) else {
1051 return;
1052 };
1053 if !rels.split_ascii_whitespace().any(|rel| rel == "stylesheet") {
1054 return;
1055 }
1056
1057 let url = self.doc.resolve_url(href);
1058 let handler = ResourceHandler::new(
1059 self.doc.tx.clone(),
1060 self.doc.id(),
1061 Some(node.id),
1062 self.doc.shell_provider.clone(),
1063 StylesheetHandler {
1064 source_url: url.clone(),
1065 guard: self.doc.guard.clone(),
1066 net_provider: self.doc.net_provider.clone(),
1067 abort_signal: self.doc.abort_signal.clone(),
1068 },
1069 );
1070
1071 if is_in_head && !self.doc.net_provider.is_noop() {
1072 self.doc
1073 .pending_critical_resources
1074 .insert(handler.request_id());
1075 }
1076
1077 self.doc.net_provider.fetch(
1078 self.doc.id(),
1079 self.doc.build_request(url),
1080 Box::new(handler),
1081 );
1082 }
1083
1084 fn unload_stylesheet(&mut self, node_id: NodeId) {
1085 let node = &mut self.doc.nodes[node_id];
1086 let Some(element) = node.element_data_mut() else {
1087 unreachable!();
1088 };
1089 let SpecialElementData::Stylesheet(stylesheet) = element.special_data.take() else {
1090 unreachable!();
1091 };
1092
1093 let guard = self.doc.guard.read();
1094 self.doc.stylist.remove_stylesheet(stylesheet, &guard);
1095 self.doc
1096 .stylist
1097 .force_stylesheet_origins_dirty(OriginSet::all());
1098
1099 self.doc.nodes_to_stylesheet.remove(&node_id);
1100 }
1101
1102 fn load_image(&mut self, target_id: NodeId) {
1103 let node = &self.doc.nodes[target_id];
1104 if let Some(raw_src) = node.attr(local_name!("src")) {
1105 if !raw_src.is_empty() {
1106 let src = self.doc.resolve_url(raw_src);
1107 let src_string = src.as_str();
1108
1109 if let Some(cached_image) = self.doc.image_cache.get(src_string) {
1111 #[cfg(feature = "tracing")]
1112 tracing::info!("Loading image {src_string} from cache");
1113 let node = &mut self.doc.nodes[target_id];
1114 node.element_data_mut().unwrap().special_data =
1115 SpecialElementData::Image(Box::new(cached_image.clone()));
1116 node.cache_mut().clear();
1117 node.insert_damage(ALL_DAMAGE);
1118 return;
1119 }
1120
1121 if let Some(waiting_list) = self.doc.pending_images.get_mut(src_string) {
1123 #[cfg(feature = "tracing")]
1124 tracing::info!("Image {src_string} already pending, queueing node {target_id}");
1125 waiting_list.push((target_id, ImageType::Image));
1126 return;
1127 }
1128
1129 #[cfg(feature = "tracing")]
1131 tracing::info!("Fetching image {src_string}");
1132 self.doc
1133 .pending_images
1134 .insert(src_string.to_string(), vec![(target_id, ImageType::Image)]);
1135
1136 self.doc.net_provider.fetch(
1137 self.doc.id(),
1138 self.doc.build_request(src),
1139 ResourceHandler::boxed(
1140 self.doc.tx.clone(),
1141 self.doc.id(),
1142 None, self.doc.shell_provider.clone(),
1144 ImageHandler::new(ImageType::Image),
1145 ),
1146 );
1147 }
1148 }
1149 }
1150
1151 fn load_iframe(&mut self, target_id: NodeId) {
1152 if self.doc.subdocument_depth >= crate::iframe::MAX_SUBDOCUMENT_DEPTH {
1153 #[cfg(feature = "tracing")]
1154 tracing::warn!(
1155 "Not loading iframe: max sub-document nesting depth ({}) reached",
1156 crate::iframe::MAX_SUBDOCUMENT_DEPTH
1157 );
1158 return;
1159 }
1160
1161 let node = &self.doc.nodes[target_id];
1162 let Some(element) = node.element_data() else {
1163 return;
1164 };
1165
1166 if let Some(srcdoc) = element.attr(local_name!("srcdoc")) {
1168 let srcdoc = srcdoc.to_string();
1169 self.doc.load_iframe_srcdoc(target_id, &srcdoc);
1170 return;
1171 }
1172
1173 let Some(raw_src) = element.attr(local_name!("src")) else {
1174 return;
1175 };
1176 if raw_src.is_empty() {
1177 return;
1178 }
1179 let Some(url) = self.doc.url.resolve_relative(raw_src) else {
1180 #[cfg(feature = "tracing")]
1181 tracing::warn!("Not loading iframe: could not resolve url {raw_src}");
1182 return;
1183 };
1184 self.doc.start_iframe_load(target_id, url);
1185 }
1186
1187 fn load_custom_paint_src(&mut self, target_id: NodeId) {
1188 let node = &mut self.doc.nodes[target_id];
1189 if let Some(raw_src) = node.attr(local_name!("src")) {
1190 if let Ok(custom_paint_source_id) = raw_src.parse::<u64>() {
1191 self.recompute_is_animating = true;
1192 let canvas_data = SpecialElementData::Canvas(CanvasData {
1193 custom_paint_source_id,
1194 });
1195 node.element_data_mut().unwrap().special_data = canvas_data;
1196 }
1197 }
1198 }
1199
1200 fn process_button_input(&mut self, target_id: NodeId) {
1201 let node = &self.doc.nodes[target_id];
1202 let Some(data) = node.element_data() else {
1203 return;
1204 };
1205
1206 let tagname = data.name.local.as_ref();
1207 let type_attr = data.attr(local_name!("type"));
1208 let value = data.attr(local_name!("value"));
1209
1210 if let ("input", Some("button" | "submit" | "reset"), Some(value)) =
1213 (tagname, type_attr, value)
1214 {
1215 let value = value.to_string();
1216 let id = self.create_text_node(&value);
1217 self.append_children(target_id, &[id]);
1218 return;
1219 }
1220 #[cfg(feature = "file-input")]
1221 if let ("input", Some("file")) = (tagname, type_attr) {
1222 let button_id = self.create_element(
1223 qual_name!("button", html),
1224 vec![
1225 Attribute {
1226 name: qual_name!("type", html),
1227 value: "button".to_string(),
1228 },
1229 Attribute {
1230 name: qual_name!("tabindex", html),
1231 value: "-1".to_string(),
1232 },
1233 ],
1234 );
1235 let label_id = self.create_element(qual_name!("label", html), vec![]);
1236 let text_id = self.create_text_node("No File Selected");
1237 let button_text_id = self.create_text_node("Browse");
1238 self.append_children(target_id, &[button_id, label_id]);
1239 self.append_children(label_id, &[text_id]);
1240 self.append_children(button_id, &[button_text_id]);
1241 }
1242 }
1243}
1244
1245fn set_input_checked_state(element: &mut ElementData, value: String) {
1247 let Ok(checked) = value.parse() else {
1248 return;
1249 };
1250 match element.special_data {
1251 SpecialElementData::CheckboxInput(_) => element.set_checkbox_input_checked(checked),
1252 SpecialElementData::None => element.attrs.push(Attribute {
1257 name: qual_name!("checked", html),
1258 value: checked.to_string(),
1259 }),
1260 _ => {}
1261 }
1262}
1263
1264pub struct ViewportMut<'doc> {
1267 doc: &'doc mut BaseDocument,
1268 initial_viewport: Viewport,
1269}
1270impl ViewportMut<'_> {
1271 pub fn new(doc: &mut BaseDocument) -> ViewportMut<'_> {
1272 let initial_viewport = doc.viewport.clone();
1273 ViewportMut {
1274 doc,
1275 initial_viewport,
1276 }
1277 }
1278}
1279impl Deref for ViewportMut<'_> {
1280 type Target = Viewport;
1281
1282 fn deref(&self) -> &Self::Target {
1283 &self.doc.viewport
1284 }
1285}
1286impl DerefMut for ViewportMut<'_> {
1287 fn deref_mut(&mut self) -> &mut Self::Target {
1288 &mut self.doc.viewport
1289 }
1290}
1291impl Drop for ViewportMut<'_> {
1292 fn drop(&mut self) {
1293 if self.doc.viewport == self.initial_viewport {
1294 return;
1295 }
1296
1297 self.doc.set_stylist_device(make_device(
1298 &self.doc.viewport,
1299 self.doc.media_type.clone(),
1300 self.doc.font_ctx.clone(),
1301 ));
1302 self.doc.scroll_viewport_by(0.0, 0.0); let scale_has_changed =
1305 self.doc.viewport().scale_f64() != self.initial_viewport.scale_f64();
1306 if scale_has_changed {
1307 self.doc.invalidate_inline_contexts();
1308 self.doc.shell_provider.request_redraw();
1309 }
1310 }
1311}
1312
1313#[cfg(test)]
1314mod test {
1315 use style::media_queries::MediaType;
1316 use style_dom::ElementState;
1317
1318 use std::sync::{
1319 Arc,
1320 atomic::{AtomicUsize, Ordering},
1321 };
1322
1323 use blitz_traits::shell::{ColorScheme, ShellProvider, Viewport};
1324
1325 use crate::{Attribute, BaseDocument, DocumentConfig, ElementData, NodeData, qual_name};
1326
1327 #[test]
1328 fn media_type_defaults_to_screen() {
1329 let mut document = BaseDocument::new(DocumentConfig::default());
1330 assert_eq!(*document.media_type(), MediaType::screen());
1331 assert_eq!(document.stylist_device().media_type(), MediaType::screen());
1332 }
1333
1334 #[test]
1335 fn media_type_honors_config() {
1336 let mut document = BaseDocument::new(DocumentConfig {
1337 media_type: Some(MediaType::print()),
1338 ..Default::default()
1339 });
1340 assert_eq!(*document.media_type(), MediaType::print());
1341 assert_eq!(document.stylist_device().media_type(), MediaType::print());
1342 }
1343
1344 #[test]
1345 fn set_media_type_updates_stylist_device() {
1346 let mut document = BaseDocument::new(DocumentConfig::default());
1347 assert_eq!(document.stylist_device().media_type(), MediaType::screen());
1348
1349 document.set_media_type(MediaType::print());
1350 assert_eq!(*document.media_type(), MediaType::print());
1351 assert_eq!(document.stylist_device().media_type(), MediaType::print());
1352 }
1353
1354 #[test]
1355 fn mutator_remove_disabled() {
1356 let mut document = BaseDocument::new(DocumentConfig::default());
1357 let id = document.create_node(NodeData::Element(Box::new(ElementData::new(
1358 qual_name!("button"),
1359 vec![Attribute {
1360 name: qual_name!("disabled"),
1361 value: "".into(),
1362 }],
1363 ))));
1364
1365 let node = document.get_node(id).unwrap();
1366 assert!(
1367 node.element_state().contains(ElementState::DISABLED),
1368 "form node is disabled"
1369 );
1370 assert!(
1371 !node.element_state().contains(ElementState::ENABLED),
1372 "form node is not enabled yet"
1373 );
1374
1375 let mut mutator = document.mutate();
1376 mutator.clear_attribute(id, qual_name!("disabled"));
1377 drop(mutator);
1378
1379 let node = document.get_node(id).unwrap();
1380 assert!(
1381 !node.element_state().contains(ElementState::DISABLED),
1382 "form node is no longer disabled"
1383 );
1384 assert!(
1385 node.element_state().contains(ElementState::ENABLED),
1386 "form node is enabled"
1387 );
1388 }
1389
1390 #[test]
1391 fn mutator_set_disabled() {
1392 let mut document = BaseDocument::new(DocumentConfig::default());
1393 let id = document.create_node(NodeData::Element(Box::new(ElementData::new(
1394 qual_name!("button"),
1395 vec![],
1396 ))));
1397
1398 let node = document.get_node(id).unwrap();
1399 assert!(
1400 !node.element_state().contains(ElementState::DISABLED),
1401 "form node is not disabled"
1402 );
1403 assert!(
1404 node.element_state().contains(ElementState::ENABLED),
1405 "form node is enabled"
1406 );
1407
1408 let mut mutator = document.mutate();
1409 mutator.set_attribute(id, qual_name!("disabled"), "");
1410 drop(mutator);
1411
1412 let node = document.get_node(id).unwrap();
1413
1414 assert!(
1415 node.element_state().contains(ElementState::DISABLED),
1416 "form node is disabled"
1417 );
1418 assert!(
1419 !node.element_state().contains(ElementState::ENABLED),
1420 "form node is no longer enabled enabled"
1421 );
1422 }
1423
1424 #[test]
1425 fn mutator_set_disabled_invalid_node() {
1426 let mut document = BaseDocument::new(DocumentConfig::default());
1427 let id = document.create_node(NodeData::Element(Box::new(ElementData::new(
1428 qual_name!("a"),
1429 vec![],
1430 ))));
1431
1432 let node = document.get_node(id).unwrap();
1433 assert!(
1434 !node.element_state().contains(ElementState::DISABLED),
1435 "form node is not disabled"
1436 );
1437 assert!(
1438 !node.element_state().contains(ElementState::ENABLED),
1439 "form node is enabled"
1440 );
1441
1442 let mut mutator = document.mutate();
1443 mutator.set_attribute(id, qual_name!("disabled"), "");
1444 drop(mutator);
1445
1446 let node = document.get_node(id).unwrap();
1447 assert!(
1448 !node.element_state().contains(ElementState::DISABLED),
1449 "form node is not disabled"
1450 );
1451 assert!(
1452 !node.element_state().contains(ElementState::ENABLED),
1453 "form node is enabled"
1454 );
1455 }
1456
1457 #[test]
1458 fn mutator_id_attribute_updates_id_map() {
1459 let mut document = BaseDocument::new(DocumentConfig::default());
1460 let root_id = document.root_node().id;
1461
1462 let node_id = {
1463 let mut mutator = document.mutate();
1464 let node_id = mutator.create_element(
1465 qual_name!("div"),
1466 vec![Attribute {
1467 name: qual_name!("id"),
1468 value: "old".into(),
1469 }],
1470 );
1471 mutator.append_children(root_id, &[node_id]);
1472 node_id
1473 };
1474 assert_eq!(document.get_element_by_id("old"), Some(node_id));
1475
1476 {
1477 let mut mutator = document.mutate();
1478 mutator.set_attribute(node_id, qual_name!("id"), "new");
1479 }
1480 assert_eq!(document.get_element_by_id("new"), Some(node_id));
1481 assert_eq!(document.get_element_by_id("old"), None);
1482
1483 {
1484 let mut mutator = document.mutate();
1485 mutator.clear_attribute(node_id, qual_name!("id"));
1486 }
1487 assert_eq!(document.get_element_by_id("new"), None);
1488 }
1489
1490 #[test]
1491 fn get_element_by_id_duplicate_ids_first_in_tree_order_wins() {
1492 let mut document = BaseDocument::new(DocumentConfig::default());
1493 let root_id = document.root_node().id;
1494
1495 let (first_id, second_id) = {
1496 let mut mutator = document.mutate();
1497 let first_id = mutator.create_element(qual_name!("div"), vec![]);
1498 let second_id = mutator.create_element(qual_name!("div"), vec![]);
1499 mutator.append_children(root_id, &[first_id, second_id]);
1500 mutator.set_attribute(second_id, qual_name!("id"), "dup");
1503 mutator.set_attribute(first_id, qual_name!("id"), "dup");
1504 (first_id, second_id)
1505 };
1506 assert_eq!(document.get_element_by_id("dup"), Some(first_id));
1507
1508 {
1509 let mut mutator = document.mutate();
1510 mutator.remove_node(first_id);
1511 }
1512 assert_eq!(document.get_element_by_id("dup"), Some(second_id));
1513 }
1514
1515 #[derive(Default)]
1516 struct RedrawShell {
1517 redraw_requests: AtomicUsize,
1518 }
1519
1520 impl ShellProvider for RedrawShell {
1521 fn request_redraw(&self) {
1522 self.redraw_requests.fetch_add(1, Ordering::Relaxed);
1523 }
1524 }
1525
1526 #[test]
1527 fn mutator_requests_redraw_only_after_mutation() {
1528 let shell = Arc::new(RedrawShell::default());
1529 let mut document = BaseDocument::new(DocumentConfig {
1530 shell_provider: Some(shell.clone()),
1531 ..Default::default()
1532 });
1533 let root_id = document.root_node().id;
1534
1535 {
1536 let mut mutator = document.mutate();
1537 let parent_id = mutator.create_element(qual_name!("div"), vec![]);
1538 let child_id = mutator.create_element(qual_name!("span"), vec![]);
1539 mutator.append_children(parent_id, &[child_id]);
1540 mutator.remove_and_drop_all_children(parent_id);
1541 mutator.set_attribute(parent_id, qual_name!("id"), "detached");
1542 }
1543 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 0);
1544
1545 {
1546 let mutator = document.mutate();
1547 assert_eq!(mutator.child_ids(root_id).len(), 0);
1548 }
1549
1550 {
1551 let mut mutator = document.mutate();
1552 let node_id = mutator.create_element(qual_name!("div"), vec![]);
1553 mutator.append_children(root_id, &[node_id]);
1554 mutator.set_attribute(node_id, qual_name!("id"), "in-document");
1555 }
1556 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 1);
1557
1558 {
1559 let mut mutator = document.mutate();
1560 let parent_id = mutator.create_element(qual_name!("div"), vec![]);
1561 let child_id = mutator.create_element(qual_name!("span"), vec![]);
1562 mutator.append_children(root_id, &[parent_id]);
1563 mutator.append_children(parent_id, &[child_id]);
1564 mutator.remove_and_drop_all_children(parent_id);
1565 }
1566 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 2);
1567
1568 {
1569 let mut mutator = document.mutate();
1570 let parent_id = mutator.create_element(qual_name!("div"), vec![]);
1571 let child_id = mutator.create_element(qual_name!("span"), vec![]);
1572 let detached_target_id = mutator.create_element(qual_name!("div"), vec![]);
1573 mutator.append_children(root_id, &[parent_id]);
1574 mutator.append_children(parent_id, &[child_id]);
1575 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 2);
1576 mutator.append_children(detached_target_id, &[child_id]);
1577 }
1578 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 3);
1579 }
1580
1581 #[test]
1582 fn moving_subtree_out_of_document_clears_in_document_flag() {
1583 let shell = Arc::new(RedrawShell::default());
1584 let mut document = BaseDocument::new(DocumentConfig {
1585 shell_provider: Some(shell.clone()),
1586 ..Default::default()
1587 });
1588 let root_id = document.root_node().id;
1589 let (child_id, grandchild_id, detached_parent_id) = {
1590 let mut mutator = document.mutate();
1591 let in_document_parent_id = mutator.create_element(qual_name!("div"), vec![]);
1592 let child_id = mutator.create_element(qual_name!("div"), vec![]);
1593 let grandchild_id = mutator.create_element(qual_name!("span"), vec![]);
1594 let detached_parent_id = mutator.create_element(qual_name!("section"), vec![]);
1595 mutator.append_children(root_id, &[in_document_parent_id]);
1596 mutator.append_children(in_document_parent_id, &[child_id]);
1597 mutator.append_children(child_id, &[grandchild_id]);
1598 (child_id, grandchild_id, detached_parent_id)
1599 };
1600 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 1);
1601 assert!(document.get_node(child_id).unwrap().flags.is_in_document());
1602 assert!(
1603 document
1604 .get_node(grandchild_id)
1605 .unwrap()
1606 .flags
1607 .is_in_document()
1608 );
1609
1610 {
1611 let mut mutator = document.mutate();
1612 mutator.append_children(detached_parent_id, &[child_id]);
1613 }
1614 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 2);
1615 assert!(!document.get_node(child_id).unwrap().flags.is_in_document());
1616 assert!(
1617 !document
1618 .get_node(grandchild_id)
1619 .unwrap()
1620 .flags
1621 .is_in_document()
1622 );
1623
1624 {
1625 let mut mutator = document.mutate();
1626 mutator.set_attribute(child_id, qual_name!("id"), "detached");
1627 }
1628 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 2);
1629
1630 {
1631 let mut mutator = document.mutate();
1632 mutator.append_children(root_id, &[child_id]);
1633 }
1634 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 3);
1635 assert!(document.get_node(child_id).unwrap().flags.is_in_document());
1636 assert!(
1637 document
1638 .get_node(grandchild_id)
1639 .unwrap()
1640 .flags
1641 .is_in_document()
1642 );
1643 }
1644
1645 #[test]
1646 fn style_property_updates_nested_layout() {
1647 let mut document = BaseDocument::new(DocumentConfig {
1648 viewport: Some(Viewport::new(800, 600, 1.0, ColorScheme::Light)),
1649 ..Default::default()
1650 });
1651 let root_id = document.root_node().id;
1652
1653 let mover_id = {
1654 let mut mutator = document.mutate();
1655 let parent_id = mutator.create_element(qual_name!("div"), vec![]);
1656 let mover_id = mutator.create_element(qual_name!("div"), vec![]);
1657 mutator.set_style_property(parent_id, "position", "relative");
1658 mutator.set_style_property(parent_id, "width", "800px");
1659 mutator.set_style_property(parent_id, "height", "600px");
1660 mutator.set_style_property(mover_id, "position", "absolute");
1661 mutator.set_style_property(mover_id, "left", "0px");
1662 mutator.set_style_property(mover_id, "top", "0px");
1663 mutator.append_children(parent_id, &[mover_id]);
1664 mutator.append_children(root_id, &[parent_id]);
1665 mover_id
1666 };
1667
1668 document.resolve(0.0);
1669 assert_eq!(
1670 document
1671 .get_node(mover_id)
1672 .unwrap()
1673 .final_layout()
1674 .location
1675 .x,
1676 0.0
1677 );
1678
1679 {
1680 let mut mutator = document.mutate();
1681 mutator.set_style_property(mover_id, "left", "120px");
1682 }
1683
1684 document.resolve(0.0);
1685 assert_eq!(
1686 document
1687 .get_node(mover_id)
1688 .unwrap()
1689 .final_layout()
1690 .location
1691 .x,
1692 120.0
1693 );
1694 }
1695}