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