1use blitz_traits::node_id::NodeId;
2use rustc_hash::FxHashSet;
3use std::collections::HashSet;
4use std::mem;
5use std::ops::{Deref, DerefMut};
6
7use crate::document::make_device;
8use crate::layout::damage::ALL_DAMAGE;
9use crate::net::{ImageHandler, ResourceHandler, StylesheetHandler};
10use crate::node::{CanvasData, NodeFlags, SpecialElementData};
11use crate::util::ImageType;
12use crate::{
13 Attribute, BaseDocument, Document, ElementData, Node, NodeData, QualName, local_name, qual_name,
14};
15use blitz_traits::shell::Viewport;
16use markup5ever::ns;
17use selectors::matching::ElementSelectorFlags;
18use style::Atom;
19use style::invalidation::element::restyle_hints::RestyleHint;
20use style::stylesheets::OriginSet;
21use thin_vec::ThinVec;
22
23macro_rules! tag_and_attr {
24 ($tag:tt, $attr:tt) => {
25 (&local_name!($tag), &local_name!($attr))
26 };
27}
28
29#[derive(Debug, Clone)]
30pub enum AppendTextErr {
31 NotTextNode,
33}
34
35enum SpecialOp {
38 LoadImage(NodeId),
39 LoadIframe(NodeId),
40 LoadStylesheet(NodeId),
41 UnloadStylesheet(NodeId),
42 LoadCustomPaintSource(NodeId),
43 ProcessButtonInput(NodeId),
44 UnloadSubDocument(NodeId),
45 #[cfg(feature = "custom-widget")]
46 UnloadCustomWidget(NodeId),
47 #[cfg(feature = "shadow-dom")]
48 UpgradeCustomElement(NodeId),
49 #[cfg(feature = "shadow-dom")]
50 DisconnectCustomElement(NodeId),
51}
52
53pub struct DocumentMutator<'doc> {
54 pub doc: &'doc mut BaseDocument,
57
58 eager_op_queue: Vec<SpecialOp>,
59
60 title_node: Option<NodeId>,
62 style_nodes: HashSet<NodeId>,
63 form_nodes: HashSet<NodeId>,
64
65 recompute_is_animating: bool,
67
68 mutations_occurred: bool,
70
71 #[cfg(feature = "shadow-dom")]
74 custom_element_attr_changes: Vec<(NodeId, QualName, Option<String>, Option<String>)>,
75
76 #[cfg(feature = "autofocus")]
78 node_to_autofocus: Option<NodeId>,
79}
80
81impl Drop for DocumentMutator<'_> {
82 fn drop(&mut self) {
83 self.flush(); if self.mutations_occurred {
85 self.doc.shell_provider.request_redraw();
86 }
87 }
88}
89
90impl DocumentMutator<'_> {
91 pub fn new<'doc>(doc: &'doc mut BaseDocument) -> DocumentMutator<'doc> {
92 DocumentMutator {
93 doc,
94 eager_op_queue: Vec::new(),
95 title_node: None,
96 style_nodes: HashSet::new(),
97 form_nodes: HashSet::new(),
98 recompute_is_animating: false,
99 mutations_occurred: false,
100 #[cfg(feature = "shadow-dom")]
101 custom_element_attr_changes: Vec::new(),
102 #[cfg(feature = "autofocus")]
103 node_to_autofocus: None,
104 }
105 }
106
107 fn restyle_parent_for_attribute_change(&mut self, parent_id: Option<NodeId>) {
110 let Some(parent_id) = parent_id else {
111 return;
112 };
113 let flags = self.doc.nodes[parent_id].selector_flags().get();
114 let child_dependent = ElementSelectorFlags::HAS_SLOW_SELECTOR
115 | ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS
116 | ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR
117 | ElementSelectorFlags::HAS_EMPTY_SELECTOR
118 | ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR
119 | ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING
120 | ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING;
121 if flags.intersects(child_dependent) {
122 if let Some(mut data) = self.doc.nodes[parent_id]
123 .stylo_element_data_opt_mut()
124 .and_then(|s| s.get_mut())
125 {
126 data.hint |= RestyleHint::restyle_subtree();
127 }
128 }
129 }
130
131 pub fn node_has_parent(&self, node_id: NodeId) -> bool {
134 self.doc.nodes[node_id].parent.is_some()
135 }
136
137 pub fn previous_sibling_id(&self, node_id: NodeId) -> Option<NodeId> {
138 self.doc.nodes[node_id].backward(1).map(|node| node.id)
139 }
140
141 pub fn next_sibling_id(&self, node_id: NodeId) -> Option<NodeId> {
142 self.doc.nodes[node_id].forward(1).map(|node| node.id)
143 }
144
145 pub fn parent_id(&self, node_id: NodeId) -> Option<NodeId> {
146 self.doc.nodes[node_id].parent
147 }
148
149 pub fn last_child_id(&self, node_id: NodeId) -> Option<NodeId> {
150 self.doc.nodes[node_id].children.last().copied()
151 }
152
153 pub fn child_ids(&self, node_id: NodeId) -> ThinVec<NodeId> {
154 self.doc.nodes[node_id].children.clone()
155 }
156
157 pub fn element_name(&self, node_id: NodeId) -> Option<&QualName> {
158 self.doc.nodes[node_id].element_data().map(|el| &el.name)
159 }
160
161 pub fn node_at_path(&self, start_node_id: NodeId, path: &[u8]) -> NodeId {
162 let mut current = &self.doc.nodes[start_node_id];
163 for i in path {
164 let new_id = current.children[*i as usize];
165 current = &self.doc.nodes[new_id];
166 }
167 current.id
168 }
169
170 pub fn create_comment_node(&mut self, contents: &str) -> NodeId {
173 self.doc.create_node(NodeData::Comment {
174 contents: contents.to_string(),
175 })
176 }
177
178 pub fn create_text_node(&mut self, text: &str) -> NodeId {
179 self.doc.create_text_node(text)
180 }
181
182 pub fn create_document_fragment(&mut self) -> NodeId {
191 self.doc.create_node(NodeData::DocumentFragment)
192 }
193
194 fn create_template_contents(&mut self) -> NodeId {
197 let contents = self.create_document_fragment();
198 let owner = self.doc.template_contents_owner();
199 self.doc.nodes[contents].owner_document = Some(owner);
200 contents
201 }
202
203 pub fn create_html_document(&mut self, title: &str) -> NodeId {
216 let doc_id = self.create_document("text/html");
217
218 let html = self.create_element(html_tag("html"), Vec::new());
219 let head = self.create_element(html_tag("head"), Vec::new());
220 let title_el = self.create_element(html_tag("title"), Vec::new());
221 let body = self.create_element(html_tag("body"), Vec::new());
222
223 if !title.is_empty() {
226 let text = self.create_text_node(title);
227 self.append_children(title_el, &[text]);
228 }
229
230 self.append_children(head, &[title_el]);
231 self.append_children(html, &[head, body]);
232 self.append_children(doc_id, &[html]);
233
234 doc_id
235 }
236
237 pub fn create_document(&mut self, content_type: &'static str) -> NodeId {
239 let mut data = crate::node::DocumentData::default();
240 data.content_type = content_type;
241 self.doc.create_node(NodeData::Document(Box::new(data)))
242 }
243
244 pub fn adopt_node(&mut self, node_id: NodeId, document_id: NodeId) {
246 self.doc.adopt_script_subtree(node_id, document_id);
247 }
248
249 pub fn create_element(&mut self, name: QualName, attrs: Vec<Attribute>) -> NodeId {
250 let is_template = name.ns == ns!(html) && name.local == local_name!("template");
251 let mut data = ElementData::new(name, attrs);
252 data.flush_style_attribute(self.doc.guard(), &self.doc.url.url_extra_data());
253 if is_template {
254 data.template_contents = Some(self.create_template_contents());
255 }
256
257 let id = self.doc.create_node(NodeData::Element(Box::new(data)));
258 let node = self.doc.get_node_mut(id).unwrap();
259
260 *node.stylo_element_data_mut().ensure_init_mut() = style::data::ElementData {
262 damage: ALL_DAMAGE,
263 ..Default::default()
264 };
265
266 id
267 }
268
269 pub fn deep_clone_node(&mut self, node_id: NodeId) -> NodeId {
270 self.doc.deep_clone_node(node_id)
271 }
272
273 pub fn template_contents(&mut self, node_id: NodeId) -> NodeId {
275 if let Some(contents) = self.doc.nodes[node_id]
276 .element_data()
277 .and_then(|element| element.template_contents)
278 {
279 return contents;
280 }
281 let contents = self.create_template_contents();
282 self.doc.nodes[node_id]
283 .element_data_mut()
284 .expect("template contents require an element")
285 .template_contents = Some(contents);
286 contents
287 }
288
289 pub fn set_node_text(&mut self, node_id: NodeId, value: &str) {
292 if matches!(
293 self.doc.nodes[node_id].markup.as_deref(),
294 Some(crate::node::MarkupNode::Doctype { .. })
295 ) {
296 return;
297 }
298 if !self.doc.live_ranges.is_empty()
299 && let Some(length) = self
300 .doc
301 .range_character_data(node_id)
302 .map(|text| text.encode_utf16().count())
303 {
304 self.doc
305 .range_replace_data(node_id, 0, length, value.encode_utf16().count());
306 }
307 self.set_node_text_without_range_update(node_id, value);
308 }
309
310 pub fn replace_character_data(
312 &mut self,
313 node_id: NodeId,
314 offset: usize,
315 count: usize,
316 value: &str,
317 ) -> Result<(), &'static str> {
318 let old = self
319 .doc
320 .range_character_data(node_id)
321 .ok_or("InvalidNodeTypeError")?;
322 let length = old.encode_utf16().count();
323 if offset > length {
324 return Err("IndexSizeError");
325 }
326 let count = count.min(length - offset);
327 let mut replacement = crate::range::utf16_slice(old, 0, offset);
328 replacement.push_str(value);
329 replacement.push_str(&crate::range::utf16_slice(old, offset + count, length));
330 self.doc
331 .range_replace_data(node_id, offset, count, value.encode_utf16().count());
332 self.set_node_text_without_range_update(node_id, &replacement);
333 Ok(())
334 }
335
336 pub fn split_text(&mut self, node_id: NodeId, offset: usize) -> Result<NodeId, &'static str> {
337 let node = self.doc.get_node(node_id).ok_or("InvalidNodeTypeError")?;
338 let NodeData::Text(text) = &node.data else {
339 return Err("InvalidNodeTypeError");
340 };
341 let length = text.content.encode_utf16().count();
342 if offset > length {
343 return Err("IndexSizeError");
344 }
345 let before = crate::range::utf16_slice(&text.content, 0, offset);
346 let after = crate::range::utf16_slice(&text.content, offset, length);
347 let position = node
348 .parent
349 .and_then(|parent| Some((parent, self.doc.get_node(parent)?.index_of_child(node_id)?)));
350 let new_id = self.create_text_node(&after);
351 if position.is_some() {
352 self.insert_nodes_after(node_id, &[new_id]);
353 }
354 self.doc.range_split_text(node_id, new_id, offset, position);
355 self.set_node_text_without_range_update(node_id, &before);
356 Ok(new_id)
357 }
358
359 fn set_node_text_without_range_update(&mut self, node_id: NodeId, value: &str) {
360 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
361 if node_is_in_document && self.doc.is_recording_mutations() {
362 let old_value = match &self.doc.nodes[node_id].data {
363 NodeData::Comment { contents } => Some(contents.clone()),
364 NodeData::Text(text) => Some(text.content.clone()),
365 _ => None,
366 };
367 if let Some(old_value) = old_value.filter(|old| old != value) {
368 self.doc.record_mutation(crate::DomMutation::CharacterData {
369 target: node_id,
370 old_value,
371 });
372 }
373 }
374 let node = &mut self.doc.nodes[node_id];
375
376 if let NodeData::Comment { ref mut contents } = node.data {
387 if contents != value {
388 contents.clear();
389 contents.push_str(value);
390 }
391 return;
392 }
393
394 let text = match node.data {
395 NodeData::Text(ref mut text) => text,
396 _ => return,
398 };
399
400 let changed = text.content != value;
401 if changed {
402 self.mutations_occurred |= node_is_in_document;
403 text.content.clear();
404 text.content.push_str(value);
405 node.insert_damage(ALL_DAMAGE);
406 node.mark_ancestors_dirty();
409 let parent_id = node.parent;
410
411 if let Some(parent_id) = parent_id {
414 let parent = &mut self.doc.nodes[parent_id];
415 parent.insert_damage(ALL_DAMAGE);
416 }
417
418 self.maybe_record_node(parent_id);
419 }
420 }
421
422 pub fn append_text_to_node(
423 &mut self,
424 node_id: NodeId,
425 text: &str,
426 ) -> Result<(), AppendTextErr> {
427 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
428 let node = &mut self.doc.nodes[node_id];
429 node.insert_damage(ALL_DAMAGE);
430 node.mark_ancestors_dirty();
431 let parent = node.parent;
432 match node.text_data_mut() {
433 Some(data) => {
434 data.content += text;
435 self.mutations_occurred |= node_is_in_document;
436 if let Some(parent) = parent
440 && self.doc.nodes[parent]
441 .data
442 .downcast_element()
443 .is_some_and(|element| element.name.local.as_ref() == "style")
444 {
445 self.style_nodes.insert(parent);
446 }
447 Ok(())
448 }
449 None => Err(AppendTextErr::NotTextNode),
450 }
451 }
452
453 pub fn add_attrs_if_missing(&mut self, node_id: NodeId, attrs: Vec<Attribute>) {
454 let node = &mut self.doc.nodes[node_id];
455 node.insert_damage(ALL_DAMAGE);
456 let element_data = node.element_data_mut().expect("Not an element");
457
458 let existing_names = element_data
459 .attrs
460 .iter()
461 .map(|e| e.name.clone())
462 .collect::<HashSet<_>>();
463
464 for attr in attrs
465 .into_iter()
466 .filter(|attr| !existing_names.contains(&attr.name))
467 {
468 self.set_attribute(node_id, attr.name, &attr.value);
469 }
470 }
471
472 pub fn set_attribute(&mut self, node_id: NodeId, name: QualName, value: &str) {
473 #[cfg(feature = "shadow-dom")]
474 if name.local == local_name!("slot") || name.local == local_name!("name") {
475 self.doc.note_shadow_tree_change(node_id);
476 }
477 self.doc
478 .record_script_custom_element_attribute(node_id, &name, Some(value));
479 self.doc
480 .record_inline_handler_attribute(node_id, &name, Some(value));
481 self.doc.record_details_open(node_id, &name, true);
482 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
483 if node_is_in_document && self.doc.is_recording_mutations() {
484 if let Some(element) = self.doc.nodes[node_id].element_data() {
488 let old_value = element.attr(name.local.clone()).map(str::to_owned);
489 self.doc.record_mutation(crate::DomMutation::Attributes {
490 target: node_id,
491 name: name.local.to_string(),
492 old_value,
493 });
494 }
495 }
496 if node_is_in_document {
497 self.doc.snapshot_attribute(node_id, &name.local);
498
499 let renders_from_attributes = self.doc.nodes[node_id]
515 .data
516 .downcast_element()
517 .is_some_and(|el| {
518 el.name.ns == ns!(svg)
519 || crate::layout::replaced::is_replaced_element(&el.name.local)
520 });
521
522 let node = &mut self.doc.nodes[node_id];
523 node.set_dirty_descendants();
524 if let Some(mut data) = node.stylo_element_data_opt_mut().and_then(|s| s.get_mut()) {
525 data.hint |= RestyleHint::restyle_subtree();
526 if renders_from_attributes {
527 data.damage.insert(ALL_DAMAGE);
528 }
529 }
530
531 let parent = node.parent;
548 if let Some(parent_id) = parent {
549 let parent = &self.doc.nodes[parent_id];
550 let flags = parent.selector_flags().get();
551 let child_dependent = ElementSelectorFlags::HAS_SLOW_SELECTOR
552 | ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS
553 | ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR
554 | ElementSelectorFlags::HAS_EMPTY_SELECTOR
555 | ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR
556 | ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING
557 | ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING;
558
559 if flags.intersects(child_dependent) {
560 let parent = &mut self.doc.nodes[parent_id];
561 if let Some(mut data) = parent
562 .stylo_element_data_opt_mut()
563 .and_then(|s| s.get_mut())
564 {
565 data.hint |= RestyleHint::restyle_subtree();
566 }
567 }
568 }
569
570 self.doc.nodes[node_id].mark_ancestors_dirty();
574 }
575
576 if name.ns == ns!() && name.local == local_name!("id") {
577 if let Some(owner) = self.doc.nodes[node_id].owner_document {
578 let old_id = self.doc.nodes[node_id]
579 .element_data()
580 .and_then(|element| element.id.as_ref())
581 .map(ToString::to_string);
582 if let Some(old_id) = old_id {
583 self.doc
584 .remove_from_document_id_map(owner, &old_id, node_id);
585 }
586 self.doc.add_to_document_id_map(owner, value, node_id);
587 }
588 if node_is_in_document {
589 if let Some(old_id) = self.doc.nodes[node_id]
590 .element_data()
591 .map(|element| element.id.clone())
592 {
593 if let Some(old_id) = old_id {
594 self.doc.remove_from_id_map(&old_id, node_id);
595 }
596 self.doc.add_to_id_map(value, node_id);
597 }
598 }
599 }
600
601 let node = &mut self.doc.nodes[node_id];
602
603 let NodeData::Element(ref mut element) = node.data else {
604 return;
605 };
606
607 self.mutations_occurred |= node_is_in_document;
608 #[cfg(feature = "custom-widget")]
610 if let SpecialElementData::CustomWidget(widget_data) = &mut element.special_data {
611 let old_value = element.attrs.get(&name).as_ref().map(|attr| &*attr.value);
612 widget_data
613 .widget
614 .attribute_changed(&name.local, old_value, Some(value));
615 }
616
617 #[cfg(feature = "shadow-dom")]
620 if element.custom_element_data().is_some() {
621 let old_value = element
622 .attrs
623 .get(&name)
624 .as_ref()
625 .map(|attr| attr.value.to_string());
626 self.custom_element_attr_changes.push((
627 node_id,
628 name.clone(),
629 old_value,
630 Some(value.to_string()),
631 ));
632 }
633
634 element.attrs.set(name.clone(), value);
635 if name.ns != ns!() {
636 return;
637 }
638
639 if name.local == local_name!("tabindex")
644 || name.local == local_name!("href")
645 || name.local == local_name!("disabled")
646 {
647 element.flush_is_focussable();
648 }
649
650 let tag = &element.name.local;
651 let attr = &name.local;
652
653 if *attr == local_name!("id") {
654 element.id = Some(Atom::from(value))
655 }
656
657 if *attr == local_name!("value") {
658 if let Some(input_data) = element.text_input_data_mut() {
659 input_data.set_text(
661 &mut self.doc.font_ctx.lock().unwrap(),
662 &mut self.doc.layout_ctx,
663 value,
664 );
665 }
666 return;
667 }
668
669 if *attr == local_name!("style") {
670 element.flush_style_attribute(&self.doc.guard, &self.doc.url.url_extra_data());
671 node.mark_style_attr_updated();
672 return;
673 }
674
675 if *attr == local_name!("disabled") && element.can_be_disabled() {
676 node.disable();
677 return;
678 }
679
680 if !node.flags.is_in_document() {
683 return;
684 }
685
686 if (tag, attr) == tag_and_attr!("input", "checked") {
687 set_input_checked_state(element, value.to_string());
688 } else if (tag, attr) == tag_and_attr!("img", "src") {
689 self.load_image(node_id);
690 } else if (tag, attr) == tag_and_attr!("canvas", "src") {
691 self.load_custom_paint_src(node_id);
692 } else if (tag, attr) == tag_and_attr!("link", "href") {
693 self.load_linked_stylesheet(node_id);
694 } else if (tag, attr) == tag_and_attr!("iframe", "src")
695 || (tag, attr) == tag_and_attr!("iframe", "srcdoc")
696 {
697 self.load_iframe(node_id);
698 } else if (tag, attr) == tag_and_attr!("option", "selected") {
699 self.set_option_selected_state(node_id, true);
710 }
711 }
712
713 fn set_option_selected_state(&mut self, option_id: NodeId, selected: bool) {
716 let Some(select_id) = self.doc.option_owner_select(option_id) else {
717 return;
718 };
719 let Some(index) = self
720 .doc
721 .select_options(select_id)
722 .iter()
723 .position(|id| *id == option_id)
724 else {
725 return;
726 };
727 let changed = if selected {
728 self.doc.set_select_selected_index(select_id, index)
729 } else {
730 self.doc
731 .get_node_mut(select_id)
732 .and_then(|node| node.data.downcast_element_mut())
733 .and_then(|el| el.select_data_mut())
734 .is_some_and(|data| data.set_selected(index, false))
735 };
736 if changed {
737 self.doc.snapshot_node(option_id);
740 self.doc.snapshot_node(select_id);
741 }
742 }
743
744 pub fn clear_attribute(&mut self, node_id: NodeId, name: QualName) {
745 #[cfg(feature = "shadow-dom")]
746 if name.local == local_name!("slot") || name.local == local_name!("name") {
747 self.doc.note_shadow_tree_change(node_id);
748 }
749 self.doc
750 .record_script_custom_element_attribute(node_id, &name, None);
751 self.doc
752 .record_inline_handler_attribute(node_id, &name, None);
753 self.doc.record_details_open(node_id, &name, false);
754 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
755 if node_is_in_document && self.doc.is_recording_mutations() {
756 let old_value = self.doc.nodes[node_id]
759 .element_data()
760 .and_then(|element| element.attr(name.local.clone()))
761 .map(str::to_owned);
762 if old_value.is_some() {
763 self.doc.record_mutation(crate::DomMutation::Attributes {
764 target: node_id,
765 name: name.local.to_string(),
766 old_value,
767 });
768 }
769 }
770 if node_is_in_document {
771 let removes_existing_attribute = self.doc.nodes[node_id]
772 .element_data()
773 .is_some_and(|element| element.attrs.iter().any(|attr| attr.name == name));
774 self.doc.snapshot_attribute(node_id, &name.local);
775 let parent_id = self.doc.nodes[node_id].parent;
776 {
777 let node = &mut self.doc.nodes[node_id];
778 node.set_dirty_descendants();
779 if let Some(mut data) = node.stylo_element_data_opt_mut().and_then(|s| s.get_mut())
780 {
781 data.hint |= RestyleHint::restyle_subtree();
782 data.damage.insert(ALL_DAMAGE);
783 }
784 node.mark_ancestors_dirty();
786 }
787 if removes_existing_attribute {
788 self.restyle_parent_for_attribute_change(parent_id);
789 }
790 }
791
792 if name.ns == ns!() && name.local == local_name!("id") {
793 if let Some(old_id) = self.doc.nodes[node_id]
794 .element_data()
795 .and_then(|element| element.id.clone())
796 {
797 if let Some(owner) = self.doc.nodes[node_id].owner_document {
798 self.doc
799 .remove_from_document_id_map(owner, &old_id, node_id);
800 }
801 if node_is_in_document {
802 self.doc.remove_from_id_map(&old_id, node_id);
803 }
804 }
805 }
806
807 let node = &mut self.doc.nodes[node_id];
808
809 let Some(element) = node.element_data_mut() else {
810 return;
811 };
812
813 let removed_attr = element.attrs.remove(&name);
814 let had_attr = removed_attr.is_some();
815 if !had_attr {
816 return;
817 }
818 self.mutations_occurred |= node_is_in_document;
819
820 #[cfg(feature = "custom-widget")]
822 if let SpecialElementData::CustomWidget(widget_data) = &mut element.special_data {
823 let old_value = removed_attr.as_ref().map(|attr| &*attr.value);
824 widget_data
825 .widget
826 .attribute_changed(&name.local, old_value, None);
827 }
828
829 #[cfg(feature = "shadow-dom")]
831 if element.custom_element_data().is_some() {
832 let old_value = removed_attr.as_ref().map(|attr| attr.value.to_string());
833 self.custom_element_attr_changes
834 .push((node_id, name.clone(), old_value, None));
835 }
836
837 if name.ns != ns!() {
838 return;
839 }
840 if name.local == local_name!("id") {
841 element.id = None;
842 }
843
844 if name.local == local_name!("tabindex")
847 || name.local == local_name!("href")
848 || name.local == local_name!("disabled")
849 {
850 element.flush_is_focussable();
851 }
852
853 if name.local == local_name!("value") {
855 if let Some(input_data) = element.text_input_data_mut() {
856 input_data.set_text(
857 &mut self.doc.font_ctx.lock().unwrap(),
858 &mut self.doc.layout_ctx,
859 "",
860 );
861 }
862 }
863
864 let tag = &element.name.local;
865 let attr = &name.local;
866
867 if *attr == local_name!("disabled") && element.can_be_disabled() {
868 node.enable();
869 return;
870 }
871
872 if *attr == local_name!("style") {
873 element.flush_style_attribute(&self.doc.guard, &self.doc.url.url_extra_data());
874 node.mark_style_attr_updated();
875 } else if (tag, attr) == tag_and_attr!("canvas", "src") {
876 self.recompute_is_animating = true;
877 } else if (tag, attr) == tag_and_attr!("link", "href") {
878 self.unload_stylesheet(node_id);
879 } else if (tag, attr) == tag_and_attr!("iframe", "srcdoc") && node_is_in_document {
880 self.load_iframe(node_id);
882 }
883 }
884
885 pub fn set_style_property(&mut self, node_id: NodeId, name: &str, value: &str) {
886 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
887 self.doc.set_style_property(node_id, name, value);
888 self.mutations_occurred |= node_is_in_document;
889 }
890
891 pub fn remove_style_property(&mut self, node_id: NodeId, name: &str) {
892 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
893 self.doc.remove_style_property(node_id, name);
894 self.mutations_occurred |= node_is_in_document;
895 }
896
897 pub fn set_sub_document(&mut self, node_id: NodeId, sub_document: Box<dyn Document>) {
898 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
899 self.doc.set_sub_document(node_id, sub_document);
900 self.mutations_occurred |= node_is_in_document;
901 }
902
903 pub fn remove_sub_document(&mut self, node_id: NodeId) {
904 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
905 self.doc.remove_sub_document(node_id);
906 self.mutations_occurred |= node_is_in_document;
907 }
908
909 #[cfg(feature = "custom-widget")]
910 pub fn set_custom_widget(&mut self, node_id: NodeId, widget: Box<dyn crate::Widget>) {
911 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
912 self.doc.set_custom_widget(node_id, widget);
913 self.mutations_occurred |= node_is_in_document;
914 }
915
916 #[cfg(feature = "custom-widget")]
917 pub fn remove_custom_widget(&mut self, node_id: NodeId) {
918 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
919 self.doc.remove_custom_widget(node_id);
920 self.mutations_occurred |= node_is_in_document;
921 }
922
923 #[cfg(feature = "shadow-dom")]
926 pub fn attach_shadow(&mut self, host_id: NodeId, mode: crate::node::ShadowRootMode) -> NodeId {
927 self.doc.attach_shadow(host_id, mode)
928 }
929
930 #[cfg(feature = "shadow-dom")]
931 pub fn try_attach_declarative_shadow(
932 &mut self,
933 host_id: NodeId,
934 template_id: NodeId,
935 mode: crate::node::ShadowRootMode,
936 clonable: bool,
937 serializable: bool,
938 delegates_focus: bool,
939 ) -> bool {
940 self.doc.try_attach_declarative_shadow(
941 host_id,
942 template_id,
943 mode,
944 clonable,
945 serializable,
946 delegates_focus,
947 )
948 }
949
950 #[cfg(feature = "shadow-dom")]
951 pub fn declarative_shadow_for_template(&self, template_id: NodeId) -> Option<NodeId> {
952 self.doc
953 .declarative_template_shadows
954 .get(&template_id)
955 .copied()
956 }
957
958 #[cfg(feature = "shadow-dom")]
959 pub fn forget_declarative_template(&mut self, template_id: NodeId) {
960 self.doc.declarative_template_shadows.remove(&template_id);
961 }
962
963 #[cfg(feature = "shadow-dom")]
966 pub fn set_custom_element(
967 &mut self,
968 node_id: NodeId,
969 controller: Box<dyn crate::node::CustomElement>,
970 ) {
971 self.doc.set_custom_element(node_id, controller);
972 self.upgrade_custom_element(node_id);
973 }
974
975 #[cfg(feature = "shadow-dom")]
978 pub fn remove_custom_element(&mut self, node_id: NodeId) {
979 self.disconnect_custom_element(node_id);
980 let _ = self.doc.take_custom_element(node_id);
981 }
982
983 #[cfg(feature = "shadow-dom")]
988 pub(crate) fn upgrade_custom_element(&mut self, node_id: NodeId) {
989 use crate::node::{CustomElementData, ShadowRootMode, SpecialElementData};
990
991 let Some(node) = self.doc.get_node(node_id) else {
992 return;
993 };
994 let Some(element) = node.element_data() else {
995 return;
996 };
997
998 let already_has_controller =
1001 matches!(element.special_data, SpecialElementData::CustomElement(_));
1002
1003 let mode = if already_has_controller {
1004 ShadowRootMode::Open
1006 } else {
1007 let tag = element.name.local.clone();
1008 let Some(definition) = self.doc.custom_element_registry.get(&tag) else {
1009 return;
1010 };
1011 let mode = definition.mode;
1012 let controller = (definition.factory)();
1013 self.doc.nodes[node_id]
1014 .element_data_mut()
1015 .unwrap()
1016 .special_data =
1017 SpecialElementData::CustomElement(CustomElementData::new(controller));
1018 self.doc.custom_element_nodes.insert(node_id);
1019 mode
1020 };
1021
1022 let is_upgraded = self.doc.nodes[node_id]
1024 .element_data()
1025 .and_then(|el| el.custom_element_data())
1026 .map(|data| data.upgraded)
1027 .unwrap_or(true);
1028 if is_upgraded {
1029 return;
1030 }
1031
1032 let shadow_root_id = self.doc.attach_shadow(node_id, mode);
1034
1035 let Some(mut controller) = self.take_controller(node_id) else {
1037 return;
1038 };
1039
1040 {
1041 let mut ctx = crate::node::CustomElementCtx {
1042 mutator: self,
1043 host_id: node_id,
1044 shadow_root_id,
1045 };
1046 controller.connected(&mut ctx);
1047 }
1048
1049 self.restore_controller(node_id, controller, true);
1050 }
1051
1052 #[cfg(feature = "shadow-dom")]
1054 pub(crate) fn disconnect_custom_element(&mut self, node_id: NodeId) {
1055 let Some(shadow_root_id) = self
1056 .doc
1057 .get_node(node_id)
1058 .and_then(|node| node.shadow_root_id())
1059 else {
1060 if let Some(mut controller) = self.take_controller(node_id) {
1062 {
1065 let mut ctx = crate::node::CustomElementCtx {
1066 mutator: self,
1067 host_id: node_id,
1068 shadow_root_id: node_id,
1069 };
1070 controller.disconnected(&mut ctx);
1071 }
1072 self.restore_controller(node_id, controller, false);
1073 }
1074 return;
1075 };
1076
1077 if let Some(mut controller) = self.take_controller(node_id) {
1078 {
1079 let mut ctx = crate::node::CustomElementCtx {
1080 mutator: self,
1081 host_id: node_id,
1082 shadow_root_id,
1083 };
1084 controller.disconnected(&mut ctx);
1085 }
1086 self.restore_controller(node_id, controller, false);
1087 }
1088 }
1089
1090 #[cfg(feature = "shadow-dom")]
1093 fn take_controller(&mut self, node_id: NodeId) -> Option<Box<dyn crate::node::CustomElement>> {
1094 self.doc
1095 .nodes
1096 .get_mut(node_id)?
1097 .element_data_mut()?
1098 .custom_element_data_mut()?
1099 .controller
1100 .take()
1101 }
1102
1103 #[cfg(feature = "shadow-dom")]
1106 fn restore_controller(
1107 &mut self,
1108 node_id: NodeId,
1109 controller: Box<dyn crate::node::CustomElement>,
1110 upgraded: bool,
1111 ) {
1112 if let Some(data) = self
1113 .doc
1114 .nodes
1115 .get_mut(node_id)
1116 .and_then(|node| node.element_data_mut())
1117 .and_then(|el| el.custom_element_data_mut())
1118 {
1119 data.controller = Some(controller);
1120 if upgraded {
1121 data.upgraded = true;
1122 }
1123 }
1124 }
1125
1126 fn invalidate_layout_parent_edge(&mut self, node_id: NodeId) {
1135 let Some(layout_parent_id) = self
1136 .doc
1137 .nodes
1138 .get(node_id)
1139 .and_then(|node| node.layout_parent.get())
1140 else {
1141 return;
1142 };
1143 if let Some(layout_parent) = self.doc.nodes.get_mut(layout_parent_id) {
1144 layout_parent.layout_children.get_mut().take();
1145 layout_parent.paint_children.get_mut().take();
1146 layout_parent.insert_damage(ALL_DAMAGE);
1147 }
1148 if let Some(node) = self.doc.nodes.get(node_id) {
1149 node.layout_parent.set(None);
1150 }
1151 }
1152
1153 fn clear_layout_of_subtree(doc: &mut BaseDocument, node_id: NodeId) {
1155 let mut stack = vec![node_id];
1156 while let Some(id) = stack.pop() {
1157 let Some(node) = doc.nodes.get_mut(id) else {
1158 continue;
1159 };
1160 if node.data.downcast_element().is_some() {
1164 *node.unrounded_layout_mut() = taffy::Layout::with_order(0);
1165 *node.final_layout_mut() = taffy::Layout::with_order(0);
1166 node.cache_mut().clear();
1167 }
1168 stack.extend(node.children.iter().copied());
1169 }
1170 }
1171
1172 fn record_removal(&mut self, node_id: NodeId) {
1176 self.doc.range_remove_node(node_id);
1177 if !self.doc.is_recording_mutations() || !self.doc.nodes[node_id].flags.is_in_document() {
1178 return;
1179 }
1180 if let Some((parent_id, previous_sibling, next_sibling)) = self.doc.sibling_context(node_id)
1181 {
1182 self.doc.record_mutation(crate::DomMutation::ChildList {
1183 target: parent_id,
1184 added: Vec::new(),
1185 removed: vec![node_id],
1186 previous_sibling,
1187 next_sibling,
1188 });
1189 }
1190 }
1191
1192 pub fn remove_node(&mut self, node_id: NodeId) {
1193 self.record_removal(node_id);
1194 self.doc.detach_script_custom_element_subtree(node_id);
1195 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
1196 self.process_removed_subtree(node_id);
1200
1201 Self::clear_layout_of_subtree(self.doc, node_id);
1209 self.invalidate_layout_parent_edge(node_id);
1210
1211 let node = &mut self.doc.nodes[node_id];
1212
1213 if let Some(parent_id) = node.parent.take() {
1215 self.mutations_occurred |= node_is_in_document;
1216 let parent = &mut self.doc.nodes[parent_id];
1217 parent.insert_damage(ALL_DAMAGE);
1218 parent.mark_ancestors_dirty();
1220 parent.children.retain(|id| *id != node_id);
1221 self.maybe_record_node(parent_id);
1222 }
1223 }
1224
1225 pub fn remove_and_drop_node(&mut self, node_id: NodeId) -> Option<Node> {
1226 self.remove_and_drop_node_with(node_id, &mut |_| {})
1227 }
1228
1229 pub fn remove_and_drop_node_with(
1232 &mut self,
1233 node_id: NodeId,
1234 on_drop: &mut dyn FnMut(NodeId),
1235 ) -> Option<Node> {
1236 self.record_removal(node_id);
1237 self.doc.detach_script_custom_element_subtree(node_id);
1238 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
1239 self.process_removed_subtree(node_id);
1240 self.invalidate_layout_parent_edge(node_id);
1241
1242 let node = self.doc.drop_node_ignoring_parent_with(node_id, on_drop);
1243 self.mutations_occurred |= node_is_in_document;
1244
1245 if let Some(parent_id) = node.as_ref().and_then(|node| node.parent) {
1247 let parent = &mut self.doc.nodes[parent_id];
1248 parent.insert_damage(ALL_DAMAGE);
1249 let parent_is_in_doc = parent.flags.is_in_document();
1250
1251 if parent_is_in_doc {
1253 if let Some(mut data) = parent
1254 .stylo_element_data_opt_mut()
1255 .and_then(|s| s.get_mut())
1256 {
1257 data.hint |= RestyleHint::restyle_subtree();
1258 }
1259 parent.mark_ancestors_dirty();
1261 }
1262
1263 parent.children.retain(|id| *id != node_id);
1264 self.maybe_record_node(parent_id);
1265 }
1266
1267 node
1268 }
1269
1270 pub fn remove_and_drop_all_children(&mut self, node_id: NodeId) {
1271 if !self.doc.live_ranges.is_empty() {
1272 let children = self.doc.nodes[node_id].children.clone();
1273 for child in children.into_iter().rev() {
1274 self.doc.range_remove_node(child);
1275 }
1276 }
1277 if self.doc.is_recording_mutations()
1278 && self.doc.nodes[node_id].flags.is_in_document()
1279 && !self.doc.nodes[node_id].children.is_empty()
1280 {
1281 let removed = self.doc.nodes[node_id].children.to_vec();
1282 self.doc.record_mutation(crate::DomMutation::ChildList {
1283 target: node_id,
1284 added: Vec::new(),
1285 removed,
1286 previous_sibling: None,
1287 next_sibling: None,
1288 });
1289 }
1290 let parent = &mut self.doc.nodes[node_id];
1291 let parent_is_in_doc = parent.flags.is_in_document();
1292
1293 if parent_is_in_doc {
1295 if let Some(mut data) = parent
1296 .stylo_element_data_opt_mut()
1297 .and_then(|s| s.get_mut())
1298 {
1299 data.hint |= RestyleHint::restyle_subtree();
1300 }
1301 parent.mark_ancestors_dirty();
1303 }
1304
1305 let children = mem::take(&mut parent.children);
1306 self.mutations_occurred |= parent_is_in_doc && !children.is_empty();
1307 for child_id in children {
1308 self.doc.detach_script_custom_element_subtree(child_id);
1309 self.process_removed_subtree(child_id);
1310 self.invalidate_layout_parent_edge(child_id);
1311 let _ = self.doc.drop_node_ignoring_parent(child_id);
1312 }
1313 self.maybe_record_node(node_id);
1314 }
1315
1316 pub fn remove_node_if_unparented(&mut self, node_id: NodeId) {
1318 self.remove_node_if_unparented_with(node_id, &mut |_| {});
1319 }
1320
1321 pub fn remove_node_if_unparented_with(
1324 &mut self,
1325 node_id: NodeId,
1326 on_drop: &mut dyn FnMut(NodeId),
1327 ) {
1328 if let Some(node) = self.doc.get_node(node_id) {
1329 if node.parent.is_none() {
1330 self.remove_and_drop_node_with(node_id, on_drop);
1331 }
1332 }
1333 }
1334
1335 pub fn append_children(&mut self, parent_id: NodeId, child_ids: &[NodeId]) {
1337 self.add_children_to_parent(parent_id, child_ids, &|parent, child_ids| {
1338 parent.children.extend_from_slice(child_ids);
1339 });
1340 }
1341
1342 pub fn insert_nodes_before(&mut self, anchor_node_id: NodeId, new_node_ids: &[NodeId]) {
1343 let parent_id = self.doc.nodes[anchor_node_id].parent.unwrap();
1344 self.add_children_to_parent(parent_id, new_node_ids, &|parent, child_ids| {
1345 let node_child_idx = parent.index_of_child(anchor_node_id).unwrap();
1346 parent
1347 .children
1348 .splice(node_child_idx..node_child_idx, child_ids.iter().copied());
1349 });
1350 }
1351
1352 fn add_children_to_parent(
1353 &mut self,
1354 parent_id: NodeId,
1355 child_ids: &[NodeId],
1356 insert_children_fn: &dyn Fn(&mut Node, &[NodeId]),
1357 ) {
1358 let child_ids: Vec<NodeId> = if child_ids
1366 .iter()
1367 .any(|id| matches!(self.doc.nodes[*id].data, NodeData::DocumentFragment))
1368 {
1369 let mut flattened = Vec::with_capacity(child_ids.len());
1370 for id in child_ids.iter().copied() {
1371 if matches!(self.doc.nodes[id].data, NodeData::DocumentFragment) {
1372 if !self.doc.live_ranges.is_empty() {
1373 let children = self.doc.nodes[id].children.clone();
1374 for child in children.into_iter().rev() {
1375 self.doc.range_remove_node(child);
1376 }
1377 }
1378 let moved = std::mem::take(&mut self.doc.nodes[id].children);
1379 flattened.extend(moved);
1380 } else {
1381 flattened.push(id);
1382 }
1383 }
1384 flattened
1385 } else {
1386 child_ids.to_vec()
1387 };
1388 let child_ids = child_ids.as_slice();
1389
1390 let new_parent_is_in_document = self.doc.nodes[parent_id].flags.is_in_document();
1391 self.mutations_occurred |= new_parent_is_in_document && !child_ids.is_empty();
1392 for child_id in child_ids.iter().copied() {
1399 self.record_removal(child_id);
1400 self.doc.detach_script_custom_element_subtree(child_id);
1401 self.invalidate_layout_parent_edge(child_id);
1402 let child = &mut self.doc.nodes[child_id];
1403 let child_was_in_doc = child.flags.is_in_document();
1404 self.mutations_occurred |= child_was_in_doc;
1405 let Some(old_parent_id) = child.parent.take() else {
1406 continue;
1407 };
1408
1409 let old_parent = &mut self.doc.nodes[old_parent_id];
1410 old_parent.insert_damage(ALL_DAMAGE);
1411
1412 if child_was_in_doc {
1414 if let Some(mut data) = old_parent
1415 .stylo_element_data_opt_mut()
1416 .and_then(|s| s.get_mut())
1417 {
1418 data.hint |= RestyleHint::restyle_subtree();
1419 }
1420 old_parent.mark_ancestors_dirty();
1422 }
1423
1424 old_parent.children.retain(|id| *id != child_id);
1425 self.maybe_record_node(old_parent_id);
1426 }
1427
1428 let new_parent = &mut self.doc.nodes[parent_id];
1429 new_parent.insert_damage(ALL_DAMAGE);
1430
1431 if new_parent_is_in_document {
1433 if let Some(mut data) = new_parent
1434 .stylo_element_data_opt_mut()
1435 .and_then(|s| s.get_mut())
1436 {
1437 data.hint |= RestyleHint::restyle_subtree();
1438 }
1439 new_parent.mark_ancestors_dirty();
1441 }
1442
1443 insert_children_fn(new_parent, child_ids);
1444 if !self.doc.live_ranges.is_empty()
1445 && let Some(first) = child_ids.first()
1446 && let Some(index) = self.doc.nodes[parent_id].index_of_child(*first)
1447 {
1448 self.doc
1449 .range_insert_nodes(parent_id, index, child_ids.len());
1450 }
1451
1452 for child_id in child_ids.iter().copied() {
1453 let child = &mut self.doc.nodes[child_id];
1454 let child_was_in_doc = child.flags.is_in_document();
1455 child.parent = Some(parent_id);
1456 self.doc
1457 .attach_script_custom_element_subtree(child_id, parent_id);
1458
1459 if new_parent_is_in_document && !child_was_in_doc {
1460 self.process_added_subtree(child_id);
1461 } else if !new_parent_is_in_document && child_was_in_doc {
1462 self.process_removed_subtree(child_id);
1463 }
1464 }
1465
1466 if new_parent_is_in_document && self.doc.is_recording_mutations() {
1467 if let (Some(first), Some(last)) = (child_ids.first(), child_ids.last()) {
1468 let previous_sibling = self.doc.sibling_context(*first).and_then(|c| c.1);
1469 let next_sibling = self.doc.sibling_context(*last).and_then(|c| c.2);
1470 self.doc.record_mutation(crate::DomMutation::ChildList {
1471 target: parent_id,
1472 added: child_ids.to_vec(),
1473 removed: Vec::new(),
1474 previous_sibling,
1475 next_sibling,
1476 });
1477 }
1478 }
1479
1480 self.maybe_record_node(parent_id);
1481 }
1482
1483 pub fn insert_nodes_after(&mut self, anchor_node_id: NodeId, new_node_ids: &[NodeId]) {
1485 match self.next_sibling_id(anchor_node_id) {
1486 Some(id) => self.insert_nodes_before(id, new_node_ids),
1487 None => {
1488 let parent_id = self.parent_id(anchor_node_id).unwrap();
1489 self.append_children(parent_id, new_node_ids)
1490 }
1491 }
1492 }
1493
1494 pub fn reparent_children(&mut self, old_parent_id: NodeId, new_parent_id: NodeId) {
1495 if self.doc.is_recording_mutations()
1496 && self.doc.nodes[old_parent_id].flags.is_in_document()
1497 && !self.doc.nodes[old_parent_id].children.is_empty()
1498 {
1499 let removed = self.doc.nodes[old_parent_id].children.to_vec();
1500 self.doc.record_mutation(crate::DomMutation::ChildList {
1501 target: old_parent_id,
1502 added: Vec::new(),
1503 removed,
1504 previous_sibling: None,
1505 next_sibling: None,
1506 });
1507 }
1508 if !self.doc.live_ranges.is_empty() {
1509 let children = self.doc.nodes[old_parent_id].children.clone();
1510 for child in children.into_iter().rev() {
1511 self.doc.range_remove_node(child);
1512 }
1513 }
1514 let child_ids = std::mem::take(&mut self.doc.nodes[old_parent_id].children);
1515 self.maybe_record_node(old_parent_id);
1516 self.append_children(new_parent_id, &child_ids);
1517 }
1518
1519 pub fn replace_node_with(&mut self, anchor_node_id: NodeId, new_node_ids: &[NodeId]) {
1520 self.insert_nodes_before(anchor_node_id, new_node_ids);
1521 self.remove_node(anchor_node_id);
1522 }
1523}
1524
1525impl<'doc> DocumentMutator<'doc> {
1526 pub fn flush(&mut self) {
1527 if self.recompute_is_animating {
1528 self.doc.has_canvas = self.doc.compute_has_canvas();
1529 }
1530
1531 if let Some(id) = self.title_node {
1532 let title = self.doc.nodes[id].text_content();
1533 self.doc.shell_provider.set_window_title(title);
1534 }
1535
1536 for id in self.style_nodes.drain() {
1538 self.doc.process_style_element(id);
1539 }
1540
1541 for id in self.form_nodes.drain() {
1542 self.doc.reset_form_owner(id);
1543 }
1544
1545 #[cfg(feature = "autofocus")]
1546 if let Some(node_id) = self.node_to_autofocus.take() {
1547 if self.doc.get_node(node_id).is_some() {
1548 self.doc.set_focus_to(node_id);
1549 }
1550 }
1551
1552 #[cfg(feature = "shadow-dom")]
1553 self.dispatch_custom_element_attr_changes();
1554 }
1555
1556 #[cfg(feature = "shadow-dom")]
1558 fn dispatch_custom_element_attr_changes(&mut self) {
1559 if self.custom_element_attr_changes.is_empty() {
1560 return;
1561 }
1562 let changes = mem::take(&mut self.custom_element_attr_changes);
1563 for (node_id, name, old_value, new_value) in changes {
1564 let tag = self
1568 .doc
1569 .get_node(node_id)
1570 .and_then(|node| node.element_data())
1571 .map(|el| el.name.local.clone());
1572 let observed = tag
1573 .as_ref()
1574 .and_then(|tag| self.doc.custom_element_registry.get(tag))
1575 .map(|def| def.observes(&name.local))
1576 .unwrap_or(true);
1577 if !observed {
1578 continue;
1579 }
1580
1581 let Some(shadow_root_id) = self
1582 .doc
1583 .get_node(node_id)
1584 .and_then(|node| node.shadow_root_id())
1585 else {
1586 continue;
1587 };
1588 let Some(mut controller) = self.take_controller(node_id) else {
1589 continue;
1590 };
1591 {
1592 let mut ctx = crate::node::CustomElementCtx {
1593 mutator: self,
1594 host_id: node_id,
1595 shadow_root_id,
1596 };
1597 controller.attribute_changed(
1598 &mut ctx,
1599 &name.local,
1600 old_value.as_deref(),
1601 new_value.as_deref(),
1602 );
1603 }
1604 self.restore_controller(node_id, controller, false);
1605 }
1606 }
1607
1608 pub fn set_inner_html(&mut self, node_id: NodeId, html: &str) {
1609 self.set_html_replacing(node_id, html, false);
1610 }
1611
1612 pub fn set_html_unsafe(&mut self, node_id: NodeId, html: &str) {
1614 self.set_html_replacing(node_id, html, true);
1615 }
1616
1617 fn set_html_replacing(
1618 &mut self,
1619 node_id: NodeId,
1620 html: &str,
1621 allow_declarative_shadow_roots: bool,
1622 ) {
1623 let target = self.doc.nodes[node_id]
1624 .element_data()
1625 .and_then(|element| element.template_contents)
1626 .unwrap_or(node_id);
1627 self.remove_and_drop_all_children(target);
1628 #[cfg(feature = "shadow-dom")]
1629 if let Some(host_id) = self.doc.nodes[node_id]
1630 .shadow_root_data()
1631 .map(|root| root.host)
1632 {
1633 let name = self.doc.nodes[host_id].element_data().unwrap().name.clone();
1639 let context_id = self.create_element(name, Vec::new());
1640 if allow_declarative_shadow_roots {
1641 self.doc
1642 .html_parser_provider
1643 .clone()
1644 .parse_fragment(self, context_id, html, true);
1645 } else {
1646 self.doc
1647 .html_parser_provider
1648 .clone()
1649 .parse_inner_html(self, context_id, html);
1650 }
1651 let children = self.child_ids(context_id);
1652 self.append_children(node_id, &children);
1653 self.remove_and_drop_node(context_id);
1654 self.doc.note_shadow_tree_change(node_id);
1655 return;
1656 }
1657 if allow_declarative_shadow_roots {
1658 self.doc
1659 .html_parser_provider
1660 .clone()
1661 .parse_fragment(self, node_id, html, true);
1662 #[cfg(feature = "shadow-dom")]
1667 self.promote_direct_declarative_shadows(target);
1668 } else {
1669 self.doc
1670 .html_parser_provider
1671 .clone()
1672 .parse_inner_html(self, node_id, html);
1673 }
1674 }
1675
1676 #[cfg(feature = "shadow-dom")]
1680 fn promote_direct_declarative_shadows(&mut self, target: NodeId) {
1681 let can_host = self
1682 .doc
1683 .nodes
1684 .get(target)
1685 .and_then(|node| node.element_data())
1686 .is_some_and(|element| {
1687 BaseDocument::element_can_be_shadow_host(element) && element.shadow_root.is_none()
1688 });
1689 if !can_host {
1690 return;
1691 }
1692 let children = self.child_ids(target);
1693 for child_id in children {
1694 let info = self.doc.nodes[child_id]
1695 .element_data()
1696 .and_then(Self::declarative_template_flags);
1697 let Some((mode, clonable, serializable, delegates_focus, contents)) = info else {
1698 continue;
1699 };
1700 let root_id = self.doc.attach_shadow(target, mode);
1701 if let Some(root) = self.doc.nodes[root_id].shadow_root_data_mut() {
1702 root.declarative = true;
1703 root.clonable = clonable;
1704 root.serializable = serializable;
1705 root.delegates_focus = delegates_focus;
1706 }
1707 if let Some(contents) = contents {
1708 let inner = self.child_ids(contents);
1709 if !inner.is_empty() {
1710 self.append_children(root_id, &inner);
1711 }
1712 }
1713 self.remove_and_drop_node(child_id);
1714 self.doc.note_shadow_tree_change(target);
1715 break;
1716 }
1717 }
1718
1719 #[cfg(feature = "shadow-dom")]
1720 fn declarative_template_flags(
1721 element: &ElementData,
1722 ) -> Option<(
1723 crate::node::ShadowRootMode,
1724 bool,
1725 bool,
1726 bool,
1727 Option<NodeId>,
1728 )> {
1729 if element.name.ns != ns!(html) || element.name.local != local_name!("template") {
1730 return None;
1731 }
1732 let raw = element.attr(local_name!("shadowrootmode"))?;
1733 let mode = if raw.eq_ignore_ascii_case("open") {
1734 crate::node::ShadowRootMode::Open
1735 } else if raw.eq_ignore_ascii_case("closed") {
1736 crate::node::ShadowRootMode::Closed
1737 } else {
1738 return None;
1739 };
1740 Some((
1741 mode,
1742 element.has_attr(local_name!("shadowrootclonable")),
1743 element.has_attr(local_name!("shadowrootserializable")),
1744 element.has_attr(local_name!("shadowrootdelegatesfocus")),
1745 element.template_contents,
1746 ))
1747 }
1748
1749 fn flush_eager_ops(&mut self) {
1750 let mut ops = mem::take(&mut self.eager_op_queue);
1751 for op in ops.drain(0..) {
1752 match op {
1753 SpecialOp::LoadImage(node_id) => self.load_image(node_id),
1754 SpecialOp::LoadIframe(node_id) => self.load_iframe(node_id),
1755 SpecialOp::LoadStylesheet(node_id) => self.load_linked_stylesheet(node_id),
1756 SpecialOp::UnloadStylesheet(node_id) => self.unload_stylesheet(node_id),
1757 SpecialOp::LoadCustomPaintSource(node_id) => self.load_custom_paint_src(node_id),
1758 SpecialOp::ProcessButtonInput(node_id) => self.process_button_input(node_id),
1759 SpecialOp::UnloadSubDocument(node_id) => self.remove_sub_document(node_id),
1760 #[cfg(feature = "custom-widget")]
1761 SpecialOp::UnloadCustomWidget(node_id) => self.remove_custom_widget(node_id),
1762 #[cfg(feature = "shadow-dom")]
1763 SpecialOp::UpgradeCustomElement(node_id) => self.upgrade_custom_element(node_id),
1764 #[cfg(feature = "shadow-dom")]
1765 SpecialOp::DisconnectCustomElement(node_id) => {
1766 self.disconnect_custom_element(node_id)
1767 }
1768 }
1769 }
1770
1771 self.eager_op_queue = ops;
1773 }
1774
1775 fn process_added_subtree(&mut self, node_id: NodeId) {
1776 self.doc
1777 .iter_shadow_including_subtree_mut(node_id, |node_id, doc| {
1778 let node = &mut doc.nodes[node_id];
1779 node.flags.set(NodeFlags::IS_IN_DOCUMENT, true);
1780 node.insert_damage(ALL_DAMAGE);
1781
1782 if let Some(id_attr) = node.attr(local_name!("id")).map(ToString::to_string) {
1784 doc.add_to_id_map(&id_attr, node_id);
1785 }
1786
1787 let node = &mut doc.nodes[node_id];
1788 let NodeData::Element(ref mut element) = node.data else {
1789 return;
1790 };
1791
1792 let tag = element.name.local.as_ref();
1794 match tag {
1795 "title" if element.name.ns == ns!(html) => self.title_node = Some(node_id),
1796 "link" => self.eager_op_queue.push(SpecialOp::LoadStylesheet(node_id)),
1797 "img" => self.eager_op_queue.push(SpecialOp::LoadImage(node_id)),
1798 "iframe" => self.eager_op_queue.push(SpecialOp::LoadIframe(node_id)),
1799 "canvas" => self
1800 .eager_op_queue
1801 .push(SpecialOp::LoadCustomPaintSource(node_id)),
1802 "style" => {
1803 self.style_nodes.insert(node_id);
1804 }
1805 "button" | "fieldset" | "input" | "select" | "textarea" | "object"
1806 | "output" => {
1807 self.eager_op_queue
1808 .push(SpecialOp::ProcessButtonInput(node_id));
1809 self.form_nodes.insert(node_id);
1810 }
1811 _ => {}
1812 }
1813
1814 #[cfg(feature = "shadow-dom")]
1818 {
1819 let needs_upgrade = doc.custom_element_registry.contains(&element.name.local)
1820 && element.custom_element_data().is_none();
1821 if needs_upgrade {
1822 self.eager_op_queue
1823 .push(SpecialOp::UpgradeCustomElement(node_id));
1824 }
1825 }
1826
1827 #[cfg(feature = "autofocus")]
1838 if node.is_focussable() {
1839 if let NodeData::Element(ref element) = node.data {
1840 if element.attr(local_name!("autofocus")).is_some() {
1841 self.node_to_autofocus = Some(node_id);
1842 }
1843 }
1844 }
1845 });
1846
1847 self.flush_eager_ops();
1848 }
1849
1850 fn process_removed_subtree(&mut self, node_id: NodeId) {
1851 let mut removed = FxHashSet::default();
1852 let mut parents = FxHashSet::default();
1853 self.doc
1854 .iter_shadow_including_subtree_mut(node_id, |node_id, doc| {
1855 removed.insert(node_id);
1856 let node = &doc.nodes[node_id];
1857 parents.extend(
1858 [node.parent, node.layout_parent.get()]
1859 .into_iter()
1860 .flatten(),
1861 );
1862 doc.nodes[node_id]
1863 .flags
1864 .set(NodeFlags::IS_IN_DOCUMENT, false);
1865
1866 doc.clear_interaction_state_for_removed_node(node_id);
1870
1871 let node = &mut doc.nodes[node_id];
1872
1873 if doc.focus_node_id == Some(node_id) {
1890 doc.focus_node_id = None;
1891 }
1892 if doc.mousedown_node_id == Some(node_id) {
1893 doc.mousedown_node_id = None;
1894 }
1895
1896 if doc.text_selection.anchor.node_or_parent == Some(node_id)
1899 || doc.text_selection.focus.node_or_parent == Some(node_id)
1900 {
1901 doc.text_selection.clear();
1902 }
1903
1904 if node.has_snapshot() {
1907 let opaque_id = style::dom::TNode::opaque(&&*node);
1908 doc.snapshots.remove(&opaque_id);
1909 node.set_has_snapshot(false);
1910 }
1911
1912 if let Some(id_attr) = node.attr(local_name!("id")).map(ToString::to_string) {
1914 doc.remove_from_id_map(&id_attr, node_id);
1915 }
1916
1917 let node = &mut doc.nodes[node_id];
1918 let NodeData::Element(ref mut element) = node.data else {
1919 return;
1920 };
1921
1922 match &element.special_data {
1923 SpecialElementData::SubDocument(_) => {
1924 self.eager_op_queue
1925 .push(SpecialOp::UnloadSubDocument(node_id));
1926 }
1927 #[cfg(feature = "custom-widget")]
1928 SpecialElementData::CustomWidget(_) => {
1929 self.eager_op_queue
1930 .push(SpecialOp::UnloadCustomWidget(node_id));
1931 }
1932 #[cfg(feature = "shadow-dom")]
1933 SpecialElementData::CustomElement(_) => {
1934 self.eager_op_queue
1935 .push(SpecialOp::DisconnectCustomElement(node_id));
1936 }
1937 SpecialElementData::Stylesheet(_) => self
1938 .eager_op_queue
1939 .push(SpecialOp::UnloadStylesheet(node_id)),
1940 SpecialElementData::Image(_) => {}
1941 SpecialElementData::Canvas(_) => {
1942 self.recompute_is_animating = true;
1943 }
1944 SpecialElementData::TableRoot(_) => {}
1945 SpecialElementData::TextInput(_) => {}
1946 SpecialElementData::CheckboxInput(_) => {}
1947 SpecialElementData::Select(_) => {}
1948 #[cfg(feature = "file-input")]
1949 SpecialElementData::FileInput(_) => {}
1950 SpecialElementData::None => {}
1951 }
1952 });
1953
1954 self.doc
1958 .invalidate_node_references(&removed, parents, FxHashSet::default());
1959 #[cfg(feature = "autofocus")]
1960 if self
1961 .node_to_autofocus
1962 .is_some_and(|id| removed.contains(&id))
1963 {
1964 self.node_to_autofocus = None;
1965 }
1966
1967 self.flush_eager_ops();
1968 }
1969
1970 fn maybe_record_node(&mut self, node_id: impl Into<Option<NodeId>>) {
1971 let Some(node_id) = node_id.into() else {
1972 return;
1973 };
1974 if self.doc.nodes[node_id].owner_document != Some(self.doc.root_node().id) {
1975 return;
1976 }
1977
1978 #[cfg(feature = "shadow-dom")]
1979 self.doc.note_shadow_tree_change(node_id);
1980
1981 let Some(element) = self.doc.nodes[node_id].data.downcast_element() else {
1982 return;
1983 };
1984
1985 match element.name.local.as_ref() {
1986 "title" if element.name.ns == ns!(html) => self.title_node = Some(node_id),
1987 "style" => {
1988 self.style_nodes.insert(node_id);
1989 }
1990 _ => {}
1991 }
1992 }
1993
1994 fn load_linked_stylesheet(&mut self, target_id: NodeId) {
1995 let node = &self.doc.nodes[target_id];
1996
1997 let mut is_in_head = false;
1998 let mut parent_id = node.parent;
1999 while let Some(id) = parent_id
2000 && !is_in_head
2001 {
2002 let parent = &self.doc.nodes[id];
2003 is_in_head |= parent.data.is_element_with_tag_name(&local_name!("head"));
2004 parent_id = parent.parent;
2005 }
2006
2007 let rel_attr = node.attr(local_name!("rel"));
2008 let href_attr = node.attr(local_name!("href"));
2009
2010 let (Some(rels), Some(href)) = (rel_attr, href_attr) else {
2011 return;
2012 };
2013 if !rels.split_ascii_whitespace().any(|rel| rel == "stylesheet") {
2014 return;
2015 }
2016
2017 let url = self.doc.resolve_url(href);
2018 let handler = ResourceHandler::new(
2019 self.doc.tx.clone(),
2020 self.doc.id(),
2021 Some(node.id),
2022 self.doc.shell_provider.clone(),
2023 StylesheetHandler {
2024 source_url: url.clone(),
2025 guard: self.doc.guard.clone(),
2026 net_provider: self.doc.net_provider.clone(),
2027 abort_signal: self.doc.abort_signal.clone(),
2028 },
2029 );
2030
2031 if is_in_head && !self.doc.net_provider.is_noop() {
2032 self.doc
2033 .pending_critical_resources
2034 .insert(handler.request_id());
2035 }
2036
2037 self.doc.net_provider.fetch(
2038 self.doc.id(),
2039 self.doc.build_request(url),
2040 Box::new(handler),
2041 );
2042 }
2043
2044 fn unload_stylesheet(&mut self, node_id: NodeId) {
2045 #[cfg(feature = "shadow-dom")]
2046 let shadow_root = self
2047 .doc
2048 .shadow_sheet_roots
2049 .remove(&node_id)
2050 .or_else(|| self.doc.containing_shadow_root(node_id));
2051 let node = &mut self.doc.nodes[node_id];
2052 let Some(element) = node.element_data_mut() else {
2053 return;
2054 };
2055 let SpecialElementData::Stylesheet(stylesheet) = element.special_data.take() else {
2056 return;
2057 };
2058
2059 self.doc.nodes_to_stylesheet.remove(&node_id);
2060 #[cfg(feature = "shadow-dom")]
2061 if let Some(root_id) = shadow_root {
2062 self.doc.invalidate_shadow_styles(root_id);
2063 return;
2064 }
2065
2066 if !self.doc.document_sheet_nodes.remove(&node_id) {
2067 return;
2068 }
2069 let guard = self.doc.guard.read();
2070 self.doc.stylist.remove_stylesheet(stylesheet, &guard);
2071 self.doc
2072 .stylist
2073 .force_stylesheet_origins_dirty(OriginSet::all());
2074 }
2075
2076 fn load_image(&mut self, target_id: NodeId) {
2077 let node = &self.doc.nodes[target_id];
2078 if let Some(raw_src) = node.attr(local_name!("src")) {
2079 if !raw_src.is_empty() {
2080 let src = self.doc.resolve_url(raw_src);
2081 let src_string = src.as_str();
2082
2083 if let Some(cached_image) = self.doc.image_cache.get(src_string) {
2085 #[cfg(feature = "tracing")]
2086 tracing::info!("Loading image {src_string} from cache");
2087 let node = &mut self.doc.nodes[target_id];
2088 node.element_data_mut().unwrap().special_data =
2089 SpecialElementData::Image(Box::new(cached_image.clone()));
2090 node.cache_mut().clear();
2091 node.insert_damage(ALL_DAMAGE);
2092 return;
2093 }
2094
2095 if let Some(waiting_list) = self.doc.pending_images.get_mut(src_string) {
2097 #[cfg(feature = "tracing")]
2098 tracing::info!("Image {src_string} already pending, queueing node {target_id}");
2099 waiting_list.push((target_id, ImageType::Image));
2100 return;
2101 }
2102
2103 #[cfg(feature = "tracing")]
2105 tracing::info!("Fetching image {src_string}");
2106 self.doc
2107 .pending_images
2108 .insert(src_string.to_string(), vec![(target_id, ImageType::Image)]);
2109
2110 self.doc.net_provider.fetch(
2111 self.doc.id(),
2112 self.doc.build_request(src),
2113 ResourceHandler::boxed(
2114 self.doc.tx.clone(),
2115 self.doc.id(),
2116 None, self.doc.shell_provider.clone(),
2118 ImageHandler::new(ImageType::Image),
2119 ),
2120 );
2121 }
2122 }
2123 }
2124
2125 fn load_iframe(&mut self, target_id: NodeId) {
2126 if self.doc.subdocument_depth >= crate::iframe::MAX_SUBDOCUMENT_DEPTH {
2127 #[cfg(feature = "tracing")]
2128 tracing::warn!(
2129 "Not loading iframe: max sub-document nesting depth ({}) reached",
2130 crate::iframe::MAX_SUBDOCUMENT_DEPTH
2131 );
2132 return;
2133 }
2134
2135 let node = &self.doc.nodes[target_id];
2136 let Some(element) = node.element_data() else {
2137 return;
2138 };
2139
2140 if let Some(srcdoc) = element.attr(local_name!("srcdoc")) {
2142 let srcdoc = srcdoc.to_string();
2143 self.doc.load_iframe_srcdoc(target_id, &srcdoc);
2144 return;
2145 }
2146
2147 let Some(raw_src) = element.attr(local_name!("src")) else {
2148 return;
2149 };
2150 if raw_src.is_empty() {
2151 return;
2152 }
2153 let Some(url) = self.doc.url.resolve_relative(raw_src) else {
2154 #[cfg(feature = "tracing")]
2155 tracing::warn!("Not loading iframe: could not resolve url {raw_src}");
2156 return;
2157 };
2158 self.doc.start_iframe_load(target_id, url);
2159 }
2160
2161 fn load_custom_paint_src(&mut self, target_id: NodeId) {
2162 let node = &mut self.doc.nodes[target_id];
2163 if let Some(raw_src) = node.attr(local_name!("src")) {
2164 if let Ok(custom_paint_source_id) = raw_src.parse::<u64>() {
2165 self.recompute_is_animating = true;
2166 let canvas_data = SpecialElementData::Canvas(CanvasData {
2167 custom_paint_source_id,
2168 });
2169 node.element_data_mut().unwrap().special_data = canvas_data;
2170 }
2171 }
2172 }
2173
2174 fn process_button_input(&mut self, target_id: NodeId) {
2175 let node = &self.doc.nodes[target_id];
2176 let Some(data) = node.element_data() else {
2177 return;
2178 };
2179
2180 let tagname = data.name.local.as_ref();
2181 let type_attr = data.attr(local_name!("type"));
2182 let value = data.attr(local_name!("value"));
2183
2184 if let ("input", Some("button" | "submit" | "reset"), Some(value)) =
2187 (tagname, type_attr, value)
2188 {
2189 let value = value.to_string();
2190 let id = self.create_text_node(&value);
2191 self.append_children(target_id, &[id]);
2192 return;
2193 }
2194 #[cfg(feature = "file-input")]
2195 if let ("input", Some("file")) = (tagname, type_attr) {
2196 let button_id = self.create_element(
2197 qual_name!("button", html),
2198 vec![
2199 Attribute {
2200 name: qual_name!("type", html),
2201 value: "button".into(),
2202 },
2203 Attribute {
2204 name: qual_name!("tabindex", html),
2205 value: "-1".into(),
2206 },
2207 ],
2208 );
2209 let label_id = self.create_element(qual_name!("label", html), vec![]);
2210 let text_id = self.create_text_node("No File Selected");
2211 let button_text_id = self.create_text_node("Browse");
2212 self.append_children(target_id, &[button_id, label_id]);
2213 self.append_children(label_id, &[text_id]);
2214 self.append_children(button_id, &[button_text_id]);
2215 }
2216 }
2217}
2218
2219fn set_input_checked_state(element: &mut ElementData, value: String) {
2221 let Ok(checked) = value.parse() else {
2222 return;
2223 };
2224 match element.special_data {
2225 SpecialElementData::CheckboxInput(ref mut checked_mut) => *checked_mut = checked,
2226 SpecialElementData::None => element.attrs.push(Attribute {
2231 name: qual_name!("checked", html),
2232 value: checked.to_string().into(),
2233 }),
2234 _ => {}
2235 }
2236}
2237
2238pub struct ViewportMut<'doc> {
2241 doc: &'doc mut BaseDocument,
2242 initial_viewport: Viewport,
2243}
2244impl ViewportMut<'_> {
2245 pub fn new(doc: &mut BaseDocument) -> ViewportMut<'_> {
2246 let initial_viewport = doc.viewport.clone();
2247 ViewportMut {
2248 doc,
2249 initial_viewport,
2250 }
2251 }
2252}
2253impl Deref for ViewportMut<'_> {
2254 type Target = Viewport;
2255
2256 fn deref(&self) -> &Self::Target {
2257 &self.doc.viewport
2258 }
2259}
2260impl DerefMut for ViewportMut<'_> {
2261 fn deref_mut(&mut self) -> &mut Self::Target {
2262 &mut self.doc.viewport
2263 }
2264}
2265impl Drop for ViewportMut<'_> {
2266 fn drop(&mut self) {
2267 if self.doc.viewport == self.initial_viewport {
2268 return;
2269 }
2270
2271 self.doc.set_stylist_device(make_device(
2272 &self.doc.viewport,
2273 self.doc.media_type.clone(),
2274 self.doc.font_ctx.clone(),
2275 ));
2276 self.doc.scroll_viewport_by(0.0, 0.0); let scale_has_changed =
2279 self.doc.viewport().scale_f64() != self.initial_viewport.scale_f64();
2280 if scale_has_changed {
2281 self.doc.invalidate_inline_contexts();
2282 self.doc.shell_provider.request_redraw();
2283 }
2284 }
2285}
2286
2287fn html_tag(name: &str) -> markup5ever::QualName {
2289 markup5ever::QualName::new(None, markup5ever::ns!(html), name.into())
2290}
2291
2292#[cfg(test)]
2293mod test {
2294 use style::media_queries::MediaType;
2295 use style_dom::ElementState;
2296
2297 use std::sync::{
2298 Arc,
2299 atomic::{AtomicUsize, Ordering},
2300 };
2301
2302 use blitz_traits::shell::{ColorScheme, ShellProvider, Viewport};
2303
2304 use crate::{
2305 Attribute, BaseDocument, DocumentConfig, ElementData, NodeData, NodeId, qual_name,
2306 };
2307
2308 #[test]
2309 fn media_type_defaults_to_screen() {
2310 let mut document = BaseDocument::new(DocumentConfig::default());
2311 assert_eq!(*document.media_type(), MediaType::screen());
2312 assert_eq!(document.stylist_device().media_type(), MediaType::screen());
2313 }
2314
2315 #[test]
2316 fn media_type_honors_config() {
2317 let mut document = BaseDocument::new(DocumentConfig {
2318 media_type: Some(MediaType::print()),
2319 ..Default::default()
2320 });
2321 assert_eq!(*document.media_type(), MediaType::print());
2322 assert_eq!(document.stylist_device().media_type(), MediaType::print());
2323 }
2324
2325 #[test]
2326 fn set_media_type_updates_stylist_device() {
2327 let mut document = BaseDocument::new(DocumentConfig::default());
2328 assert_eq!(document.stylist_device().media_type(), MediaType::screen());
2329
2330 document.set_media_type(MediaType::print());
2331 assert_eq!(*document.media_type(), MediaType::print());
2332 assert_eq!(document.stylist_device().media_type(), MediaType::print());
2333 }
2334
2335 #[test]
2336 fn removing_a_node_forgets_it_as_focused_and_pressed() {
2337 let mut document = BaseDocument::new(DocumentConfig::default());
2343 let button = document.create_node(NodeData::Element(Box::new(ElementData::new(
2344 qual_name!("button"),
2345 Vec::new(),
2346 ))));
2347 let root = document.root_node().id;
2348
2349 let mut mutator = document.mutate();
2350 mutator.append_children(root, &[button]);
2351 drop(mutator);
2352
2353 document.set_focus_to(button);
2354 document.set_mousedown_node_id(Some(button));
2355 assert_eq!(document.get_focussed_node_id(), Some(button));
2356 assert_eq!(document.mousedown_node_id, Some(button));
2357
2358 let mut mutator = document.mutate();
2359 mutator.remove_node(button);
2360 drop(mutator);
2361
2362 assert_eq!(
2363 document.get_focussed_node_id(),
2364 None,
2365 "a removed node must not stay focused"
2366 );
2367 assert_eq!(
2368 document.mousedown_node_id, None,
2369 "a removed node must not stay the pressed node"
2370 );
2371 }
2372
2373 #[test]
2374 fn dropping_a_child_clears_a_hidden_retained_layout_edge() {
2375 let mut document = BaseDocument::new(DocumentConfig {
2376 viewport: Some(Viewport::new(800, 600, 1.0, ColorScheme::Light)),
2377 ..Default::default()
2378 });
2379 let root = document.root_node().id;
2380 let (parent, child) = {
2381 let mut mutator = document.mutate();
2382 let parent = mutator.create_element(qual_name!("div"), vec![]);
2383 let child = mutator.create_element(qual_name!("button"), vec![]);
2384 mutator.set_style_property(parent, "width", "200px");
2385 mutator.set_style_property(parent, "height", "100px");
2386 mutator.append_children(parent, &[child]);
2387 mutator.append_children(root, &[parent]);
2388 (parent, child)
2389 };
2390
2391 document.resolve(0.0);
2392 {
2393 let mut mutator = document.mutate();
2394 mutator.set_style_property(parent, "display", "none");
2395 }
2396 document.resolve(0.0);
2397 assert!(
2398 document.nodes[parent]
2399 .layout_children
2400 .borrow()
2401 .as_ref()
2402 .is_some_and(|children| children.contains(&child)),
2403 "the hidden subtree should retain the layout edge that makes this regression possible"
2404 );
2405
2406 document.mutate().remove_and_drop_node(child);
2407 assert!(document.get_node(child).is_none(), "the child was freed");
2408 assert!(
2409 document.nodes[parent].layout_children.borrow().is_none(),
2410 "the surviving layout parent must not retain the freed key"
2411 );
2412
2413 document.resolve(0.0);
2415 }
2416
2417 #[test]
2418 fn mutator_remove_disabled() {
2419 let mut document = BaseDocument::new(DocumentConfig::default());
2420 let id = document.create_node(NodeData::Element(Box::new(ElementData::new(
2421 qual_name!("button"),
2422 vec![Attribute {
2423 name: qual_name!("disabled"),
2424 value: "".into(),
2425 }],
2426 ))));
2427
2428 let node = document.get_node(id).unwrap();
2429 assert!(
2430 node.element_state().contains(ElementState::DISABLED),
2431 "form node is disabled"
2432 );
2433 assert!(
2434 !node.element_state().contains(ElementState::ENABLED),
2435 "form node is not enabled yet"
2436 );
2437
2438 let mut mutator = document.mutate();
2439 mutator.clear_attribute(id, qual_name!("disabled"));
2440 drop(mutator);
2441
2442 let node = document.get_node(id).unwrap();
2443 assert!(
2444 !node.element_state().contains(ElementState::DISABLED),
2445 "form node is no longer disabled"
2446 );
2447 assert!(
2448 node.element_state().contains(ElementState::ENABLED),
2449 "form node is enabled"
2450 );
2451 }
2452
2453 #[test]
2454 fn mutator_set_disabled() {
2455 let mut document = BaseDocument::new(DocumentConfig::default());
2456 let id = document.create_node(NodeData::Element(Box::new(ElementData::new(
2457 qual_name!("button"),
2458 vec![],
2459 ))));
2460
2461 let node = document.get_node(id).unwrap();
2462 assert!(
2463 !node.element_state().contains(ElementState::DISABLED),
2464 "form node is not disabled"
2465 );
2466 assert!(
2467 node.element_state().contains(ElementState::ENABLED),
2468 "form node is enabled"
2469 );
2470
2471 let mut mutator = document.mutate();
2472 mutator.set_attribute(id, qual_name!("disabled"), "");
2473 drop(mutator);
2474
2475 let node = document.get_node(id).unwrap();
2476
2477 assert!(
2478 node.element_state().contains(ElementState::DISABLED),
2479 "form node is disabled"
2480 );
2481 assert!(
2482 !node.element_state().contains(ElementState::ENABLED),
2483 "form node is no longer enabled enabled"
2484 );
2485 }
2486
2487 #[test]
2488 fn mutator_set_disabled_invalid_node() {
2489 let mut document = BaseDocument::new(DocumentConfig::default());
2490 let id = document.create_node(NodeData::Element(Box::new(ElementData::new(
2491 qual_name!("a"),
2492 vec![],
2493 ))));
2494
2495 let node = document.get_node(id).unwrap();
2496 assert!(
2497 !node.element_state().contains(ElementState::DISABLED),
2498 "form node is not disabled"
2499 );
2500 assert!(
2501 !node.element_state().contains(ElementState::ENABLED),
2502 "form node is enabled"
2503 );
2504
2505 let mut mutator = document.mutate();
2506 mutator.set_attribute(id, qual_name!("disabled"), "");
2507 drop(mutator);
2508
2509 let node = document.get_node(id).unwrap();
2510 assert!(
2511 !node.element_state().contains(ElementState::DISABLED),
2512 "form node is not disabled"
2513 );
2514 assert!(
2515 !node.element_state().contains(ElementState::ENABLED),
2516 "form node is enabled"
2517 );
2518 }
2519
2520 #[test]
2521 fn mutator_id_attribute_updates_id_map() {
2522 let mut document = BaseDocument::new(DocumentConfig::default());
2523 let root_id = document.root_node().id;
2524
2525 let node_id = {
2526 let mut mutator = document.mutate();
2527 let node_id = mutator.create_element(
2528 qual_name!("div"),
2529 vec![Attribute {
2530 name: qual_name!("id"),
2531 value: "old".into(),
2532 }],
2533 );
2534 mutator.append_children(root_id, &[node_id]);
2535 node_id
2536 };
2537 assert_eq!(document.get_element_by_id("old"), Some(node_id));
2538
2539 {
2540 let mut mutator = document.mutate();
2541 mutator.set_attribute(node_id, qual_name!("id"), "new");
2542 }
2543 assert_eq!(document.get_element_by_id("new"), Some(node_id));
2544 assert_eq!(document.get_element_by_id("old"), None);
2545
2546 {
2547 let mut mutator = document.mutate();
2548 mutator.clear_attribute(node_id, qual_name!("id"));
2549 }
2550 assert_eq!(document.get_element_by_id("new"), None);
2551 }
2552
2553 #[test]
2554 fn get_element_by_id_duplicate_ids_first_in_tree_order_wins() {
2555 let mut document = BaseDocument::new(DocumentConfig::default());
2556 let root_id = document.root_node().id;
2557
2558 let (first_id, second_id) = {
2559 let mut mutator = document.mutate();
2560 let first_id = mutator.create_element(qual_name!("div"), vec![]);
2561 let second_id = mutator.create_element(qual_name!("div"), vec![]);
2562 mutator.append_children(root_id, &[first_id, second_id]);
2563 mutator.set_attribute(second_id, qual_name!("id"), "dup");
2566 mutator.set_attribute(first_id, qual_name!("id"), "dup");
2567 (first_id, second_id)
2568 };
2569 assert_eq!(document.get_element_by_id("dup"), Some(first_id));
2570
2571 {
2572 let mut mutator = document.mutate();
2573 mutator.remove_node(first_id);
2574 }
2575 assert_eq!(document.get_element_by_id("dup"), Some(second_id));
2576 }
2577
2578 #[derive(Default)]
2579 struct RedrawShell {
2580 redraw_requests: AtomicUsize,
2581 }
2582
2583 impl ShellProvider for RedrawShell {
2584 fn request_redraw(&self) {
2585 self.redraw_requests.fetch_add(1, Ordering::Relaxed);
2586 }
2587 }
2588
2589 #[test]
2590 fn mutator_requests_redraw_only_after_mutation() {
2591 let shell = Arc::new(RedrawShell::default());
2592 let mut document = BaseDocument::new(DocumentConfig {
2593 shell_provider: Some(shell.clone()),
2594 ..Default::default()
2595 });
2596 let root_id = document.root_node().id;
2597
2598 {
2599 let mut mutator = document.mutate();
2600 let parent_id = mutator.create_element(qual_name!("div"), vec![]);
2601 let child_id = mutator.create_element(qual_name!("span"), vec![]);
2602 mutator.append_children(parent_id, &[child_id]);
2603 mutator.remove_and_drop_all_children(parent_id);
2604 mutator.set_attribute(parent_id, qual_name!("id"), "detached");
2605 }
2606 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 0);
2607
2608 {
2609 let mutator = document.mutate();
2610 assert_eq!(mutator.child_ids(root_id).len(), 0);
2611 }
2612
2613 {
2614 let mut mutator = document.mutate();
2615 let node_id = mutator.create_element(qual_name!("div"), vec![]);
2616 mutator.append_children(root_id, &[node_id]);
2617 mutator.set_attribute(node_id, qual_name!("id"), "in-document");
2618 }
2619 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 1);
2620
2621 {
2622 let mut mutator = document.mutate();
2623 let parent_id = mutator.create_element(qual_name!("div"), vec![]);
2624 let child_id = mutator.create_element(qual_name!("span"), vec![]);
2625 mutator.append_children(root_id, &[parent_id]);
2626 mutator.append_children(parent_id, &[child_id]);
2627 mutator.remove_and_drop_all_children(parent_id);
2628 }
2629 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 2);
2630
2631 {
2632 let mut mutator = document.mutate();
2633 let parent_id = mutator.create_element(qual_name!("div"), vec![]);
2634 let child_id = mutator.create_element(qual_name!("span"), vec![]);
2635 let detached_target_id = mutator.create_element(qual_name!("div"), vec![]);
2636 mutator.append_children(root_id, &[parent_id]);
2637 mutator.append_children(parent_id, &[child_id]);
2638 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 2);
2639 mutator.append_children(detached_target_id, &[child_id]);
2640 }
2641 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 3);
2642 }
2643
2644 #[test]
2645 fn moving_subtree_out_of_document_clears_in_document_flag() {
2646 let shell = Arc::new(RedrawShell::default());
2647 let mut document = BaseDocument::new(DocumentConfig {
2648 shell_provider: Some(shell.clone()),
2649 ..Default::default()
2650 });
2651 let root_id = document.root_node().id;
2652 let (child_id, grandchild_id, detached_parent_id) = {
2653 let mut mutator = document.mutate();
2654 let in_document_parent_id = mutator.create_element(qual_name!("div"), vec![]);
2655 let child_id = mutator.create_element(qual_name!("div"), vec![]);
2656 let grandchild_id = mutator.create_element(qual_name!("span"), vec![]);
2657 let detached_parent_id = mutator.create_element(qual_name!("section"), vec![]);
2658 mutator.append_children(root_id, &[in_document_parent_id]);
2659 mutator.append_children(in_document_parent_id, &[child_id]);
2660 mutator.append_children(child_id, &[grandchild_id]);
2661 (child_id, grandchild_id, detached_parent_id)
2662 };
2663 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 1);
2664 assert!(document.get_node(child_id).unwrap().flags.is_in_document());
2665 assert!(
2666 document
2667 .get_node(grandchild_id)
2668 .unwrap()
2669 .flags
2670 .is_in_document()
2671 );
2672
2673 {
2674 let mut mutator = document.mutate();
2675 mutator.append_children(detached_parent_id, &[child_id]);
2676 }
2677 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 2);
2678 assert!(!document.get_node(child_id).unwrap().flags.is_in_document());
2679 assert!(
2680 !document
2681 .get_node(grandchild_id)
2682 .unwrap()
2683 .flags
2684 .is_in_document()
2685 );
2686
2687 {
2688 let mut mutator = document.mutate();
2689 mutator.set_attribute(child_id, qual_name!("id"), "detached");
2690 }
2691 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 2);
2692
2693 {
2694 let mut mutator = document.mutate();
2695 mutator.append_children(root_id, &[child_id]);
2696 }
2697 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 3);
2698 assert!(document.get_node(child_id).unwrap().flags.is_in_document());
2699 assert!(
2700 document
2701 .get_node(grandchild_id)
2702 .unwrap()
2703 .flags
2704 .is_in_document()
2705 );
2706 }
2707
2708 #[test]
2720 fn a_paint_only_restyle_refreshes_the_calc_the_taffy_style_points_at() {
2721 use style::servo_arc::Arc as ServoArc;
2722
2723 let mut document = BaseDocument::new(DocumentConfig {
2724 viewport: Some(Viewport::new(800, 600, 1.0, ColorScheme::Light)),
2725 ..Default::default()
2726 });
2727 let root_id = document.root_node().id;
2728
2729 let (header_id, chip_id) = {
2730 let mut mutator = document.mutate();
2731 let header_id = mutator.create_element(qual_name!("div"), vec![]);
2732 let chip_id = mutator.create_element(qual_name!("span"), vec![]);
2733 mutator.set_style_property(header_id, "position", "relative");
2734 mutator.set_style_property(header_id, "width", "800px");
2735 mutator.set_style_property(header_id, "height", "60px");
2736 mutator.set_style_property(chip_id, "position", "absolute");
2737 mutator.set_style_property(chip_id, "max-width", "calc(100% - 24px)");
2738 mutator.set_style_property(chip_id, "color", "rgb(1, 2, 3)");
2739 mutator.append_children(header_id, &[chip_id]);
2740 mutator.append_children(root_id, &[header_id]);
2741 (header_id, chip_id)
2742 };
2743
2744 document.resolve(0.0);
2745
2746 {
2752 let mut mutator = document.mutate();
2753 mutator.set_style_property(header_id, "color", "rgb(4, 5, 6)");
2754 }
2755 document.resolve(0.0);
2756
2757 let node = document.get_node(chip_id).unwrap();
2758 let stylo_data = node.stylo_element_data_opt().and_then(|data| data.get());
2759 let primary = stylo_data
2760 .as_ref()
2761 .and_then(|data| data.styles.get_primary())
2762 .expect("the chip is styled");
2763 let source = node
2764 .style_source_opt()
2765 .expect("a styled node records the computed values its taffy style was built from");
2766
2767 assert!(
2768 ServoArc::ptr_eq(primary, source),
2769 "the cached taffy style still points into computed values that a restyle replaced, \
2770 so every calc() in it is a dangling pointer",
2771 );
2772 }
2773
2774 #[test]
2775 fn style_property_updates_nested_layout() {
2776 let mut document = BaseDocument::new(DocumentConfig {
2777 viewport: Some(Viewport::new(800, 600, 1.0, ColorScheme::Light)),
2778 ..Default::default()
2779 });
2780 let root_id = document.root_node().id;
2781
2782 let mover_id = {
2783 let mut mutator = document.mutate();
2784 let parent_id = mutator.create_element(qual_name!("div"), vec![]);
2785 let mover_id = mutator.create_element(qual_name!("div"), vec![]);
2786 mutator.set_style_property(parent_id, "position", "relative");
2787 mutator.set_style_property(parent_id, "width", "800px");
2788 mutator.set_style_property(parent_id, "height", "600px");
2789 mutator.set_style_property(mover_id, "position", "absolute");
2790 mutator.set_style_property(mover_id, "left", "0px");
2791 mutator.set_style_property(mover_id, "top", "0px");
2792 mutator.append_children(parent_id, &[mover_id]);
2793 mutator.append_children(root_id, &[parent_id]);
2794 mover_id
2795 };
2796
2797 document.resolve(0.0);
2798 assert_eq!(
2799 document
2800 .get_node(mover_id)
2801 .unwrap()
2802 .final_layout()
2803 .location
2804 .x,
2805 0.0
2806 );
2807
2808 {
2809 let mut mutator = document.mutate();
2810 mutator.set_style_property(mover_id, "left", "120px");
2811 }
2812
2813 document.resolve(0.0);
2814 assert_eq!(
2815 document
2816 .get_node(mover_id)
2817 .unwrap()
2818 .final_layout()
2819 .location
2820 .x,
2821 120.0
2822 );
2823 }
2824
2825 fn doc_with_a_comment() -> (BaseDocument, NodeId, NodeId, NodeId) {
2828 let mut doc = BaseDocument::new(DocumentConfig {
2829 viewport: Some(Viewport::new(400, 300, 1.0, ColorScheme::Light)),
2830 ..Default::default()
2831 });
2832 let root_id = doc.root_node().id;
2833
2834 let mut mutr = doc.mutate();
2835 let html = mutr.create_element(qual_name!("html"), vec![]);
2836 let body = mutr.create_element(qual_name!("body"), vec![]);
2837 let container = mutr.create_element(qual_name!("div"), vec![]);
2838 let text = mutr.create_text_node("text");
2839 let comment = mutr.create_comment_node("comment");
2840 mutr.append_children(container, &[text, comment]);
2841 mutr.append_children(body, &[container]);
2842 mutr.append_children(html, &[body]);
2843 mutr.append_children(root_id, &[html]);
2844 drop(mutr);
2845
2846 doc.resolve(0.0);
2847 (doc, container, text, comment)
2848 }
2849
2850 #[test]
2854 fn setting_a_comments_data_writes_the_contents() {
2855 let (mut doc, _container, _text, comment) = doc_with_a_comment();
2856
2857 doc.mutate().set_node_text(comment, "rewritten");
2858
2859 let NodeData::Comment { contents } = &doc.get_node(comment).unwrap().data else {
2860 panic!("expected a comment node");
2861 };
2862 assert_eq!(contents, "rewritten");
2863 }
2864
2865 #[test]
2872 fn setting_a_comments_data_does_not_dirty_layout() {
2873 let (mut doc, container, text, comment) = doc_with_a_comment();
2874
2875 let container_damage_before = doc.get_node(container).unwrap().damage();
2876 let comment_damage_before = doc.get_node(comment).unwrap().damage();
2877
2878 doc.mutate().set_node_text(comment, "rewritten");
2879
2880 assert_eq!(
2881 doc.get_node(comment).unwrap().damage(),
2882 comment_damage_before,
2883 "writing a comment's data damaged the comment"
2884 );
2885 assert_eq!(
2886 doc.get_node(container).unwrap().damage(),
2887 container_damage_before,
2888 "writing a comment's data damaged its parent, scheduling a relayout \
2889 for a change that cannot affect a pixel"
2890 );
2891
2892 doc.mutate().set_node_text(text, "rewritten");
2893 assert_ne!(
2894 doc.get_node(container).unwrap().damage(),
2895 container_damage_before,
2896 "a text write should damage the parent, so the assertions above can fail"
2897 );
2898 }
2899
2900 #[test]
2903 fn rewriting_a_comment_with_its_own_contents_is_inert() {
2904 let (mut doc, container, _text, comment) = doc_with_a_comment();
2905 let container_damage_before = doc.get_node(container).unwrap().damage();
2906
2907 doc.mutate().set_node_text(comment, "comment");
2908
2909 let NodeData::Comment { contents } = &doc.get_node(comment).unwrap().data else {
2910 panic!("expected a comment node");
2911 };
2912 assert_eq!(contents, "comment");
2913 assert_eq!(
2914 doc.get_node(container).unwrap().damage(),
2915 container_damage_before
2916 );
2917 }
2918}