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 markup5ever::ns;
16use selectors::matching::ElementSelectorFlags;
17use style::Atom;
18use style::invalidation::element::restyle_hints::RestyleHint;
19use style::stylesheets::OriginSet;
20use thin_vec::ThinVec;
21
22macro_rules! tag_and_attr {
23 ($tag:tt, $attr:tt) => {
24 (&local_name!($tag), &local_name!($attr))
25 };
26}
27
28#[derive(Debug, Clone)]
29pub enum AppendTextErr {
30 NotTextNode,
32}
33
34enum SpecialOp {
37 LoadImage(NodeId),
38 LoadIframe(NodeId),
39 LoadStylesheet(NodeId),
40 UnloadStylesheet(NodeId),
41 LoadCustomPaintSource(NodeId),
42 ProcessButtonInput(NodeId),
43 UnloadSubDocument(NodeId),
44 #[cfg(feature = "custom-widget")]
45 UnloadCustomWidget(NodeId),
46 #[cfg(feature = "shadow-dom")]
47 UpgradeCustomElement(NodeId),
48 #[cfg(feature = "shadow-dom")]
49 DisconnectCustomElement(NodeId),
50}
51
52pub struct DocumentMutator<'doc> {
53 pub doc: &'doc mut BaseDocument,
56
57 eager_op_queue: Vec<SpecialOp>,
58
59 title_node: Option<NodeId>,
61 style_nodes: HashSet<NodeId>,
62 form_nodes: HashSet<NodeId>,
63
64 recompute_is_animating: bool,
66
67 mutations_occurred: bool,
69
70 #[cfg(feature = "shadow-dom")]
73 custom_element_attr_changes: Vec<(NodeId, QualName, Option<String>, Option<String>)>,
74
75 #[cfg(feature = "autofocus")]
77 node_to_autofocus: Option<NodeId>,
78}
79
80impl Drop for DocumentMutator<'_> {
81 fn drop(&mut self) {
82 self.flush(); if self.mutations_occurred {
84 self.doc.shell_provider.request_redraw();
85 }
86 }
87}
88
89impl DocumentMutator<'_> {
90 pub fn new<'doc>(doc: &'doc mut BaseDocument) -> DocumentMutator<'doc> {
91 DocumentMutator {
92 doc,
93 eager_op_queue: Vec::new(),
94 title_node: None,
95 style_nodes: HashSet::new(),
96 form_nodes: HashSet::new(),
97 recompute_is_animating: false,
98 mutations_occurred: false,
99 #[cfg(feature = "shadow-dom")]
100 custom_element_attr_changes: Vec::new(),
101 #[cfg(feature = "autofocus")]
102 node_to_autofocus: None,
103 }
104 }
105
106 pub fn node_has_parent(&self, node_id: NodeId) -> bool {
109 self.doc.nodes[node_id].parent.is_some()
110 }
111
112 pub fn previous_sibling_id(&self, node_id: NodeId) -> Option<NodeId> {
113 self.doc.nodes[node_id].backward(1).map(|node| node.id)
114 }
115
116 pub fn next_sibling_id(&self, node_id: NodeId) -> Option<NodeId> {
117 self.doc.nodes[node_id].forward(1).map(|node| node.id)
118 }
119
120 pub fn parent_id(&self, node_id: NodeId) -> Option<NodeId> {
121 self.doc.nodes[node_id].parent
122 }
123
124 pub fn last_child_id(&self, node_id: NodeId) -> Option<NodeId> {
125 self.doc.nodes[node_id].children.last().copied()
126 }
127
128 pub fn child_ids(&self, node_id: NodeId) -> ThinVec<NodeId> {
129 self.doc.nodes[node_id].children.clone()
130 }
131
132 pub fn element_name(&self, node_id: NodeId) -> Option<&QualName> {
133 self.doc.nodes[node_id].element_data().map(|el| &el.name)
134 }
135
136 pub fn node_at_path(&self, start_node_id: NodeId, path: &[u8]) -> NodeId {
137 let mut current = &self.doc.nodes[start_node_id];
138 for i in path {
139 let new_id = current.children[*i as usize];
140 current = &self.doc.nodes[new_id];
141 }
142 current.id
143 }
144
145 pub fn create_comment_node(&mut self, contents: &str) -> NodeId {
148 self.doc.create_node(NodeData::Comment {
149 contents: contents.to_string(),
150 })
151 }
152
153 pub fn create_text_node(&mut self, text: &str) -> NodeId {
154 self.doc.create_text_node(text)
155 }
156
157 pub fn create_document_fragment(&mut self) -> NodeId {
166 self.doc.create_node(NodeData::DocumentFragment)
167 }
168
169 pub fn create_html_document(&mut self, title: &str) -> NodeId {
182 let doc_id = self.doc.create_node(NodeData::Document(Box::default()));
183
184 let html = self.create_element(html_tag("html"), Vec::new());
185 let head = self.create_element(html_tag("head"), Vec::new());
186 let title_el = self.create_element(html_tag("title"), Vec::new());
187 let body = self.create_element(html_tag("body"), Vec::new());
188
189 if !title.is_empty() {
192 let text = self.create_text_node(title);
193 self.append_children(title_el, &[text]);
194 }
195
196 self.append_children(head, &[title_el]);
197 self.append_children(html, &[head, body]);
198 self.append_children(doc_id, &[html]);
199
200 doc_id
201 }
202
203 pub fn create_element(&mut self, name: QualName, attrs: Vec<Attribute>) -> NodeId {
204 let mut data = ElementData::new(name, attrs);
205 data.flush_style_attribute(self.doc.guard(), &self.doc.url.url_extra_data());
206
207 let id = self.doc.create_node(NodeData::Element(Box::new(data)));
208 let node = self.doc.get_node_mut(id).unwrap();
209
210 *node.stylo_element_data_mut().ensure_init_mut() = style::data::ElementData {
212 damage: ALL_DAMAGE,
213 ..Default::default()
214 };
215
216 id
217 }
218
219 pub fn deep_clone_node(&mut self, node_id: NodeId) -> NodeId {
220 self.doc.deep_clone_node(node_id)
221 }
222
223 pub fn set_node_text(&mut self, node_id: NodeId, value: &str) {
226 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
227 let node = &mut self.doc.nodes[node_id];
228
229 if let NodeData::Comment { ref mut contents } = node.data {
240 if contents != value {
241 contents.clear();
242 contents.push_str(value);
243 }
244 return;
245 }
246
247 let text = match node.data {
248 NodeData::Text(ref mut text) => text,
249 _ => return,
251 };
252
253 let changed = text.content != value;
254 if changed {
255 self.mutations_occurred |= node_is_in_document;
256 text.content.clear();
257 text.content.push_str(value);
258 node.insert_damage(ALL_DAMAGE);
259 node.mark_ancestors_dirty();
262 let parent_id = node.parent;
263
264 if let Some(parent_id) = parent_id {
267 let parent = &mut self.doc.nodes[parent_id];
268 parent.insert_damage(ALL_DAMAGE);
269 }
270
271 self.maybe_record_node(parent_id);
272 }
273 }
274
275 pub fn append_text_to_node(
276 &mut self,
277 node_id: NodeId,
278 text: &str,
279 ) -> Result<(), AppendTextErr> {
280 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
281 let node = &mut self.doc.nodes[node_id];
282 node.insert_damage(ALL_DAMAGE);
283 node.mark_ancestors_dirty();
284 match node.text_data_mut() {
285 Some(data) => {
286 data.content += text;
287 self.mutations_occurred |= node_is_in_document;
288 Ok(())
289 }
290 None => Err(AppendTextErr::NotTextNode),
291 }
292 }
293
294 pub fn add_attrs_if_missing(&mut self, node_id: NodeId, attrs: Vec<Attribute>) {
295 let node = &mut self.doc.nodes[node_id];
296 node.insert_damage(ALL_DAMAGE);
297 let element_data = node.element_data_mut().expect("Not an element");
298
299 let existing_names = element_data
300 .attrs
301 .iter()
302 .map(|e| e.name.clone())
303 .collect::<HashSet<_>>();
304
305 for attr in attrs
306 .into_iter()
307 .filter(|attr| !existing_names.contains(&attr.name))
308 {
309 self.set_attribute(node_id, attr.name, &attr.value);
310 }
311 }
312
313 pub fn set_attribute(&mut self, node_id: NodeId, name: QualName, value: &str) {
314 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
315 if node_is_in_document {
316 self.doc.snapshot_node(node_id);
317
318 let renders_from_attributes = self.doc.nodes[node_id]
334 .data
335 .downcast_element()
336 .is_some_and(|el| {
337 el.name.ns == ns!(svg)
338 || crate::layout::replaced::is_replaced_element(&el.name.local)
339 });
340
341 let node = &mut self.doc.nodes[node_id];
342 if let Some(mut data) = node.stylo_element_data_opt_mut().and_then(|s| s.get_mut()) {
343 data.hint |= RestyleHint::restyle_subtree();
344 if renders_from_attributes {
345 data.damage.insert(ALL_DAMAGE);
346 }
347 }
348
349 let parent = node.parent;
366 if let Some(parent_id) = parent {
367 let parent = &self.doc.nodes[parent_id];
368 let flags = parent.selector_flags().get();
369 let child_dependent = ElementSelectorFlags::HAS_SLOW_SELECTOR
370 | ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS
371 | ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR
372 | ElementSelectorFlags::HAS_EMPTY_SELECTOR
373 | ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR
374 | ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING
375 | ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING;
376
377 if flags.intersects(child_dependent) {
378 let parent = &mut self.doc.nodes[parent_id];
379 if let Some(mut data) = parent
380 .stylo_element_data_opt_mut()
381 .and_then(|s| s.get_mut())
382 {
383 data.hint |= RestyleHint::restyle_subtree();
384 }
385 }
386 }
387
388 self.doc.nodes[node_id].mark_ancestors_dirty();
392 }
393
394 if name.local == local_name!("id") && node_is_in_document {
395 if let Some(old_id) = self.doc.nodes[node_id]
396 .element_data()
397 .map(|element| element.id.clone())
398 {
399 if let Some(old_id) = old_id {
400 self.doc.remove_from_id_map(&old_id, node_id);
401 }
402 self.doc.add_to_id_map(value, node_id);
403 }
404 }
405
406 let node = &mut self.doc.nodes[node_id];
407
408 let NodeData::Element(ref mut element) = node.data else {
409 return;
410 };
411
412 self.mutations_occurred |= node_is_in_document;
413 #[cfg(feature = "custom-widget")]
415 if let SpecialElementData::CustomWidget(widget_data) = &mut element.special_data {
416 let old_value = element.attrs.get(&name).as_ref().map(|attr| &*attr.value);
417 widget_data
418 .widget
419 .attribute_changed(&name.local, old_value, Some(value));
420 }
421
422 #[cfg(feature = "shadow-dom")]
425 if element.custom_element_data().is_some() {
426 let old_value = element
427 .attrs
428 .get(&name)
429 .as_ref()
430 .map(|attr| attr.value.to_string());
431 self.custom_element_attr_changes.push((
432 node_id,
433 name.clone(),
434 old_value,
435 Some(value.to_string()),
436 ));
437 }
438
439 element.attrs.set(name.clone(), value);
440
441 if name.local == local_name!("tabindex")
446 || name.local == local_name!("href")
447 || name.local == local_name!("disabled")
448 {
449 element.flush_is_focussable();
450 }
451
452 let tag = &element.name.local;
453 let attr = &name.local;
454
455 if *attr == local_name!("id") {
456 element.id = Some(Atom::from(value))
457 }
458
459 if *attr == local_name!("value") {
460 if let Some(input_data) = element.text_input_data_mut() {
461 input_data.set_text(
463 &mut self.doc.font_ctx.lock().unwrap(),
464 &mut self.doc.layout_ctx,
465 value,
466 );
467 }
468 return;
469 }
470
471 if *attr == local_name!("style") {
472 element.flush_style_attribute(&self.doc.guard, &self.doc.url.url_extra_data());
473 node.mark_style_attr_updated();
474 return;
475 }
476
477 if *attr == local_name!("disabled") && element.can_be_disabled() {
478 node.disable();
479 return;
480 }
481
482 if !node.flags.is_in_document() {
485 return;
486 }
487
488 if (tag, attr) == tag_and_attr!("input", "checked") {
489 set_input_checked_state(element, value.to_string());
490 } else if (tag, attr) == tag_and_attr!("img", "src") {
491 self.load_image(node_id);
492 } else if (tag, attr) == tag_and_attr!("canvas", "src") {
493 self.load_custom_paint_src(node_id);
494 } else if (tag, attr) == tag_and_attr!("link", "href") {
495 self.load_linked_stylesheet(node_id);
496 } else if (tag, attr) == tag_and_attr!("iframe", "src")
497 || (tag, attr) == tag_and_attr!("iframe", "srcdoc")
498 {
499 self.load_iframe(node_id);
500 }
501 }
502
503 pub fn clear_attribute(&mut self, node_id: NodeId, name: QualName) {
504 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
505 if node_is_in_document {
506 self.doc.snapshot_node(node_id);
507
508 let node = &mut self.doc.nodes[node_id];
509
510 if let Some(mut data) = node.stylo_element_data_opt_mut().and_then(|s| s.get_mut()) {
511 data.hint |= RestyleHint::restyle_subtree();
512 data.damage.insert(ALL_DAMAGE);
513 }
514
515 node.mark_ancestors_dirty();
518 }
519
520 if name.local == local_name!("id") && node_is_in_document {
521 if let Some(old_id) = self.doc.nodes[node_id]
522 .element_data()
523 .and_then(|element| element.id.clone())
524 {
525 self.doc.remove_from_id_map(&old_id, node_id);
526 }
527 }
528
529 let node = &mut self.doc.nodes[node_id];
530
531 let Some(element) = node.element_data_mut() else {
532 return;
533 };
534
535 let removed_attr = element.attrs.remove(&name);
536 let had_attr = removed_attr.is_some();
537 if !had_attr {
538 return;
539 }
540 self.mutations_occurred |= node_is_in_document;
541
542 #[cfg(feature = "custom-widget")]
544 if let SpecialElementData::CustomWidget(widget_data) = &mut element.special_data {
545 let old_value = removed_attr.as_ref().map(|attr| &*attr.value);
546 widget_data
547 .widget
548 .attribute_changed(&name.local, old_value, None);
549 }
550
551 #[cfg(feature = "shadow-dom")]
553 if element.custom_element_data().is_some() {
554 let old_value = removed_attr.as_ref().map(|attr| attr.value.to_string());
555 self.custom_element_attr_changes
556 .push((node_id, name.clone(), old_value, None));
557 }
558
559 if name.local == local_name!("id") {
560 element.id = None;
561 }
562
563 if name.local == local_name!("tabindex")
566 || name.local == local_name!("href")
567 || name.local == local_name!("disabled")
568 {
569 element.flush_is_focussable();
570 }
571
572 if name.local == local_name!("value") {
574 if let Some(input_data) = element.text_input_data_mut() {
575 input_data.set_text(
576 &mut self.doc.font_ctx.lock().unwrap(),
577 &mut self.doc.layout_ctx,
578 "",
579 );
580 }
581 }
582
583 let tag = &element.name.local;
584 let attr = &name.local;
585
586 if *attr == local_name!("disabled") && element.can_be_disabled() {
587 node.enable();
588 return;
589 }
590
591 if *attr == local_name!("style") {
592 element.flush_style_attribute(&self.doc.guard, &self.doc.url.url_extra_data());
593 node.mark_style_attr_updated();
594 } else if (tag, attr) == tag_and_attr!("canvas", "src") {
595 self.recompute_is_animating = true;
596 } else if (tag, attr) == tag_and_attr!("link", "href") {
597 self.unload_stylesheet(node_id);
598 } else if (tag, attr) == tag_and_attr!("iframe", "srcdoc") && node_is_in_document {
599 self.load_iframe(node_id);
601 }
602 }
603
604 pub fn set_style_property(&mut self, node_id: NodeId, name: &str, value: &str) {
605 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
606 self.doc.set_style_property(node_id, name, value);
607 self.mutations_occurred |= node_is_in_document;
608 }
609
610 pub fn remove_style_property(&mut self, node_id: NodeId, name: &str) {
611 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
612 self.doc.remove_style_property(node_id, name);
613 self.mutations_occurred |= node_is_in_document;
614 }
615
616 pub fn set_sub_document(&mut self, node_id: NodeId, sub_document: Box<dyn Document>) {
617 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
618 self.doc.set_sub_document(node_id, sub_document);
619 self.mutations_occurred |= node_is_in_document;
620 }
621
622 pub fn remove_sub_document(&mut self, node_id: NodeId) {
623 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
624 self.doc.remove_sub_document(node_id);
625 self.mutations_occurred |= node_is_in_document;
626 }
627
628 #[cfg(feature = "custom-widget")]
629 pub fn set_custom_widget(&mut self, node_id: NodeId, widget: Box<dyn crate::Widget>) {
630 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
631 self.doc.set_custom_widget(node_id, widget);
632 self.mutations_occurred |= node_is_in_document;
633 }
634
635 #[cfg(feature = "custom-widget")]
636 pub fn remove_custom_widget(&mut self, node_id: NodeId) {
637 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
638 self.doc.remove_custom_widget(node_id);
639 self.mutations_occurred |= node_is_in_document;
640 }
641
642 #[cfg(feature = "shadow-dom")]
645 pub fn attach_shadow(&mut self, host_id: NodeId, mode: crate::node::ShadowRootMode) -> NodeId {
646 self.doc.attach_shadow(host_id, mode)
647 }
648
649 #[cfg(feature = "shadow-dom")]
652 pub fn set_custom_element(
653 &mut self,
654 node_id: NodeId,
655 controller: Box<dyn crate::node::CustomElement>,
656 ) {
657 self.doc.set_custom_element(node_id, controller);
658 self.upgrade_custom_element(node_id);
659 }
660
661 #[cfg(feature = "shadow-dom")]
664 pub fn remove_custom_element(&mut self, node_id: NodeId) {
665 self.disconnect_custom_element(node_id);
666 let _ = self.doc.take_custom_element(node_id);
667 }
668
669 #[cfg(feature = "shadow-dom")]
674 pub(crate) fn upgrade_custom_element(&mut self, node_id: NodeId) {
675 use crate::node::{CustomElementData, ShadowRootMode, SpecialElementData};
676
677 let Some(node) = self.doc.get_node(node_id) else {
678 return;
679 };
680 let Some(element) = node.element_data() else {
681 return;
682 };
683
684 let already_has_controller =
687 matches!(element.special_data, SpecialElementData::CustomElement(_));
688
689 let mode = if already_has_controller {
690 ShadowRootMode::Open
692 } else {
693 let tag = element.name.local.clone();
694 let Some(definition) = self.doc.custom_element_registry.get(&tag) else {
695 return;
696 };
697 let mode = definition.mode;
698 let controller = (definition.factory)();
699 self.doc.nodes[node_id]
700 .element_data_mut()
701 .unwrap()
702 .special_data =
703 SpecialElementData::CustomElement(CustomElementData::new(controller));
704 self.doc.custom_element_nodes.insert(node_id);
705 mode
706 };
707
708 let is_upgraded = self.doc.nodes[node_id]
710 .element_data()
711 .and_then(|el| el.custom_element_data())
712 .map(|data| data.upgraded)
713 .unwrap_or(true);
714 if is_upgraded {
715 return;
716 }
717
718 let shadow_root_id = self.doc.attach_shadow(node_id, mode);
720
721 let Some(mut controller) = self.take_controller(node_id) else {
723 return;
724 };
725
726 {
727 let mut ctx = crate::node::CustomElementCtx {
728 mutator: self,
729 host_id: node_id,
730 shadow_root_id,
731 };
732 controller.connected(&mut ctx);
733 }
734
735 self.restore_controller(node_id, controller, true);
736 }
737
738 #[cfg(feature = "shadow-dom")]
740 pub(crate) fn disconnect_custom_element(&mut self, node_id: NodeId) {
741 let Some(shadow_root_id) = self
742 .doc
743 .get_node(node_id)
744 .and_then(|node| node.shadow_root_id())
745 else {
746 if let Some(mut controller) = self.take_controller(node_id) {
748 {
751 let mut ctx = crate::node::CustomElementCtx {
752 mutator: self,
753 host_id: node_id,
754 shadow_root_id: node_id,
755 };
756 controller.disconnected(&mut ctx);
757 }
758 self.restore_controller(node_id, controller, false);
759 }
760 return;
761 };
762
763 if let Some(mut controller) = self.take_controller(node_id) {
764 {
765 let mut ctx = crate::node::CustomElementCtx {
766 mutator: self,
767 host_id: node_id,
768 shadow_root_id,
769 };
770 controller.disconnected(&mut ctx);
771 }
772 self.restore_controller(node_id, controller, false);
773 }
774 }
775
776 #[cfg(feature = "shadow-dom")]
779 fn take_controller(&mut self, node_id: NodeId) -> Option<Box<dyn crate::node::CustomElement>> {
780 self.doc
781 .nodes
782 .get_mut(node_id)?
783 .element_data_mut()?
784 .custom_element_data_mut()?
785 .controller
786 .take()
787 }
788
789 #[cfg(feature = "shadow-dom")]
792 fn restore_controller(
793 &mut self,
794 node_id: NodeId,
795 controller: Box<dyn crate::node::CustomElement>,
796 upgraded: bool,
797 ) {
798 if let Some(data) = self
799 .doc
800 .nodes
801 .get_mut(node_id)
802 .and_then(|node| node.element_data_mut())
803 .and_then(|el| el.custom_element_data_mut())
804 {
805 data.controller = Some(controller);
806 if upgraded {
807 data.upgraded = true;
808 }
809 }
810 }
811
812 fn invalidate_layout_parent_edge(&mut self, node_id: NodeId) {
821 let Some(layout_parent_id) = self
822 .doc
823 .nodes
824 .get(node_id)
825 .and_then(|node| node.layout_parent.get())
826 else {
827 return;
828 };
829 if let Some(layout_parent) = self.doc.nodes.get_mut(layout_parent_id) {
830 layout_parent.layout_children.get_mut().take();
831 layout_parent.paint_children.get_mut().take();
832 layout_parent.insert_damage(ALL_DAMAGE);
833 }
834 if let Some(node) = self.doc.nodes.get(node_id) {
835 node.layout_parent.set(None);
836 }
837 }
838
839 fn clear_layout_of_subtree(doc: &mut BaseDocument, node_id: NodeId) {
841 let mut stack = vec![node_id];
842 while let Some(id) = stack.pop() {
843 let Some(node) = doc.nodes.get_mut(id) else {
844 continue;
845 };
846 if node.data.downcast_element().is_some() {
850 *node.unrounded_layout_mut() = taffy::Layout::with_order(0);
851 *node.final_layout_mut() = taffy::Layout::with_order(0);
852 node.cache_mut().clear();
853 }
854 stack.extend(node.children.iter().copied());
855 }
856 }
857
858 pub fn remove_node(&mut self, node_id: NodeId) {
860 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
861 self.process_removed_subtree(node_id);
865
866 Self::clear_layout_of_subtree(self.doc, node_id);
874 self.invalidate_layout_parent_edge(node_id);
875
876 let node = &mut self.doc.nodes[node_id];
877
878 if let Some(parent_id) = node.parent.take() {
880 self.mutations_occurred |= node_is_in_document;
881 let parent = &mut self.doc.nodes[parent_id];
882 parent.insert_damage(ALL_DAMAGE);
883 parent.mark_ancestors_dirty();
885 parent.children.retain(|id| *id != node_id);
886 self.maybe_record_node(parent_id);
887 }
888 }
889
890 pub fn remove_and_drop_node(&mut self, node_id: NodeId) -> Option<Node> {
891 self.remove_and_drop_node_with(node_id, &mut |_| {})
892 }
893
894 pub fn remove_and_drop_node_with(
897 &mut self,
898 node_id: NodeId,
899 on_drop: &mut dyn FnMut(NodeId),
900 ) -> Option<Node> {
901 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
902 self.process_removed_subtree(node_id);
903 self.invalidate_layout_parent_edge(node_id);
904
905 let node = self.doc.drop_node_ignoring_parent_with(node_id, on_drop);
906 self.mutations_occurred |= node_is_in_document;
907
908 if let Some(parent_id) = node.as_ref().and_then(|node| node.parent) {
910 let parent = &mut self.doc.nodes[parent_id];
911 parent.insert_damage(ALL_DAMAGE);
912 let parent_is_in_doc = parent.flags.is_in_document();
913
914 if parent_is_in_doc {
916 if let Some(mut data) = parent
917 .stylo_element_data_opt_mut()
918 .and_then(|s| s.get_mut())
919 {
920 data.hint |= RestyleHint::restyle_subtree();
921 }
922 parent.mark_ancestors_dirty();
924 }
925
926 parent.children.retain(|id| *id != node_id);
927 self.maybe_record_node(parent_id);
928 }
929
930 node
931 }
932
933 pub fn remove_and_drop_all_children(&mut self, node_id: NodeId) {
934 let parent = &mut self.doc.nodes[node_id];
935 let parent_is_in_doc = parent.flags.is_in_document();
936
937 if parent_is_in_doc {
939 if let Some(mut data) = parent
940 .stylo_element_data_opt_mut()
941 .and_then(|s| s.get_mut())
942 {
943 data.hint |= RestyleHint::restyle_subtree();
944 }
945 parent.mark_ancestors_dirty();
947 }
948
949 let children = mem::take(&mut parent.children);
950 self.mutations_occurred |= parent_is_in_doc && !children.is_empty();
951 for child_id in children {
952 self.process_removed_subtree(child_id);
953 self.invalidate_layout_parent_edge(child_id);
954 let _ = self.doc.drop_node_ignoring_parent(child_id);
955 }
956 self.maybe_record_node(node_id);
957 }
958
959 pub fn remove_node_if_unparented(&mut self, node_id: NodeId) {
961 self.remove_node_if_unparented_with(node_id, &mut |_| {});
962 }
963
964 pub fn remove_node_if_unparented_with(
967 &mut self,
968 node_id: NodeId,
969 on_drop: &mut dyn FnMut(NodeId),
970 ) {
971 if let Some(node) = self.doc.get_node(node_id) {
972 if node.parent.is_none() {
973 self.remove_and_drop_node_with(node_id, on_drop);
974 }
975 }
976 }
977
978 pub fn append_children(&mut self, parent_id: NodeId, child_ids: &[NodeId]) {
980 self.add_children_to_parent(parent_id, child_ids, &|parent, child_ids| {
981 parent.children.extend_from_slice(child_ids);
982 });
983 }
984
985 pub fn insert_nodes_before(&mut self, anchor_node_id: NodeId, new_node_ids: &[NodeId]) {
986 let parent_id = self.doc.nodes[anchor_node_id].parent.unwrap();
987 self.add_children_to_parent(parent_id, new_node_ids, &|parent, child_ids| {
988 let node_child_idx = parent.index_of_child(anchor_node_id).unwrap();
989 parent
990 .children
991 .splice(node_child_idx..node_child_idx, child_ids.iter().copied());
992 });
993 }
994
995 fn add_children_to_parent(
996 &mut self,
997 parent_id: NodeId,
998 child_ids: &[NodeId],
999 insert_children_fn: &dyn Fn(&mut Node, &[NodeId]),
1000 ) {
1001 let child_ids: Vec<NodeId> = if child_ids
1009 .iter()
1010 .any(|id| matches!(self.doc.nodes[*id].data, NodeData::DocumentFragment))
1011 {
1012 let mut flattened = Vec::with_capacity(child_ids.len());
1013 for id in child_ids.iter().copied() {
1014 if matches!(self.doc.nodes[id].data, NodeData::DocumentFragment) {
1015 let moved = std::mem::take(&mut self.doc.nodes[id].children);
1016 flattened.extend(moved);
1017 } else {
1018 flattened.push(id);
1019 }
1020 }
1021 flattened
1022 } else {
1023 child_ids.to_vec()
1024 };
1025 let child_ids = child_ids.as_slice();
1026
1027 let new_parent_is_in_document = self.doc.nodes[parent_id].flags.is_in_document();
1028 self.mutations_occurred |= new_parent_is_in_document && !child_ids.is_empty();
1029 for child_id in child_ids.iter().copied() {
1036 self.invalidate_layout_parent_edge(child_id);
1037 let child = &mut self.doc.nodes[child_id];
1038 let child_was_in_doc = child.flags.is_in_document();
1039 self.mutations_occurred |= child_was_in_doc;
1040 let Some(old_parent_id) = child.parent.take() else {
1041 continue;
1042 };
1043
1044 let old_parent = &mut self.doc.nodes[old_parent_id];
1045 old_parent.insert_damage(ALL_DAMAGE);
1046
1047 if child_was_in_doc {
1049 if let Some(mut data) = old_parent
1050 .stylo_element_data_opt_mut()
1051 .and_then(|s| s.get_mut())
1052 {
1053 data.hint |= RestyleHint::restyle_subtree();
1054 }
1055 old_parent.mark_ancestors_dirty();
1057 }
1058
1059 old_parent.children.retain(|id| *id != child_id);
1060 self.maybe_record_node(old_parent_id);
1061 }
1062
1063 let new_parent = &mut self.doc.nodes[parent_id];
1064 new_parent.insert_damage(ALL_DAMAGE);
1065
1066 if new_parent_is_in_document {
1068 if let Some(mut data) = new_parent
1069 .stylo_element_data_opt_mut()
1070 .and_then(|s| s.get_mut())
1071 {
1072 data.hint |= RestyleHint::restyle_subtree();
1073 }
1074 new_parent.mark_ancestors_dirty();
1076 }
1077
1078 insert_children_fn(new_parent, child_ids);
1079
1080 for child_id in child_ids.iter().copied() {
1081 let child = &mut self.doc.nodes[child_id];
1082 let child_was_in_doc = child.flags.is_in_document();
1083 child.parent = Some(parent_id);
1084
1085 if new_parent_is_in_document && !child_was_in_doc {
1086 self.process_added_subtree(child_id);
1087 } else if !new_parent_is_in_document && child_was_in_doc {
1088 self.process_removed_subtree(child_id);
1089 }
1090 }
1091
1092 self.maybe_record_node(parent_id);
1093 }
1094
1095 pub fn insert_nodes_after(&mut self, anchor_node_id: NodeId, new_node_ids: &[NodeId]) {
1097 match self.next_sibling_id(anchor_node_id) {
1098 Some(id) => self.insert_nodes_before(id, new_node_ids),
1099 None => {
1100 let parent_id = self.parent_id(anchor_node_id).unwrap();
1101 self.append_children(parent_id, new_node_ids)
1102 }
1103 }
1104 }
1105
1106 pub fn reparent_children(&mut self, old_parent_id: NodeId, new_parent_id: NodeId) {
1107 let child_ids = std::mem::take(&mut self.doc.nodes[old_parent_id].children);
1108 self.maybe_record_node(old_parent_id);
1109 self.append_children(new_parent_id, &child_ids);
1110 }
1111
1112 pub fn replace_node_with(&mut self, anchor_node_id: NodeId, new_node_ids: &[NodeId]) {
1113 self.insert_nodes_before(anchor_node_id, new_node_ids);
1114 self.remove_node(anchor_node_id);
1115 }
1116}
1117
1118impl<'doc> DocumentMutator<'doc> {
1119 pub fn flush(&mut self) {
1120 if self.recompute_is_animating {
1121 self.doc.has_canvas = self.doc.compute_has_canvas();
1122 }
1123
1124 if let Some(id) = self.title_node {
1125 let title = self.doc.nodes[id].text_content();
1126 self.doc.shell_provider.set_window_title(title);
1127 }
1128
1129 for id in self.style_nodes.drain() {
1131 self.doc.process_style_element(id);
1132 }
1133
1134 for id in self.form_nodes.drain() {
1135 self.doc.reset_form_owner(id);
1136 }
1137
1138 #[cfg(feature = "autofocus")]
1139 if let Some(node_id) = self.node_to_autofocus.take() {
1140 if self.doc.get_node(node_id).is_some() {
1141 self.doc.set_focus_to(node_id);
1142 }
1143 }
1144
1145 #[cfg(feature = "shadow-dom")]
1146 self.dispatch_custom_element_attr_changes();
1147 }
1148
1149 #[cfg(feature = "shadow-dom")]
1151 fn dispatch_custom_element_attr_changes(&mut self) {
1152 if self.custom_element_attr_changes.is_empty() {
1153 return;
1154 }
1155 let changes = mem::take(&mut self.custom_element_attr_changes);
1156 for (node_id, name, old_value, new_value) in changes {
1157 let tag = self
1161 .doc
1162 .get_node(node_id)
1163 .and_then(|node| node.element_data())
1164 .map(|el| el.name.local.clone());
1165 let observed = tag
1166 .as_ref()
1167 .and_then(|tag| self.doc.custom_element_registry.get(tag))
1168 .map(|def| def.observes(&name.local))
1169 .unwrap_or(true);
1170 if !observed {
1171 continue;
1172 }
1173
1174 let Some(shadow_root_id) = self
1175 .doc
1176 .get_node(node_id)
1177 .and_then(|node| node.shadow_root_id())
1178 else {
1179 continue;
1180 };
1181 let Some(mut controller) = self.take_controller(node_id) else {
1182 continue;
1183 };
1184 {
1185 let mut ctx = crate::node::CustomElementCtx {
1186 mutator: self,
1187 host_id: node_id,
1188 shadow_root_id,
1189 };
1190 controller.attribute_changed(
1191 &mut ctx,
1192 &name.local,
1193 old_value.as_deref(),
1194 new_value.as_deref(),
1195 );
1196 }
1197 self.restore_controller(node_id, controller, false);
1198 }
1199 }
1200
1201 pub fn set_inner_html(&mut self, node_id: NodeId, html: &str) {
1202 self.remove_and_drop_all_children(node_id);
1203 self.doc
1204 .html_parser_provider
1205 .clone()
1206 .parse_inner_html(self, node_id, html);
1207 }
1208
1209 fn flush_eager_ops(&mut self) {
1210 let mut ops = mem::take(&mut self.eager_op_queue);
1211 for op in ops.drain(0..) {
1212 match op {
1213 SpecialOp::LoadImage(node_id) => self.load_image(node_id),
1214 SpecialOp::LoadIframe(node_id) => self.load_iframe(node_id),
1215 SpecialOp::LoadStylesheet(node_id) => self.load_linked_stylesheet(node_id),
1216 SpecialOp::UnloadStylesheet(node_id) => self.unload_stylesheet(node_id),
1217 SpecialOp::LoadCustomPaintSource(node_id) => self.load_custom_paint_src(node_id),
1218 SpecialOp::ProcessButtonInput(node_id) => self.process_button_input(node_id),
1219 SpecialOp::UnloadSubDocument(node_id) => self.remove_sub_document(node_id),
1220 #[cfg(feature = "custom-widget")]
1221 SpecialOp::UnloadCustomWidget(node_id) => self.remove_custom_widget(node_id),
1222 #[cfg(feature = "shadow-dom")]
1223 SpecialOp::UpgradeCustomElement(node_id) => self.upgrade_custom_element(node_id),
1224 #[cfg(feature = "shadow-dom")]
1225 SpecialOp::DisconnectCustomElement(node_id) => {
1226 self.disconnect_custom_element(node_id)
1227 }
1228 }
1229 }
1230
1231 self.eager_op_queue = ops;
1233 }
1234
1235 fn process_added_subtree(&mut self, node_id: NodeId) {
1236 self.doc.iter_subtree_mut(node_id, |node_id, doc| {
1237 let node = &mut doc.nodes[node_id];
1238 node.flags.set(NodeFlags::IS_IN_DOCUMENT, true);
1239 node.insert_damage(ALL_DAMAGE);
1240
1241 if let Some(id_attr) = node.attr(local_name!("id")).map(ToString::to_string) {
1243 doc.add_to_id_map(&id_attr, node_id);
1244 }
1245
1246 let node = &mut doc.nodes[node_id];
1247 let NodeData::Element(ref mut element) = node.data else {
1248 return;
1249 };
1250
1251 let tag = element.name.local.as_ref();
1253 match tag {
1254 "title" if element.name.ns == ns!(html) => self.title_node = Some(node_id),
1255 "link" => self.eager_op_queue.push(SpecialOp::LoadStylesheet(node_id)),
1256 "img" => self.eager_op_queue.push(SpecialOp::LoadImage(node_id)),
1257 "iframe" => self.eager_op_queue.push(SpecialOp::LoadIframe(node_id)),
1258 "canvas" => self
1259 .eager_op_queue
1260 .push(SpecialOp::LoadCustomPaintSource(node_id)),
1261 "style" => {
1262 self.style_nodes.insert(node_id);
1263 }
1264 "button" | "fieldset" | "input" | "select" | "textarea" | "object" | "output" => {
1265 self.eager_op_queue
1266 .push(SpecialOp::ProcessButtonInput(node_id));
1267 self.form_nodes.insert(node_id);
1268 }
1269 _ => {}
1270 }
1271
1272 #[cfg(feature = "shadow-dom")]
1276 {
1277 let needs_upgrade = doc.custom_element_registry.contains(&element.name.local)
1278 && element.custom_element_data().is_none();
1279 if needs_upgrade {
1280 self.eager_op_queue
1281 .push(SpecialOp::UpgradeCustomElement(node_id));
1282 }
1283 }
1284
1285 #[cfg(feature = "autofocus")]
1296 if node.is_focussable() {
1297 if let NodeData::Element(ref element) = node.data {
1298 if element.attr(local_name!("autofocus")).is_some() {
1299 self.node_to_autofocus = Some(node_id);
1300 }
1301 }
1302 }
1303 });
1304
1305 self.flush_eager_ops();
1306 }
1307
1308 fn process_removed_subtree(&mut self, node_id: NodeId) {
1309 self.doc.iter_subtree_mut(node_id, |node_id, doc| {
1310 doc.nodes[node_id]
1311 .flags
1312 .set(NodeFlags::IS_IN_DOCUMENT, false);
1313
1314 doc.clear_interaction_state_for_removed_node(node_id);
1318
1319 let node = &mut doc.nodes[node_id];
1320
1321 if doc.focus_node_id == Some(node_id) {
1338 doc.focus_node_id = None;
1339 }
1340 if doc.mousedown_node_id == Some(node_id) {
1341 doc.mousedown_node_id = None;
1342 }
1343
1344 if doc.text_selection.anchor.node_or_parent == Some(node_id)
1347 || doc.text_selection.focus.node_or_parent == Some(node_id)
1348 {
1349 doc.text_selection.clear();
1350 }
1351
1352 if node.has_snapshot() {
1355 let opaque_id = style::dom::TNode::opaque(&&*node);
1356 doc.snapshots.remove(&opaque_id);
1357 node.set_has_snapshot(false);
1358 }
1359
1360 if let Some(id_attr) = node.attr(local_name!("id")).map(ToString::to_string) {
1362 doc.remove_from_id_map(&id_attr, node_id);
1363 }
1364
1365 let node = &mut doc.nodes[node_id];
1366 let NodeData::Element(ref mut element) = node.data else {
1367 return;
1368 };
1369
1370 match &element.special_data {
1371 SpecialElementData::SubDocument(_) => {
1372 self.eager_op_queue
1373 .push(SpecialOp::UnloadSubDocument(node_id));
1374 }
1375 #[cfg(feature = "custom-widget")]
1376 SpecialElementData::CustomWidget(_) => {
1377 self.eager_op_queue
1378 .push(SpecialOp::UnloadCustomWidget(node_id));
1379 }
1380 #[cfg(feature = "shadow-dom")]
1381 SpecialElementData::CustomElement(_) => {
1382 self.eager_op_queue
1383 .push(SpecialOp::DisconnectCustomElement(node_id));
1384 }
1385 SpecialElementData::Stylesheet(_) => self
1386 .eager_op_queue
1387 .push(SpecialOp::UnloadStylesheet(node_id)),
1388 SpecialElementData::Image(_) => {}
1389 SpecialElementData::Canvas(_) => {
1390 self.recompute_is_animating = true;
1391 }
1392 SpecialElementData::TableRoot(_) => {}
1393 SpecialElementData::TextInput(_) => {}
1394 SpecialElementData::CheckboxInput(_) => {}
1395 #[cfg(feature = "file-input")]
1396 SpecialElementData::FileInput(_) => {}
1397 SpecialElementData::None => {}
1398 }
1399 });
1400
1401 self.flush_eager_ops();
1402 }
1403
1404 fn maybe_record_node(&mut self, node_id: impl Into<Option<NodeId>>) {
1405 let Some(node_id) = node_id.into() else {
1406 return;
1407 };
1408
1409 let Some(element) = self.doc.nodes[node_id].data.downcast_element() else {
1410 return;
1411 };
1412
1413 match element.name.local.as_ref() {
1414 "title" if element.name.ns == ns!(html) => self.title_node = Some(node_id),
1415 "style" => {
1416 self.style_nodes.insert(node_id);
1417 }
1418 _ => {}
1419 }
1420 }
1421
1422 fn load_linked_stylesheet(&mut self, target_id: NodeId) {
1423 let node = &self.doc.nodes[target_id];
1424
1425 let mut is_in_head = false;
1426 let mut parent_id = node.parent;
1427 while let Some(id) = parent_id
1428 && !is_in_head
1429 {
1430 let parent = &self.doc.nodes[id];
1431 is_in_head |= parent.data.is_element_with_tag_name(&local_name!("head"));
1432 parent_id = parent.parent;
1433 }
1434
1435 let rel_attr = node.attr(local_name!("rel"));
1436 let href_attr = node.attr(local_name!("href"));
1437
1438 let (Some(rels), Some(href)) = (rel_attr, href_attr) else {
1439 return;
1440 };
1441 if !rels.split_ascii_whitespace().any(|rel| rel == "stylesheet") {
1442 return;
1443 }
1444
1445 let url = self.doc.resolve_url(href);
1446 let handler = ResourceHandler::new(
1447 self.doc.tx.clone(),
1448 self.doc.id(),
1449 Some(node.id),
1450 self.doc.shell_provider.clone(),
1451 StylesheetHandler {
1452 source_url: url.clone(),
1453 guard: self.doc.guard.clone(),
1454 net_provider: self.doc.net_provider.clone(),
1455 abort_signal: self.doc.abort_signal.clone(),
1456 },
1457 );
1458
1459 if is_in_head && !self.doc.net_provider.is_noop() {
1460 self.doc
1461 .pending_critical_resources
1462 .insert(handler.request_id());
1463 }
1464
1465 self.doc.net_provider.fetch(
1466 self.doc.id(),
1467 self.doc.build_request(url),
1468 Box::new(handler),
1469 );
1470 }
1471
1472 fn unload_stylesheet(&mut self, node_id: NodeId) {
1473 let node = &mut self.doc.nodes[node_id];
1474 let Some(element) = node.element_data_mut() else {
1475 unreachable!();
1476 };
1477 let SpecialElementData::Stylesheet(stylesheet) = element.special_data.take() else {
1478 unreachable!();
1479 };
1480
1481 let guard = self.doc.guard.read();
1482 self.doc.stylist.remove_stylesheet(stylesheet, &guard);
1483 self.doc
1484 .stylist
1485 .force_stylesheet_origins_dirty(OriginSet::all());
1486
1487 self.doc.nodes_to_stylesheet.remove(&node_id);
1488 }
1489
1490 fn load_image(&mut self, target_id: NodeId) {
1491 let node = &self.doc.nodes[target_id];
1492 if let Some(raw_src) = node.attr(local_name!("src")) {
1493 if !raw_src.is_empty() {
1494 let src = self.doc.resolve_url(raw_src);
1495 let src_string = src.as_str();
1496
1497 if let Some(cached_image) = self.doc.image_cache.get(src_string) {
1499 #[cfg(feature = "tracing")]
1500 tracing::info!("Loading image {src_string} from cache");
1501 let node = &mut self.doc.nodes[target_id];
1502 node.element_data_mut().unwrap().special_data =
1503 SpecialElementData::Image(Box::new(cached_image.clone()));
1504 node.cache_mut().clear();
1505 node.insert_damage(ALL_DAMAGE);
1506 return;
1507 }
1508
1509 if let Some(waiting_list) = self.doc.pending_images.get_mut(src_string) {
1511 #[cfg(feature = "tracing")]
1512 tracing::info!("Image {src_string} already pending, queueing node {target_id}");
1513 waiting_list.push((target_id, ImageType::Image));
1514 return;
1515 }
1516
1517 #[cfg(feature = "tracing")]
1519 tracing::info!("Fetching image {src_string}");
1520 self.doc
1521 .pending_images
1522 .insert(src_string.to_string(), vec![(target_id, ImageType::Image)]);
1523
1524 self.doc.net_provider.fetch(
1525 self.doc.id(),
1526 self.doc.build_request(src),
1527 ResourceHandler::boxed(
1528 self.doc.tx.clone(),
1529 self.doc.id(),
1530 None, self.doc.shell_provider.clone(),
1532 ImageHandler::new(ImageType::Image),
1533 ),
1534 );
1535 }
1536 }
1537 }
1538
1539 fn load_iframe(&mut self, target_id: NodeId) {
1540 if self.doc.subdocument_depth >= crate::iframe::MAX_SUBDOCUMENT_DEPTH {
1541 #[cfg(feature = "tracing")]
1542 tracing::warn!(
1543 "Not loading iframe: max sub-document nesting depth ({}) reached",
1544 crate::iframe::MAX_SUBDOCUMENT_DEPTH
1545 );
1546 return;
1547 }
1548
1549 let node = &self.doc.nodes[target_id];
1550 let Some(element) = node.element_data() else {
1551 return;
1552 };
1553
1554 if let Some(srcdoc) = element.attr(local_name!("srcdoc")) {
1556 let srcdoc = srcdoc.to_string();
1557 self.doc.load_iframe_srcdoc(target_id, &srcdoc);
1558 return;
1559 }
1560
1561 let Some(raw_src) = element.attr(local_name!("src")) else {
1562 return;
1563 };
1564 if raw_src.is_empty() {
1565 return;
1566 }
1567 let Some(url) = self.doc.url.resolve_relative(raw_src) else {
1568 #[cfg(feature = "tracing")]
1569 tracing::warn!("Not loading iframe: could not resolve url {raw_src}");
1570 return;
1571 };
1572 self.doc.start_iframe_load(target_id, url);
1573 }
1574
1575 fn load_custom_paint_src(&mut self, target_id: NodeId) {
1576 let node = &mut self.doc.nodes[target_id];
1577 if let Some(raw_src) = node.attr(local_name!("src")) {
1578 if let Ok(custom_paint_source_id) = raw_src.parse::<u64>() {
1579 self.recompute_is_animating = true;
1580 let canvas_data = SpecialElementData::Canvas(CanvasData {
1581 custom_paint_source_id,
1582 });
1583 node.element_data_mut().unwrap().special_data = canvas_data;
1584 }
1585 }
1586 }
1587
1588 fn process_button_input(&mut self, target_id: NodeId) {
1589 let node = &self.doc.nodes[target_id];
1590 let Some(data) = node.element_data() else {
1591 return;
1592 };
1593
1594 let tagname = data.name.local.as_ref();
1595 let type_attr = data.attr(local_name!("type"));
1596 let value = data.attr(local_name!("value"));
1597
1598 if let ("input", Some("button" | "submit" | "reset"), Some(value)) =
1601 (tagname, type_attr, value)
1602 {
1603 let value = value.to_string();
1604 let id = self.create_text_node(&value);
1605 self.append_children(target_id, &[id]);
1606 return;
1607 }
1608 #[cfg(feature = "file-input")]
1609 if let ("input", Some("file")) = (tagname, type_attr) {
1610 let button_id = self.create_element(
1611 qual_name!("button", html),
1612 vec![
1613 Attribute {
1614 name: qual_name!("type", html),
1615 value: "button".into(),
1616 },
1617 Attribute {
1618 name: qual_name!("tabindex", html),
1619 value: "-1".into(),
1620 },
1621 ],
1622 );
1623 let label_id = self.create_element(qual_name!("label", html), vec![]);
1624 let text_id = self.create_text_node("No File Selected");
1625 let button_text_id = self.create_text_node("Browse");
1626 self.append_children(target_id, &[button_id, label_id]);
1627 self.append_children(label_id, &[text_id]);
1628 self.append_children(button_id, &[button_text_id]);
1629 }
1630 }
1631}
1632
1633fn set_input_checked_state(element: &mut ElementData, value: String) {
1635 let Ok(checked) = value.parse() else {
1636 return;
1637 };
1638 match element.special_data {
1639 SpecialElementData::CheckboxInput(ref mut checked_mut) => *checked_mut = checked,
1640 SpecialElementData::None => element.attrs.push(Attribute {
1645 name: qual_name!("checked", html),
1646 value: checked.to_string().into(),
1647 }),
1648 _ => {}
1649 }
1650}
1651
1652pub struct ViewportMut<'doc> {
1655 doc: &'doc mut BaseDocument,
1656 initial_viewport: Viewport,
1657}
1658impl ViewportMut<'_> {
1659 pub fn new(doc: &mut BaseDocument) -> ViewportMut<'_> {
1660 let initial_viewport = doc.viewport.clone();
1661 ViewportMut {
1662 doc,
1663 initial_viewport,
1664 }
1665 }
1666}
1667impl Deref for ViewportMut<'_> {
1668 type Target = Viewport;
1669
1670 fn deref(&self) -> &Self::Target {
1671 &self.doc.viewport
1672 }
1673}
1674impl DerefMut for ViewportMut<'_> {
1675 fn deref_mut(&mut self) -> &mut Self::Target {
1676 &mut self.doc.viewport
1677 }
1678}
1679impl Drop for ViewportMut<'_> {
1680 fn drop(&mut self) {
1681 if self.doc.viewport == self.initial_viewport {
1682 return;
1683 }
1684
1685 self.doc.set_stylist_device(make_device(
1686 &self.doc.viewport,
1687 self.doc.media_type.clone(),
1688 self.doc.font_ctx.clone(),
1689 ));
1690 self.doc.scroll_viewport_by(0.0, 0.0); let scale_has_changed =
1693 self.doc.viewport().scale_f64() != self.initial_viewport.scale_f64();
1694 if scale_has_changed {
1695 self.doc.invalidate_inline_contexts();
1696 self.doc.shell_provider.request_redraw();
1697 }
1698 }
1699}
1700
1701fn html_tag(name: &str) -> markup5ever::QualName {
1703 markup5ever::QualName::new(None, markup5ever::ns!(html), name.into())
1704}
1705
1706#[cfg(test)]
1707mod test {
1708 use style::media_queries::MediaType;
1709 use style_dom::ElementState;
1710
1711 use std::sync::{
1712 Arc,
1713 atomic::{AtomicUsize, Ordering},
1714 };
1715
1716 use blitz_traits::shell::{ColorScheme, ShellProvider, Viewport};
1717
1718 use crate::{
1719 Attribute, BaseDocument, DocumentConfig, ElementData, NodeData, NodeId, qual_name,
1720 };
1721
1722 #[test]
1723 fn media_type_defaults_to_screen() {
1724 let mut document = BaseDocument::new(DocumentConfig::default());
1725 assert_eq!(*document.media_type(), MediaType::screen());
1726 assert_eq!(document.stylist_device().media_type(), MediaType::screen());
1727 }
1728
1729 #[test]
1730 fn media_type_honors_config() {
1731 let mut document = BaseDocument::new(DocumentConfig {
1732 media_type: Some(MediaType::print()),
1733 ..Default::default()
1734 });
1735 assert_eq!(*document.media_type(), MediaType::print());
1736 assert_eq!(document.stylist_device().media_type(), MediaType::print());
1737 }
1738
1739 #[test]
1740 fn set_media_type_updates_stylist_device() {
1741 let mut document = BaseDocument::new(DocumentConfig::default());
1742 assert_eq!(document.stylist_device().media_type(), MediaType::screen());
1743
1744 document.set_media_type(MediaType::print());
1745 assert_eq!(*document.media_type(), MediaType::print());
1746 assert_eq!(document.stylist_device().media_type(), MediaType::print());
1747 }
1748
1749 #[test]
1750 fn removing_a_node_forgets_it_as_focused_and_pressed() {
1751 let mut document = BaseDocument::new(DocumentConfig::default());
1757 let button = document.create_node(NodeData::Element(Box::new(ElementData::new(
1758 qual_name!("button"),
1759 Vec::new(),
1760 ))));
1761 let root = document.root_node().id;
1762
1763 let mut mutator = document.mutate();
1764 mutator.append_children(root, &[button]);
1765 drop(mutator);
1766
1767 document.set_focus_to(button);
1768 document.set_mousedown_node_id(Some(button));
1769 assert_eq!(document.get_focussed_node_id(), Some(button));
1770 assert_eq!(document.mousedown_node_id, Some(button));
1771
1772 let mut mutator = document.mutate();
1773 mutator.remove_node(button);
1774 drop(mutator);
1775
1776 assert_eq!(
1777 document.get_focussed_node_id(),
1778 None,
1779 "a removed node must not stay focused"
1780 );
1781 assert_eq!(
1782 document.mousedown_node_id, None,
1783 "a removed node must not stay the pressed node"
1784 );
1785 }
1786
1787 #[test]
1788 fn dropping_a_child_clears_a_hidden_retained_layout_edge() {
1789 let mut document = BaseDocument::new(DocumentConfig {
1790 viewport: Some(Viewport::new(800, 600, 1.0, ColorScheme::Light)),
1791 ..Default::default()
1792 });
1793 let root = document.root_node().id;
1794 let (parent, child) = {
1795 let mut mutator = document.mutate();
1796 let parent = mutator.create_element(qual_name!("div"), vec![]);
1797 let child = mutator.create_element(qual_name!("button"), vec![]);
1798 mutator.set_style_property(parent, "width", "200px");
1799 mutator.set_style_property(parent, "height", "100px");
1800 mutator.append_children(parent, &[child]);
1801 mutator.append_children(root, &[parent]);
1802 (parent, child)
1803 };
1804
1805 document.resolve(0.0);
1806 {
1807 let mut mutator = document.mutate();
1808 mutator.set_style_property(parent, "display", "none");
1809 }
1810 document.resolve(0.0);
1811 assert!(
1812 document.nodes[parent]
1813 .layout_children
1814 .borrow()
1815 .as_ref()
1816 .is_some_and(|children| children.contains(&child)),
1817 "the hidden subtree should retain the layout edge that makes this regression possible"
1818 );
1819
1820 document.mutate().remove_and_drop_node(child);
1821 assert!(document.get_node(child).is_none(), "the child was freed");
1822 assert!(
1823 document.nodes[parent].layout_children.borrow().is_none(),
1824 "the surviving layout parent must not retain the freed key"
1825 );
1826
1827 document.resolve(0.0);
1829 }
1830
1831 #[test]
1832 fn mutator_remove_disabled() {
1833 let mut document = BaseDocument::new(DocumentConfig::default());
1834 let id = document.create_node(NodeData::Element(Box::new(ElementData::new(
1835 qual_name!("button"),
1836 vec![Attribute {
1837 name: qual_name!("disabled"),
1838 value: "".into(),
1839 }],
1840 ))));
1841
1842 let node = document.get_node(id).unwrap();
1843 assert!(
1844 node.element_state().contains(ElementState::DISABLED),
1845 "form node is disabled"
1846 );
1847 assert!(
1848 !node.element_state().contains(ElementState::ENABLED),
1849 "form node is not enabled yet"
1850 );
1851
1852 let mut mutator = document.mutate();
1853 mutator.clear_attribute(id, qual_name!("disabled"));
1854 drop(mutator);
1855
1856 let node = document.get_node(id).unwrap();
1857 assert!(
1858 !node.element_state().contains(ElementState::DISABLED),
1859 "form node is no longer disabled"
1860 );
1861 assert!(
1862 node.element_state().contains(ElementState::ENABLED),
1863 "form node is enabled"
1864 );
1865 }
1866
1867 #[test]
1868 fn mutator_set_disabled() {
1869 let mut document = BaseDocument::new(DocumentConfig::default());
1870 let id = document.create_node(NodeData::Element(Box::new(ElementData::new(
1871 qual_name!("button"),
1872 vec![],
1873 ))));
1874
1875 let node = document.get_node(id).unwrap();
1876 assert!(
1877 !node.element_state().contains(ElementState::DISABLED),
1878 "form node is not disabled"
1879 );
1880 assert!(
1881 node.element_state().contains(ElementState::ENABLED),
1882 "form node is enabled"
1883 );
1884
1885 let mut mutator = document.mutate();
1886 mutator.set_attribute(id, qual_name!("disabled"), "");
1887 drop(mutator);
1888
1889 let node = document.get_node(id).unwrap();
1890
1891 assert!(
1892 node.element_state().contains(ElementState::DISABLED),
1893 "form node is disabled"
1894 );
1895 assert!(
1896 !node.element_state().contains(ElementState::ENABLED),
1897 "form node is no longer enabled enabled"
1898 );
1899 }
1900
1901 #[test]
1902 fn mutator_set_disabled_invalid_node() {
1903 let mut document = BaseDocument::new(DocumentConfig::default());
1904 let id = document.create_node(NodeData::Element(Box::new(ElementData::new(
1905 qual_name!("a"),
1906 vec![],
1907 ))));
1908
1909 let node = document.get_node(id).unwrap();
1910 assert!(
1911 !node.element_state().contains(ElementState::DISABLED),
1912 "form node is not disabled"
1913 );
1914 assert!(
1915 !node.element_state().contains(ElementState::ENABLED),
1916 "form node is enabled"
1917 );
1918
1919 let mut mutator = document.mutate();
1920 mutator.set_attribute(id, qual_name!("disabled"), "");
1921 drop(mutator);
1922
1923 let node = document.get_node(id).unwrap();
1924 assert!(
1925 !node.element_state().contains(ElementState::DISABLED),
1926 "form node is not disabled"
1927 );
1928 assert!(
1929 !node.element_state().contains(ElementState::ENABLED),
1930 "form node is enabled"
1931 );
1932 }
1933
1934 #[test]
1935 fn mutator_id_attribute_updates_id_map() {
1936 let mut document = BaseDocument::new(DocumentConfig::default());
1937 let root_id = document.root_node().id;
1938
1939 let node_id = {
1940 let mut mutator = document.mutate();
1941 let node_id = mutator.create_element(
1942 qual_name!("div"),
1943 vec![Attribute {
1944 name: qual_name!("id"),
1945 value: "old".into(),
1946 }],
1947 );
1948 mutator.append_children(root_id, &[node_id]);
1949 node_id
1950 };
1951 assert_eq!(document.get_element_by_id("old"), Some(node_id));
1952
1953 {
1954 let mut mutator = document.mutate();
1955 mutator.set_attribute(node_id, qual_name!("id"), "new");
1956 }
1957 assert_eq!(document.get_element_by_id("new"), Some(node_id));
1958 assert_eq!(document.get_element_by_id("old"), None);
1959
1960 {
1961 let mut mutator = document.mutate();
1962 mutator.clear_attribute(node_id, qual_name!("id"));
1963 }
1964 assert_eq!(document.get_element_by_id("new"), None);
1965 }
1966
1967 #[test]
1968 fn get_element_by_id_duplicate_ids_first_in_tree_order_wins() {
1969 let mut document = BaseDocument::new(DocumentConfig::default());
1970 let root_id = document.root_node().id;
1971
1972 let (first_id, second_id) = {
1973 let mut mutator = document.mutate();
1974 let first_id = mutator.create_element(qual_name!("div"), vec![]);
1975 let second_id = mutator.create_element(qual_name!("div"), vec![]);
1976 mutator.append_children(root_id, &[first_id, second_id]);
1977 mutator.set_attribute(second_id, qual_name!("id"), "dup");
1980 mutator.set_attribute(first_id, qual_name!("id"), "dup");
1981 (first_id, second_id)
1982 };
1983 assert_eq!(document.get_element_by_id("dup"), Some(first_id));
1984
1985 {
1986 let mut mutator = document.mutate();
1987 mutator.remove_node(first_id);
1988 }
1989 assert_eq!(document.get_element_by_id("dup"), Some(second_id));
1990 }
1991
1992 #[derive(Default)]
1993 struct RedrawShell {
1994 redraw_requests: AtomicUsize,
1995 }
1996
1997 impl ShellProvider for RedrawShell {
1998 fn request_redraw(&self) {
1999 self.redraw_requests.fetch_add(1, Ordering::Relaxed);
2000 }
2001 }
2002
2003 #[test]
2004 fn mutator_requests_redraw_only_after_mutation() {
2005 let shell = Arc::new(RedrawShell::default());
2006 let mut document = BaseDocument::new(DocumentConfig {
2007 shell_provider: Some(shell.clone()),
2008 ..Default::default()
2009 });
2010 let root_id = document.root_node().id;
2011
2012 {
2013 let mut mutator = document.mutate();
2014 let parent_id = mutator.create_element(qual_name!("div"), vec![]);
2015 let child_id = mutator.create_element(qual_name!("span"), vec![]);
2016 mutator.append_children(parent_id, &[child_id]);
2017 mutator.remove_and_drop_all_children(parent_id);
2018 mutator.set_attribute(parent_id, qual_name!("id"), "detached");
2019 }
2020 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 0);
2021
2022 {
2023 let mutator = document.mutate();
2024 assert_eq!(mutator.child_ids(root_id).len(), 0);
2025 }
2026
2027 {
2028 let mut mutator = document.mutate();
2029 let node_id = mutator.create_element(qual_name!("div"), vec![]);
2030 mutator.append_children(root_id, &[node_id]);
2031 mutator.set_attribute(node_id, qual_name!("id"), "in-document");
2032 }
2033 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 1);
2034
2035 {
2036 let mut mutator = document.mutate();
2037 let parent_id = mutator.create_element(qual_name!("div"), vec![]);
2038 let child_id = mutator.create_element(qual_name!("span"), vec![]);
2039 mutator.append_children(root_id, &[parent_id]);
2040 mutator.append_children(parent_id, &[child_id]);
2041 mutator.remove_and_drop_all_children(parent_id);
2042 }
2043 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 2);
2044
2045 {
2046 let mut mutator = document.mutate();
2047 let parent_id = mutator.create_element(qual_name!("div"), vec![]);
2048 let child_id = mutator.create_element(qual_name!("span"), vec![]);
2049 let detached_target_id = mutator.create_element(qual_name!("div"), vec![]);
2050 mutator.append_children(root_id, &[parent_id]);
2051 mutator.append_children(parent_id, &[child_id]);
2052 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 2);
2053 mutator.append_children(detached_target_id, &[child_id]);
2054 }
2055 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 3);
2056 }
2057
2058 #[test]
2059 fn moving_subtree_out_of_document_clears_in_document_flag() {
2060 let shell = Arc::new(RedrawShell::default());
2061 let mut document = BaseDocument::new(DocumentConfig {
2062 shell_provider: Some(shell.clone()),
2063 ..Default::default()
2064 });
2065 let root_id = document.root_node().id;
2066 let (child_id, grandchild_id, detached_parent_id) = {
2067 let mut mutator = document.mutate();
2068 let in_document_parent_id = mutator.create_element(qual_name!("div"), vec![]);
2069 let child_id = mutator.create_element(qual_name!("div"), vec![]);
2070 let grandchild_id = mutator.create_element(qual_name!("span"), vec![]);
2071 let detached_parent_id = mutator.create_element(qual_name!("section"), vec![]);
2072 mutator.append_children(root_id, &[in_document_parent_id]);
2073 mutator.append_children(in_document_parent_id, &[child_id]);
2074 mutator.append_children(child_id, &[grandchild_id]);
2075 (child_id, grandchild_id, detached_parent_id)
2076 };
2077 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 1);
2078 assert!(document.get_node(child_id).unwrap().flags.is_in_document());
2079 assert!(
2080 document
2081 .get_node(grandchild_id)
2082 .unwrap()
2083 .flags
2084 .is_in_document()
2085 );
2086
2087 {
2088 let mut mutator = document.mutate();
2089 mutator.append_children(detached_parent_id, &[child_id]);
2090 }
2091 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 2);
2092 assert!(!document.get_node(child_id).unwrap().flags.is_in_document());
2093 assert!(
2094 !document
2095 .get_node(grandchild_id)
2096 .unwrap()
2097 .flags
2098 .is_in_document()
2099 );
2100
2101 {
2102 let mut mutator = document.mutate();
2103 mutator.set_attribute(child_id, qual_name!("id"), "detached");
2104 }
2105 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 2);
2106
2107 {
2108 let mut mutator = document.mutate();
2109 mutator.append_children(root_id, &[child_id]);
2110 }
2111 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 3);
2112 assert!(document.get_node(child_id).unwrap().flags.is_in_document());
2113 assert!(
2114 document
2115 .get_node(grandchild_id)
2116 .unwrap()
2117 .flags
2118 .is_in_document()
2119 );
2120 }
2121
2122 #[test]
2134 fn a_paint_only_restyle_refreshes_the_calc_the_taffy_style_points_at() {
2135 use style::servo_arc::Arc as ServoArc;
2136
2137 let mut document = BaseDocument::new(DocumentConfig {
2138 viewport: Some(Viewport::new(800, 600, 1.0, ColorScheme::Light)),
2139 ..Default::default()
2140 });
2141 let root_id = document.root_node().id;
2142
2143 let (header_id, chip_id) = {
2144 let mut mutator = document.mutate();
2145 let header_id = mutator.create_element(qual_name!("div"), vec![]);
2146 let chip_id = mutator.create_element(qual_name!("span"), vec![]);
2147 mutator.set_style_property(header_id, "position", "relative");
2148 mutator.set_style_property(header_id, "width", "800px");
2149 mutator.set_style_property(header_id, "height", "60px");
2150 mutator.set_style_property(chip_id, "position", "absolute");
2151 mutator.set_style_property(chip_id, "max-width", "calc(100% - 24px)");
2152 mutator.set_style_property(chip_id, "color", "rgb(1, 2, 3)");
2153 mutator.append_children(header_id, &[chip_id]);
2154 mutator.append_children(root_id, &[header_id]);
2155 (header_id, chip_id)
2156 };
2157
2158 document.resolve(0.0);
2159
2160 {
2166 let mut mutator = document.mutate();
2167 mutator.set_style_property(header_id, "color", "rgb(4, 5, 6)");
2168 }
2169 document.resolve(0.0);
2170
2171 let node = document.get_node(chip_id).unwrap();
2172 let stylo_data = node.stylo_element_data_opt().and_then(|data| data.get());
2173 let primary = stylo_data
2174 .as_ref()
2175 .and_then(|data| data.styles.get_primary())
2176 .expect("the chip is styled");
2177 let source = node
2178 .style_source_opt()
2179 .expect("a styled node records the computed values its taffy style was built from");
2180
2181 assert!(
2182 ServoArc::ptr_eq(primary, source),
2183 "the cached taffy style still points into computed values that a restyle replaced, \
2184 so every calc() in it is a dangling pointer",
2185 );
2186 }
2187
2188 #[test]
2189 fn style_property_updates_nested_layout() {
2190 let mut document = BaseDocument::new(DocumentConfig {
2191 viewport: Some(Viewport::new(800, 600, 1.0, ColorScheme::Light)),
2192 ..Default::default()
2193 });
2194 let root_id = document.root_node().id;
2195
2196 let mover_id = {
2197 let mut mutator = document.mutate();
2198 let parent_id = mutator.create_element(qual_name!("div"), vec![]);
2199 let mover_id = mutator.create_element(qual_name!("div"), vec![]);
2200 mutator.set_style_property(parent_id, "position", "relative");
2201 mutator.set_style_property(parent_id, "width", "800px");
2202 mutator.set_style_property(parent_id, "height", "600px");
2203 mutator.set_style_property(mover_id, "position", "absolute");
2204 mutator.set_style_property(mover_id, "left", "0px");
2205 mutator.set_style_property(mover_id, "top", "0px");
2206 mutator.append_children(parent_id, &[mover_id]);
2207 mutator.append_children(root_id, &[parent_id]);
2208 mover_id
2209 };
2210
2211 document.resolve(0.0);
2212 assert_eq!(
2213 document
2214 .get_node(mover_id)
2215 .unwrap()
2216 .final_layout()
2217 .location
2218 .x,
2219 0.0
2220 );
2221
2222 {
2223 let mut mutator = document.mutate();
2224 mutator.set_style_property(mover_id, "left", "120px");
2225 }
2226
2227 document.resolve(0.0);
2228 assert_eq!(
2229 document
2230 .get_node(mover_id)
2231 .unwrap()
2232 .final_layout()
2233 .location
2234 .x,
2235 120.0
2236 );
2237 }
2238
2239 fn doc_with_a_comment() -> (BaseDocument, NodeId, NodeId, NodeId) {
2242 let mut doc = BaseDocument::new(DocumentConfig {
2243 viewport: Some(Viewport::new(400, 300, 1.0, ColorScheme::Light)),
2244 ..Default::default()
2245 });
2246 let root_id = doc.root_node().id;
2247
2248 let mut mutr = doc.mutate();
2249 let html = mutr.create_element(qual_name!("html"), vec![]);
2250 let body = mutr.create_element(qual_name!("body"), vec![]);
2251 let container = mutr.create_element(qual_name!("div"), vec![]);
2252 let text = mutr.create_text_node("text");
2253 let comment = mutr.create_comment_node("comment");
2254 mutr.append_children(container, &[text, comment]);
2255 mutr.append_children(body, &[container]);
2256 mutr.append_children(html, &[body]);
2257 mutr.append_children(root_id, &[html]);
2258 drop(mutr);
2259
2260 doc.resolve(0.0);
2261 (doc, container, text, comment)
2262 }
2263
2264 #[test]
2268 fn setting_a_comments_data_writes_the_contents() {
2269 let (mut doc, _container, _text, comment) = doc_with_a_comment();
2270
2271 doc.mutate().set_node_text(comment, "rewritten");
2272
2273 let NodeData::Comment { contents } = &doc.get_node(comment).unwrap().data else {
2274 panic!("expected a comment node");
2275 };
2276 assert_eq!(contents, "rewritten");
2277 }
2278
2279 #[test]
2286 fn setting_a_comments_data_does_not_dirty_layout() {
2287 let (mut doc, container, text, comment) = doc_with_a_comment();
2288
2289 let container_damage_before = doc.get_node(container).unwrap().damage();
2290 let comment_damage_before = doc.get_node(comment).unwrap().damage();
2291
2292 doc.mutate().set_node_text(comment, "rewritten");
2293
2294 assert_eq!(
2295 doc.get_node(comment).unwrap().damage(),
2296 comment_damage_before,
2297 "writing a comment's data damaged the comment"
2298 );
2299 assert_eq!(
2300 doc.get_node(container).unwrap().damage(),
2301 container_damage_before,
2302 "writing a comment's data damaged its parent, scheduling a relayout \
2303 for a change that cannot affect a pixel"
2304 );
2305
2306 doc.mutate().set_node_text(text, "rewritten");
2307 assert_ne!(
2308 doc.get_node(container).unwrap().damage(),
2309 container_damage_before,
2310 "a text write should damage the parent, so the assertions above can fail"
2311 );
2312 }
2313
2314 #[test]
2317 fn rewriting_a_comment_with_its_own_contents_is_inert() {
2318 let (mut doc, container, _text, comment) = doc_with_a_comment();
2319 let container_damage_before = doc.get_node(container).unwrap().damage();
2320
2321 doc.mutate().set_node_text(comment, "comment");
2322
2323 let NodeData::Comment { contents } = &doc.get_node(comment).unwrap().data else {
2324 panic!("expected a comment node");
2325 };
2326 assert_eq!(contents, "comment");
2327 assert_eq!(
2328 doc.get_node(container).unwrap().damage(),
2329 container_damage_before
2330 );
2331 }
2332}