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