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