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