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 clear_layout_of_subtree(doc: &mut BaseDocument, node_id: NodeId) {
768 let mut stack = vec![node_id];
769 while let Some(id) = stack.pop() {
770 let Some(node) = doc.nodes.get_mut(id) else {
771 continue;
772 };
773 if node.data.downcast_element().is_some() {
777 *node.unrounded_layout_mut() = taffy::Layout::with_order(0);
778 *node.final_layout_mut() = taffy::Layout::with_order(0);
779 node.cache_mut().clear();
780 }
781 stack.extend(node.children.iter().copied());
782 }
783 }
784
785 pub fn remove_node(&mut self, node_id: NodeId) {
787 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
788 self.process_removed_subtree(node_id);
792
793 Self::clear_layout_of_subtree(self.doc, node_id);
801
802 let node = &mut self.doc.nodes[node_id];
803
804 if let Some(parent_id) = node.parent.take() {
806 self.mutations_occurred |= node_is_in_document;
807 let parent = &mut self.doc.nodes[parent_id];
808 parent.insert_damage(ALL_DAMAGE);
809 parent.mark_ancestors_dirty();
811 parent.children.retain(|id| *id != node_id);
812 self.maybe_record_node(parent_id);
813 }
814 }
815
816 pub fn remove_and_drop_node(&mut self, node_id: NodeId) -> Option<Node> {
817 self.remove_and_drop_node_with(node_id, &mut |_| {})
818 }
819
820 pub fn remove_and_drop_node_with(
823 &mut self,
824 node_id: NodeId,
825 on_drop: &mut dyn FnMut(NodeId),
826 ) -> Option<Node> {
827 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
828 self.process_removed_subtree(node_id);
829
830 let node = self.doc.drop_node_ignoring_parent_with(node_id, on_drop);
831 self.mutations_occurred |= node_is_in_document;
832
833 if let Some(parent_id) = node.as_ref().and_then(|node| node.parent) {
835 let parent = &mut self.doc.nodes[parent_id];
836 parent.insert_damage(ALL_DAMAGE);
837 let parent_is_in_doc = parent.flags.is_in_document();
838
839 if parent_is_in_doc {
841 if let Some(mut data) = parent
842 .stylo_element_data_opt_mut()
843 .and_then(|s| s.get_mut())
844 {
845 data.hint |= RestyleHint::restyle_subtree();
846 }
847 parent.mark_ancestors_dirty();
849 }
850
851 parent.children.retain(|id| *id != node_id);
852 self.maybe_record_node(parent_id);
853 }
854
855 node
856 }
857
858 pub fn remove_and_drop_all_children(&mut self, node_id: NodeId) {
859 let parent = &mut self.doc.nodes[node_id];
860 let parent_is_in_doc = parent.flags.is_in_document();
861
862 if parent_is_in_doc {
864 if let Some(mut data) = parent
865 .stylo_element_data_opt_mut()
866 .and_then(|s| s.get_mut())
867 {
868 data.hint |= RestyleHint::restyle_subtree();
869 }
870 parent.mark_ancestors_dirty();
872 }
873
874 let children = mem::take(&mut parent.children);
875 self.mutations_occurred |= parent_is_in_doc && !children.is_empty();
876 for child_id in children {
877 self.process_removed_subtree(child_id);
878 let _ = self.doc.drop_node_ignoring_parent(child_id);
879 }
880 self.maybe_record_node(node_id);
881 }
882
883 pub fn remove_node_if_unparented(&mut self, node_id: NodeId) {
885 self.remove_node_if_unparented_with(node_id, &mut |_| {});
886 }
887
888 pub fn remove_node_if_unparented_with(
891 &mut self,
892 node_id: NodeId,
893 on_drop: &mut dyn FnMut(NodeId),
894 ) {
895 if let Some(node) = self.doc.get_node(node_id) {
896 if node.parent.is_none() {
897 self.remove_and_drop_node_with(node_id, on_drop);
898 }
899 }
900 }
901
902 pub fn append_children(&mut self, parent_id: NodeId, child_ids: &[NodeId]) {
904 self.add_children_to_parent(parent_id, child_ids, &|parent, child_ids| {
905 parent.children.extend_from_slice(child_ids);
906 });
907 }
908
909 pub fn insert_nodes_before(&mut self, anchor_node_id: NodeId, new_node_ids: &[NodeId]) {
910 let parent_id = self.doc.nodes[anchor_node_id].parent.unwrap();
911 self.add_children_to_parent(parent_id, new_node_ids, &|parent, child_ids| {
912 let node_child_idx = parent.index_of_child(anchor_node_id).unwrap();
913 parent
914 .children
915 .splice(node_child_idx..node_child_idx, child_ids.iter().copied());
916 });
917 }
918
919 fn add_children_to_parent(
920 &mut self,
921 parent_id: NodeId,
922 child_ids: &[NodeId],
923 insert_children_fn: &dyn Fn(&mut Node, &[NodeId]),
924 ) {
925 let new_parent_is_in_document = self.doc.nodes[parent_id].flags.is_in_document();
926 self.mutations_occurred |= new_parent_is_in_document && !child_ids.is_empty();
927 for child_id in child_ids.iter().copied() {
934 let child = &mut self.doc.nodes[child_id];
935 let child_was_in_doc = child.flags.is_in_document();
936 self.mutations_occurred |= child_was_in_doc;
937 let Some(old_parent_id) = child.parent.take() else {
938 continue;
939 };
940
941 let old_parent = &mut self.doc.nodes[old_parent_id];
942 old_parent.insert_damage(ALL_DAMAGE);
943
944 if child_was_in_doc {
946 if let Some(mut data) = old_parent
947 .stylo_element_data_opt_mut()
948 .and_then(|s| s.get_mut())
949 {
950 data.hint |= RestyleHint::restyle_subtree();
951 }
952 old_parent.mark_ancestors_dirty();
954 }
955
956 old_parent.children.retain(|id| *id != child_id);
957 self.maybe_record_node(old_parent_id);
958 }
959
960 let new_parent = &mut self.doc.nodes[parent_id];
961 new_parent.insert_damage(ALL_DAMAGE);
962
963 if new_parent_is_in_document {
965 if let Some(mut data) = new_parent
966 .stylo_element_data_opt_mut()
967 .and_then(|s| s.get_mut())
968 {
969 data.hint |= RestyleHint::restyle_subtree();
970 }
971 new_parent.mark_ancestors_dirty();
973 }
974
975 insert_children_fn(new_parent, child_ids);
976
977 for child_id in child_ids.iter().copied() {
978 let child = &mut self.doc.nodes[child_id];
979 let child_was_in_doc = child.flags.is_in_document();
980 child.parent = Some(parent_id);
981
982 if new_parent_is_in_document && !child_was_in_doc {
983 self.process_added_subtree(child_id);
984 } else if !new_parent_is_in_document && child_was_in_doc {
985 self.process_removed_subtree(child_id);
986 }
987 }
988
989 self.maybe_record_node(parent_id);
990 }
991
992 pub fn insert_nodes_after(&mut self, anchor_node_id: NodeId, new_node_ids: &[NodeId]) {
994 match self.next_sibling_id(anchor_node_id) {
995 Some(id) => self.insert_nodes_before(id, new_node_ids),
996 None => {
997 let parent_id = self.parent_id(anchor_node_id).unwrap();
998 self.append_children(parent_id, new_node_ids)
999 }
1000 }
1001 }
1002
1003 pub fn reparent_children(&mut self, old_parent_id: NodeId, new_parent_id: NodeId) {
1004 let child_ids = std::mem::take(&mut self.doc.nodes[old_parent_id].children);
1005 self.maybe_record_node(old_parent_id);
1006 self.append_children(new_parent_id, &child_ids);
1007 }
1008
1009 pub fn replace_node_with(&mut self, anchor_node_id: NodeId, new_node_ids: &[NodeId]) {
1010 self.insert_nodes_before(anchor_node_id, new_node_ids);
1011 self.remove_node(anchor_node_id);
1012 }
1013}
1014
1015impl<'doc> DocumentMutator<'doc> {
1016 pub fn flush(&mut self) {
1017 if self.recompute_is_animating {
1018 self.doc.has_canvas = self.doc.compute_has_canvas();
1019 }
1020
1021 if let Some(id) = self.title_node {
1022 let title = self.doc.nodes[id].text_content();
1023 self.doc.shell_provider.set_window_title(title);
1024 }
1025
1026 for id in self.style_nodes.drain() {
1028 self.doc.process_style_element(id);
1029 }
1030
1031 for id in self.form_nodes.drain() {
1032 self.doc.reset_form_owner(id);
1033 }
1034
1035 #[cfg(feature = "autofocus")]
1036 if let Some(node_id) = self.node_to_autofocus.take() {
1037 if self.doc.get_node(node_id).is_some() {
1038 self.doc.set_focus_to(node_id);
1039 }
1040 }
1041
1042 #[cfg(feature = "shadow-dom")]
1043 self.dispatch_custom_element_attr_changes();
1044 }
1045
1046 #[cfg(feature = "shadow-dom")]
1048 fn dispatch_custom_element_attr_changes(&mut self) {
1049 if self.custom_element_attr_changes.is_empty() {
1050 return;
1051 }
1052 let changes = mem::take(&mut self.custom_element_attr_changes);
1053 for (node_id, name, old_value, new_value) in changes {
1054 let tag = self
1058 .doc
1059 .get_node(node_id)
1060 .and_then(|node| node.element_data())
1061 .map(|el| el.name.local.clone());
1062 let observed = tag
1063 .as_ref()
1064 .and_then(|tag| self.doc.custom_element_registry.get(tag))
1065 .map(|def| def.observes(&name.local))
1066 .unwrap_or(true);
1067 if !observed {
1068 continue;
1069 }
1070
1071 let Some(shadow_root_id) = self
1072 .doc
1073 .get_node(node_id)
1074 .and_then(|node| node.shadow_root_id())
1075 else {
1076 continue;
1077 };
1078 let Some(mut controller) = self.take_controller(node_id) else {
1079 continue;
1080 };
1081 {
1082 let mut ctx = crate::node::CustomElementCtx {
1083 mutator: self,
1084 host_id: node_id,
1085 shadow_root_id,
1086 };
1087 controller.attribute_changed(
1088 &mut ctx,
1089 &name.local,
1090 old_value.as_deref(),
1091 new_value.as_deref(),
1092 );
1093 }
1094 self.restore_controller(node_id, controller, false);
1095 }
1096 }
1097
1098 pub fn set_inner_html(&mut self, node_id: NodeId, html: &str) {
1099 self.remove_and_drop_all_children(node_id);
1100 self.doc
1101 .html_parser_provider
1102 .clone()
1103 .parse_inner_html(self, node_id, html);
1104 }
1105
1106 fn flush_eager_ops(&mut self) {
1107 let mut ops = mem::take(&mut self.eager_op_queue);
1108 for op in ops.drain(0..) {
1109 match op {
1110 SpecialOp::LoadImage(node_id) => self.load_image(node_id),
1111 SpecialOp::LoadIframe(node_id) => self.load_iframe(node_id),
1112 SpecialOp::LoadStylesheet(node_id) => self.load_linked_stylesheet(node_id),
1113 SpecialOp::UnloadStylesheet(node_id) => self.unload_stylesheet(node_id),
1114 SpecialOp::LoadCustomPaintSource(node_id) => self.load_custom_paint_src(node_id),
1115 SpecialOp::ProcessButtonInput(node_id) => self.process_button_input(node_id),
1116 SpecialOp::UnloadSubDocument(node_id) => self.remove_sub_document(node_id),
1117 #[cfg(feature = "custom-widget")]
1118 SpecialOp::UnloadCustomWidget(node_id) => self.remove_custom_widget(node_id),
1119 #[cfg(feature = "shadow-dom")]
1120 SpecialOp::UpgradeCustomElement(node_id) => self.upgrade_custom_element(node_id),
1121 #[cfg(feature = "shadow-dom")]
1122 SpecialOp::DisconnectCustomElement(node_id) => {
1123 self.disconnect_custom_element(node_id)
1124 }
1125 }
1126 }
1127
1128 self.eager_op_queue = ops;
1130 }
1131
1132 fn process_added_subtree(&mut self, node_id: NodeId) {
1133 self.doc.iter_subtree_mut(node_id, |node_id, doc| {
1134 let node = &mut doc.nodes[node_id];
1135 node.flags.set(NodeFlags::IS_IN_DOCUMENT, true);
1136 node.insert_damage(ALL_DAMAGE);
1137
1138 if let Some(id_attr) = node.attr(local_name!("id")).map(ToString::to_string) {
1140 doc.add_to_id_map(&id_attr, node_id);
1141 }
1142
1143 let node = &mut doc.nodes[node_id];
1144 let NodeData::Element(ref mut element) = node.data else {
1145 return;
1146 };
1147
1148 let tag = element.name.local.as_ref();
1150 match tag {
1151 "title" if element.name.ns == ns!(html) => self.title_node = Some(node_id),
1152 "link" => self.eager_op_queue.push(SpecialOp::LoadStylesheet(node_id)),
1153 "img" => self.eager_op_queue.push(SpecialOp::LoadImage(node_id)),
1154 "iframe" => self.eager_op_queue.push(SpecialOp::LoadIframe(node_id)),
1155 "canvas" => self
1156 .eager_op_queue
1157 .push(SpecialOp::LoadCustomPaintSource(node_id)),
1158 "style" => {
1159 self.style_nodes.insert(node_id);
1160 }
1161 "button" | "fieldset" | "input" | "select" | "textarea" | "object" | "output" => {
1162 self.eager_op_queue
1163 .push(SpecialOp::ProcessButtonInput(node_id));
1164 self.form_nodes.insert(node_id);
1165 }
1166 _ => {}
1167 }
1168
1169 #[cfg(feature = "shadow-dom")]
1173 {
1174 let needs_upgrade = doc.custom_element_registry.contains(&element.name.local)
1175 && element.custom_element_data().is_none();
1176 if needs_upgrade {
1177 self.eager_op_queue
1178 .push(SpecialOp::UpgradeCustomElement(node_id));
1179 }
1180 }
1181
1182 #[cfg(feature = "autofocus")]
1193 if node.is_focussable() {
1194 if let NodeData::Element(ref element) = node.data {
1195 if element.attr(local_name!("autofocus")).is_some() {
1196 self.node_to_autofocus = Some(node_id);
1197 }
1198 }
1199 }
1200 });
1201
1202 self.flush_eager_ops();
1203 }
1204
1205 fn process_removed_subtree(&mut self, node_id: NodeId) {
1206 self.doc.iter_subtree_mut(node_id, |node_id, doc| {
1207 doc.nodes[node_id]
1208 .flags
1209 .set(NodeFlags::IS_IN_DOCUMENT, false);
1210
1211 doc.clear_interaction_state_for_removed_node(node_id);
1215
1216 let node = &mut doc.nodes[node_id];
1217
1218 if doc.focus_node_id == Some(node_id) {
1235 doc.focus_node_id = None;
1236 }
1237 if doc.mousedown_node_id == Some(node_id) {
1238 doc.mousedown_node_id = None;
1239 }
1240
1241 if doc.text_selection.anchor.node_or_parent == Some(node_id)
1244 || doc.text_selection.focus.node_or_parent == Some(node_id)
1245 {
1246 doc.text_selection.clear();
1247 }
1248
1249 if node.has_snapshot() {
1252 let opaque_id = style::dom::TNode::opaque(&&*node);
1253 doc.snapshots.remove(&opaque_id);
1254 node.set_has_snapshot(false);
1255 }
1256
1257 if let Some(id_attr) = node.attr(local_name!("id")).map(ToString::to_string) {
1259 doc.remove_from_id_map(&id_attr, node_id);
1260 }
1261
1262 let node = &mut doc.nodes[node_id];
1263 let NodeData::Element(ref mut element) = node.data else {
1264 return;
1265 };
1266
1267 match &element.special_data {
1268 SpecialElementData::SubDocument(_) => {
1269 self.eager_op_queue
1270 .push(SpecialOp::UnloadSubDocument(node_id));
1271 }
1272 #[cfg(feature = "custom-widget")]
1273 SpecialElementData::CustomWidget(_) => {
1274 self.eager_op_queue
1275 .push(SpecialOp::UnloadCustomWidget(node_id));
1276 }
1277 #[cfg(feature = "shadow-dom")]
1278 SpecialElementData::CustomElement(_) => {
1279 self.eager_op_queue
1280 .push(SpecialOp::DisconnectCustomElement(node_id));
1281 }
1282 SpecialElementData::Stylesheet(_) => self
1283 .eager_op_queue
1284 .push(SpecialOp::UnloadStylesheet(node_id)),
1285 SpecialElementData::Image(_) => {}
1286 SpecialElementData::Canvas(_) => {
1287 self.recompute_is_animating = true;
1288 }
1289 SpecialElementData::TableRoot(_) => {}
1290 SpecialElementData::TextInput(_) => {}
1291 SpecialElementData::CheckboxInput(_) => {}
1292 #[cfg(feature = "file-input")]
1293 SpecialElementData::FileInput(_) => {}
1294 SpecialElementData::None => {}
1295 }
1296 });
1297
1298 self.flush_eager_ops();
1299 }
1300
1301 fn maybe_record_node(&mut self, node_id: impl Into<Option<NodeId>>) {
1302 let Some(node_id) = node_id.into() else {
1303 return;
1304 };
1305
1306 let Some(element) = self.doc.nodes[node_id].data.downcast_element() else {
1307 return;
1308 };
1309
1310 match element.name.local.as_ref() {
1311 "title" if element.name.ns == ns!(html) => self.title_node = Some(node_id),
1312 "style" => {
1313 self.style_nodes.insert(node_id);
1314 }
1315 _ => {}
1316 }
1317 }
1318
1319 fn load_linked_stylesheet(&mut self, target_id: NodeId) {
1320 let node = &self.doc.nodes[target_id];
1321
1322 let mut is_in_head = false;
1323 let mut parent_id = node.parent;
1324 while let Some(id) = parent_id
1325 && !is_in_head
1326 {
1327 let parent = &self.doc.nodes[id];
1328 is_in_head |= parent.data.is_element_with_tag_name(&local_name!("head"));
1329 parent_id = parent.parent;
1330 }
1331
1332 let rel_attr = node.attr(local_name!("rel"));
1333 let href_attr = node.attr(local_name!("href"));
1334
1335 let (Some(rels), Some(href)) = (rel_attr, href_attr) else {
1336 return;
1337 };
1338 if !rels.split_ascii_whitespace().any(|rel| rel == "stylesheet") {
1339 return;
1340 }
1341
1342 let url = self.doc.resolve_url(href);
1343 let handler = ResourceHandler::new(
1344 self.doc.tx.clone(),
1345 self.doc.id(),
1346 Some(node.id),
1347 self.doc.shell_provider.clone(),
1348 StylesheetHandler {
1349 source_url: url.clone(),
1350 guard: self.doc.guard.clone(),
1351 net_provider: self.doc.net_provider.clone(),
1352 abort_signal: self.doc.abort_signal.clone(),
1353 },
1354 );
1355
1356 if is_in_head && !self.doc.net_provider.is_noop() {
1357 self.doc
1358 .pending_critical_resources
1359 .insert(handler.request_id());
1360 }
1361
1362 self.doc.net_provider.fetch(
1363 self.doc.id(),
1364 self.doc.build_request(url),
1365 Box::new(handler),
1366 );
1367 }
1368
1369 fn unload_stylesheet(&mut self, node_id: NodeId) {
1370 let node = &mut self.doc.nodes[node_id];
1371 let Some(element) = node.element_data_mut() else {
1372 unreachable!();
1373 };
1374 let SpecialElementData::Stylesheet(stylesheet) = element.special_data.take() else {
1375 unreachable!();
1376 };
1377
1378 let guard = self.doc.guard.read();
1379 self.doc.stylist.remove_stylesheet(stylesheet, &guard);
1380 self.doc
1381 .stylist
1382 .force_stylesheet_origins_dirty(OriginSet::all());
1383
1384 self.doc.nodes_to_stylesheet.remove(&node_id);
1385 }
1386
1387 fn load_image(&mut self, target_id: NodeId) {
1388 let node = &self.doc.nodes[target_id];
1389 if let Some(raw_src) = node.attr(local_name!("src")) {
1390 if !raw_src.is_empty() {
1391 let src = self.doc.resolve_url(raw_src);
1392 let src_string = src.as_str();
1393
1394 if let Some(cached_image) = self.doc.image_cache.get(src_string) {
1396 #[cfg(feature = "tracing")]
1397 tracing::info!("Loading image {src_string} from cache");
1398 let node = &mut self.doc.nodes[target_id];
1399 node.element_data_mut().unwrap().special_data =
1400 SpecialElementData::Image(Box::new(cached_image.clone()));
1401 node.cache_mut().clear();
1402 node.insert_damage(ALL_DAMAGE);
1403 return;
1404 }
1405
1406 if let Some(waiting_list) = self.doc.pending_images.get_mut(src_string) {
1408 #[cfg(feature = "tracing")]
1409 tracing::info!("Image {src_string} already pending, queueing node {target_id}");
1410 waiting_list.push((target_id, ImageType::Image));
1411 return;
1412 }
1413
1414 #[cfg(feature = "tracing")]
1416 tracing::info!("Fetching image {src_string}");
1417 self.doc
1418 .pending_images
1419 .insert(src_string.to_string(), vec![(target_id, ImageType::Image)]);
1420
1421 self.doc.net_provider.fetch(
1422 self.doc.id(),
1423 self.doc.build_request(src),
1424 ResourceHandler::boxed(
1425 self.doc.tx.clone(),
1426 self.doc.id(),
1427 None, self.doc.shell_provider.clone(),
1429 ImageHandler::new(ImageType::Image),
1430 ),
1431 );
1432 }
1433 }
1434 }
1435
1436 fn load_iframe(&mut self, target_id: NodeId) {
1437 if self.doc.subdocument_depth >= crate::iframe::MAX_SUBDOCUMENT_DEPTH {
1438 #[cfg(feature = "tracing")]
1439 tracing::warn!(
1440 "Not loading iframe: max sub-document nesting depth ({}) reached",
1441 crate::iframe::MAX_SUBDOCUMENT_DEPTH
1442 );
1443 return;
1444 }
1445
1446 let node = &self.doc.nodes[target_id];
1447 let Some(element) = node.element_data() else {
1448 return;
1449 };
1450
1451 if let Some(srcdoc) = element.attr(local_name!("srcdoc")) {
1453 let srcdoc = srcdoc.to_string();
1454 self.doc.load_iframe_srcdoc(target_id, &srcdoc);
1455 return;
1456 }
1457
1458 let Some(raw_src) = element.attr(local_name!("src")) else {
1459 return;
1460 };
1461 if raw_src.is_empty() {
1462 return;
1463 }
1464 let Some(url) = self.doc.url.resolve_relative(raw_src) else {
1465 #[cfg(feature = "tracing")]
1466 tracing::warn!("Not loading iframe: could not resolve url {raw_src}");
1467 return;
1468 };
1469 self.doc.start_iframe_load(target_id, url);
1470 }
1471
1472 fn load_custom_paint_src(&mut self, target_id: NodeId) {
1473 let node = &mut self.doc.nodes[target_id];
1474 if let Some(raw_src) = node.attr(local_name!("src")) {
1475 if let Ok(custom_paint_source_id) = raw_src.parse::<u64>() {
1476 self.recompute_is_animating = true;
1477 let canvas_data = SpecialElementData::Canvas(CanvasData {
1478 custom_paint_source_id,
1479 });
1480 node.element_data_mut().unwrap().special_data = canvas_data;
1481 }
1482 }
1483 }
1484
1485 fn process_button_input(&mut self, target_id: NodeId) {
1486 let node = &self.doc.nodes[target_id];
1487 let Some(data) = node.element_data() else {
1488 return;
1489 };
1490
1491 let tagname = data.name.local.as_ref();
1492 let type_attr = data.attr(local_name!("type"));
1493 let value = data.attr(local_name!("value"));
1494
1495 if let ("input", Some("button" | "submit" | "reset"), Some(value)) =
1498 (tagname, type_attr, value)
1499 {
1500 let value = value.to_string();
1501 let id = self.create_text_node(&value);
1502 self.append_children(target_id, &[id]);
1503 return;
1504 }
1505 #[cfg(feature = "file-input")]
1506 if let ("input", Some("file")) = (tagname, type_attr) {
1507 let button_id = self.create_element(
1508 qual_name!("button", html),
1509 vec![
1510 Attribute {
1511 name: qual_name!("type", html),
1512 value: "button".into(),
1513 },
1514 Attribute {
1515 name: qual_name!("tabindex", html),
1516 value: "-1".into(),
1517 },
1518 ],
1519 );
1520 let label_id = self.create_element(qual_name!("label", html), vec![]);
1521 let text_id = self.create_text_node("No File Selected");
1522 let button_text_id = self.create_text_node("Browse");
1523 self.append_children(target_id, &[button_id, label_id]);
1524 self.append_children(label_id, &[text_id]);
1525 self.append_children(button_id, &[button_text_id]);
1526 }
1527 }
1528}
1529
1530fn set_input_checked_state(element: &mut ElementData, value: String) {
1532 let Ok(checked) = value.parse() else {
1533 return;
1534 };
1535 match element.special_data {
1536 SpecialElementData::CheckboxInput(ref mut checked_mut) => *checked_mut = checked,
1537 SpecialElementData::None => element.attrs.push(Attribute {
1542 name: qual_name!("checked", html),
1543 value: checked.to_string().into(),
1544 }),
1545 _ => {}
1546 }
1547}
1548
1549pub struct ViewportMut<'doc> {
1552 doc: &'doc mut BaseDocument,
1553 initial_viewport: Viewport,
1554}
1555impl ViewportMut<'_> {
1556 pub fn new(doc: &mut BaseDocument) -> ViewportMut<'_> {
1557 let initial_viewport = doc.viewport.clone();
1558 ViewportMut {
1559 doc,
1560 initial_viewport,
1561 }
1562 }
1563}
1564impl Deref for ViewportMut<'_> {
1565 type Target = Viewport;
1566
1567 fn deref(&self) -> &Self::Target {
1568 &self.doc.viewport
1569 }
1570}
1571impl DerefMut for ViewportMut<'_> {
1572 fn deref_mut(&mut self) -> &mut Self::Target {
1573 &mut self.doc.viewport
1574 }
1575}
1576impl Drop for ViewportMut<'_> {
1577 fn drop(&mut self) {
1578 if self.doc.viewport == self.initial_viewport {
1579 return;
1580 }
1581
1582 self.doc.set_stylist_device(make_device(
1583 &self.doc.viewport,
1584 self.doc.media_type.clone(),
1585 self.doc.font_ctx.clone(),
1586 ));
1587 self.doc.scroll_viewport_by(0.0, 0.0); let scale_has_changed =
1590 self.doc.viewport().scale_f64() != self.initial_viewport.scale_f64();
1591 if scale_has_changed {
1592 self.doc.invalidate_inline_contexts();
1593 self.doc.shell_provider.request_redraw();
1594 }
1595 }
1596}
1597
1598#[cfg(test)]
1599mod test {
1600 use style::media_queries::MediaType;
1601 use style_dom::ElementState;
1602
1603 use std::sync::{
1604 Arc,
1605 atomic::{AtomicUsize, Ordering},
1606 };
1607
1608 use blitz_traits::shell::{ColorScheme, ShellProvider, Viewport};
1609
1610 use crate::{
1611 Attribute, BaseDocument, DocumentConfig, ElementData, NodeData, NodeId, qual_name,
1612 };
1613
1614 #[test]
1615 fn media_type_defaults_to_screen() {
1616 let mut document = BaseDocument::new(DocumentConfig::default());
1617 assert_eq!(*document.media_type(), MediaType::screen());
1618 assert_eq!(document.stylist_device().media_type(), MediaType::screen());
1619 }
1620
1621 #[test]
1622 fn media_type_honors_config() {
1623 let mut document = BaseDocument::new(DocumentConfig {
1624 media_type: Some(MediaType::print()),
1625 ..Default::default()
1626 });
1627 assert_eq!(*document.media_type(), MediaType::print());
1628 assert_eq!(document.stylist_device().media_type(), MediaType::print());
1629 }
1630
1631 #[test]
1632 fn set_media_type_updates_stylist_device() {
1633 let mut document = BaseDocument::new(DocumentConfig::default());
1634 assert_eq!(document.stylist_device().media_type(), MediaType::screen());
1635
1636 document.set_media_type(MediaType::print());
1637 assert_eq!(*document.media_type(), MediaType::print());
1638 assert_eq!(document.stylist_device().media_type(), MediaType::print());
1639 }
1640
1641 #[test]
1642 fn removing_a_node_forgets_it_as_focused_and_pressed() {
1643 let mut document = BaseDocument::new(DocumentConfig::default());
1649 let button = document.create_node(NodeData::Element(Box::new(ElementData::new(
1650 qual_name!("button"),
1651 Vec::new(),
1652 ))));
1653 let root = document.root_node().id;
1654
1655 let mut mutator = document.mutate();
1656 mutator.append_children(root, &[button]);
1657 drop(mutator);
1658
1659 document.set_focus_to(button);
1660 document.set_mousedown_node_id(Some(button));
1661 assert_eq!(document.get_focussed_node_id(), Some(button));
1662 assert_eq!(document.mousedown_node_id, Some(button));
1663
1664 let mut mutator = document.mutate();
1665 mutator.remove_node(button);
1666 drop(mutator);
1667
1668 assert_eq!(
1669 document.get_focussed_node_id(),
1670 None,
1671 "a removed node must not stay focused"
1672 );
1673 assert_eq!(
1674 document.mousedown_node_id, None,
1675 "a removed node must not stay the pressed node"
1676 );
1677 }
1678
1679 #[test]
1680 fn mutator_remove_disabled() {
1681 let mut document = BaseDocument::new(DocumentConfig::default());
1682 let id = document.create_node(NodeData::Element(Box::new(ElementData::new(
1683 qual_name!("button"),
1684 vec![Attribute {
1685 name: qual_name!("disabled"),
1686 value: "".into(),
1687 }],
1688 ))));
1689
1690 let node = document.get_node(id).unwrap();
1691 assert!(
1692 node.element_state().contains(ElementState::DISABLED),
1693 "form node is disabled"
1694 );
1695 assert!(
1696 !node.element_state().contains(ElementState::ENABLED),
1697 "form node is not enabled yet"
1698 );
1699
1700 let mut mutator = document.mutate();
1701 mutator.clear_attribute(id, qual_name!("disabled"));
1702 drop(mutator);
1703
1704 let node = document.get_node(id).unwrap();
1705 assert!(
1706 !node.element_state().contains(ElementState::DISABLED),
1707 "form node is no longer disabled"
1708 );
1709 assert!(
1710 node.element_state().contains(ElementState::ENABLED),
1711 "form node is enabled"
1712 );
1713 }
1714
1715 #[test]
1716 fn mutator_set_disabled() {
1717 let mut document = BaseDocument::new(DocumentConfig::default());
1718 let id = document.create_node(NodeData::Element(Box::new(ElementData::new(
1719 qual_name!("button"),
1720 vec![],
1721 ))));
1722
1723 let node = document.get_node(id).unwrap();
1724 assert!(
1725 !node.element_state().contains(ElementState::DISABLED),
1726 "form node is not disabled"
1727 );
1728 assert!(
1729 node.element_state().contains(ElementState::ENABLED),
1730 "form node is enabled"
1731 );
1732
1733 let mut mutator = document.mutate();
1734 mutator.set_attribute(id, qual_name!("disabled"), "");
1735 drop(mutator);
1736
1737 let node = document.get_node(id).unwrap();
1738
1739 assert!(
1740 node.element_state().contains(ElementState::DISABLED),
1741 "form node is disabled"
1742 );
1743 assert!(
1744 !node.element_state().contains(ElementState::ENABLED),
1745 "form node is no longer enabled enabled"
1746 );
1747 }
1748
1749 #[test]
1750 fn mutator_set_disabled_invalid_node() {
1751 let mut document = BaseDocument::new(DocumentConfig::default());
1752 let id = document.create_node(NodeData::Element(Box::new(ElementData::new(
1753 qual_name!("a"),
1754 vec![],
1755 ))));
1756
1757 let node = document.get_node(id).unwrap();
1758 assert!(
1759 !node.element_state().contains(ElementState::DISABLED),
1760 "form node is not disabled"
1761 );
1762 assert!(
1763 !node.element_state().contains(ElementState::ENABLED),
1764 "form node is enabled"
1765 );
1766
1767 let mut mutator = document.mutate();
1768 mutator.set_attribute(id, qual_name!("disabled"), "");
1769 drop(mutator);
1770
1771 let node = document.get_node(id).unwrap();
1772 assert!(
1773 !node.element_state().contains(ElementState::DISABLED),
1774 "form node is not disabled"
1775 );
1776 assert!(
1777 !node.element_state().contains(ElementState::ENABLED),
1778 "form node is enabled"
1779 );
1780 }
1781
1782 #[test]
1783 fn mutator_id_attribute_updates_id_map() {
1784 let mut document = BaseDocument::new(DocumentConfig::default());
1785 let root_id = document.root_node().id;
1786
1787 let node_id = {
1788 let mut mutator = document.mutate();
1789 let node_id = mutator.create_element(
1790 qual_name!("div"),
1791 vec![Attribute {
1792 name: qual_name!("id"),
1793 value: "old".into(),
1794 }],
1795 );
1796 mutator.append_children(root_id, &[node_id]);
1797 node_id
1798 };
1799 assert_eq!(document.get_element_by_id("old"), Some(node_id));
1800
1801 {
1802 let mut mutator = document.mutate();
1803 mutator.set_attribute(node_id, qual_name!("id"), "new");
1804 }
1805 assert_eq!(document.get_element_by_id("new"), Some(node_id));
1806 assert_eq!(document.get_element_by_id("old"), None);
1807
1808 {
1809 let mut mutator = document.mutate();
1810 mutator.clear_attribute(node_id, qual_name!("id"));
1811 }
1812 assert_eq!(document.get_element_by_id("new"), None);
1813 }
1814
1815 #[test]
1816 fn get_element_by_id_duplicate_ids_first_in_tree_order_wins() {
1817 let mut document = BaseDocument::new(DocumentConfig::default());
1818 let root_id = document.root_node().id;
1819
1820 let (first_id, second_id) = {
1821 let mut mutator = document.mutate();
1822 let first_id = mutator.create_element(qual_name!("div"), vec![]);
1823 let second_id = mutator.create_element(qual_name!("div"), vec![]);
1824 mutator.append_children(root_id, &[first_id, second_id]);
1825 mutator.set_attribute(second_id, qual_name!("id"), "dup");
1828 mutator.set_attribute(first_id, qual_name!("id"), "dup");
1829 (first_id, second_id)
1830 };
1831 assert_eq!(document.get_element_by_id("dup"), Some(first_id));
1832
1833 {
1834 let mut mutator = document.mutate();
1835 mutator.remove_node(first_id);
1836 }
1837 assert_eq!(document.get_element_by_id("dup"), Some(second_id));
1838 }
1839
1840 #[derive(Default)]
1841 struct RedrawShell {
1842 redraw_requests: AtomicUsize,
1843 }
1844
1845 impl ShellProvider for RedrawShell {
1846 fn request_redraw(&self) {
1847 self.redraw_requests.fetch_add(1, Ordering::Relaxed);
1848 }
1849 }
1850
1851 #[test]
1852 fn mutator_requests_redraw_only_after_mutation() {
1853 let shell = Arc::new(RedrawShell::default());
1854 let mut document = BaseDocument::new(DocumentConfig {
1855 shell_provider: Some(shell.clone()),
1856 ..Default::default()
1857 });
1858 let root_id = document.root_node().id;
1859
1860 {
1861 let mut mutator = document.mutate();
1862 let parent_id = mutator.create_element(qual_name!("div"), vec![]);
1863 let child_id = mutator.create_element(qual_name!("span"), vec![]);
1864 mutator.append_children(parent_id, &[child_id]);
1865 mutator.remove_and_drop_all_children(parent_id);
1866 mutator.set_attribute(parent_id, qual_name!("id"), "detached");
1867 }
1868 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 0);
1869
1870 {
1871 let mutator = document.mutate();
1872 assert_eq!(mutator.child_ids(root_id).len(), 0);
1873 }
1874
1875 {
1876 let mut mutator = document.mutate();
1877 let node_id = mutator.create_element(qual_name!("div"), vec![]);
1878 mutator.append_children(root_id, &[node_id]);
1879 mutator.set_attribute(node_id, qual_name!("id"), "in-document");
1880 }
1881 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 1);
1882
1883 {
1884 let mut mutator = document.mutate();
1885 let parent_id = mutator.create_element(qual_name!("div"), vec![]);
1886 let child_id = mutator.create_element(qual_name!("span"), vec![]);
1887 mutator.append_children(root_id, &[parent_id]);
1888 mutator.append_children(parent_id, &[child_id]);
1889 mutator.remove_and_drop_all_children(parent_id);
1890 }
1891 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 2);
1892
1893 {
1894 let mut mutator = document.mutate();
1895 let parent_id = mutator.create_element(qual_name!("div"), vec![]);
1896 let child_id = mutator.create_element(qual_name!("span"), vec![]);
1897 let detached_target_id = mutator.create_element(qual_name!("div"), vec![]);
1898 mutator.append_children(root_id, &[parent_id]);
1899 mutator.append_children(parent_id, &[child_id]);
1900 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 2);
1901 mutator.append_children(detached_target_id, &[child_id]);
1902 }
1903 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 3);
1904 }
1905
1906 #[test]
1907 fn moving_subtree_out_of_document_clears_in_document_flag() {
1908 let shell = Arc::new(RedrawShell::default());
1909 let mut document = BaseDocument::new(DocumentConfig {
1910 shell_provider: Some(shell.clone()),
1911 ..Default::default()
1912 });
1913 let root_id = document.root_node().id;
1914 let (child_id, grandchild_id, detached_parent_id) = {
1915 let mut mutator = document.mutate();
1916 let in_document_parent_id = mutator.create_element(qual_name!("div"), vec![]);
1917 let child_id = mutator.create_element(qual_name!("div"), vec![]);
1918 let grandchild_id = mutator.create_element(qual_name!("span"), vec![]);
1919 let detached_parent_id = mutator.create_element(qual_name!("section"), vec![]);
1920 mutator.append_children(root_id, &[in_document_parent_id]);
1921 mutator.append_children(in_document_parent_id, &[child_id]);
1922 mutator.append_children(child_id, &[grandchild_id]);
1923 (child_id, grandchild_id, detached_parent_id)
1924 };
1925 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 1);
1926 assert!(document.get_node(child_id).unwrap().flags.is_in_document());
1927 assert!(
1928 document
1929 .get_node(grandchild_id)
1930 .unwrap()
1931 .flags
1932 .is_in_document()
1933 );
1934
1935 {
1936 let mut mutator = document.mutate();
1937 mutator.append_children(detached_parent_id, &[child_id]);
1938 }
1939 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 2);
1940 assert!(!document.get_node(child_id).unwrap().flags.is_in_document());
1941 assert!(
1942 !document
1943 .get_node(grandchild_id)
1944 .unwrap()
1945 .flags
1946 .is_in_document()
1947 );
1948
1949 {
1950 let mut mutator = document.mutate();
1951 mutator.set_attribute(child_id, qual_name!("id"), "detached");
1952 }
1953 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 2);
1954
1955 {
1956 let mut mutator = document.mutate();
1957 mutator.append_children(root_id, &[child_id]);
1958 }
1959 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 3);
1960 assert!(document.get_node(child_id).unwrap().flags.is_in_document());
1961 assert!(
1962 document
1963 .get_node(grandchild_id)
1964 .unwrap()
1965 .flags
1966 .is_in_document()
1967 );
1968 }
1969
1970 #[test]
1982 fn a_paint_only_restyle_refreshes_the_calc_the_taffy_style_points_at() {
1983 use style::servo_arc::Arc as ServoArc;
1984
1985 let mut document = BaseDocument::new(DocumentConfig {
1986 viewport: Some(Viewport::new(800, 600, 1.0, ColorScheme::Light)),
1987 ..Default::default()
1988 });
1989 let root_id = document.root_node().id;
1990
1991 let (header_id, chip_id) = {
1992 let mut mutator = document.mutate();
1993 let header_id = mutator.create_element(qual_name!("div"), vec![]);
1994 let chip_id = mutator.create_element(qual_name!("span"), vec![]);
1995 mutator.set_style_property(header_id, "position", "relative");
1996 mutator.set_style_property(header_id, "width", "800px");
1997 mutator.set_style_property(header_id, "height", "60px");
1998 mutator.set_style_property(chip_id, "position", "absolute");
1999 mutator.set_style_property(chip_id, "max-width", "calc(100% - 24px)");
2000 mutator.set_style_property(chip_id, "color", "rgb(1, 2, 3)");
2001 mutator.append_children(header_id, &[chip_id]);
2002 mutator.append_children(root_id, &[header_id]);
2003 (header_id, chip_id)
2004 };
2005
2006 document.resolve(0.0);
2007
2008 {
2014 let mut mutator = document.mutate();
2015 mutator.set_style_property(header_id, "color", "rgb(4, 5, 6)");
2016 }
2017 document.resolve(0.0);
2018
2019 let node = document.get_node(chip_id).unwrap();
2020 let stylo_data = node.stylo_element_data_opt().and_then(|data| data.get());
2021 let primary = stylo_data
2022 .as_ref()
2023 .and_then(|data| data.styles.get_primary())
2024 .expect("the chip is styled");
2025 let source = node
2026 .style_source_opt()
2027 .expect("a styled node records the computed values its taffy style was built from");
2028
2029 assert!(
2030 ServoArc::ptr_eq(primary, source),
2031 "the cached taffy style still points into computed values that a restyle replaced, \
2032 so every calc() in it is a dangling pointer",
2033 );
2034 }
2035
2036 #[test]
2037 fn style_property_updates_nested_layout() {
2038 let mut document = BaseDocument::new(DocumentConfig {
2039 viewport: Some(Viewport::new(800, 600, 1.0, ColorScheme::Light)),
2040 ..Default::default()
2041 });
2042 let root_id = document.root_node().id;
2043
2044 let mover_id = {
2045 let mut mutator = document.mutate();
2046 let parent_id = mutator.create_element(qual_name!("div"), vec![]);
2047 let mover_id = mutator.create_element(qual_name!("div"), vec![]);
2048 mutator.set_style_property(parent_id, "position", "relative");
2049 mutator.set_style_property(parent_id, "width", "800px");
2050 mutator.set_style_property(parent_id, "height", "600px");
2051 mutator.set_style_property(mover_id, "position", "absolute");
2052 mutator.set_style_property(mover_id, "left", "0px");
2053 mutator.set_style_property(mover_id, "top", "0px");
2054 mutator.append_children(parent_id, &[mover_id]);
2055 mutator.append_children(root_id, &[parent_id]);
2056 mover_id
2057 };
2058
2059 document.resolve(0.0);
2060 assert_eq!(
2061 document
2062 .get_node(mover_id)
2063 .unwrap()
2064 .final_layout()
2065 .location
2066 .x,
2067 0.0
2068 );
2069
2070 {
2071 let mut mutator = document.mutate();
2072 mutator.set_style_property(mover_id, "left", "120px");
2073 }
2074
2075 document.resolve(0.0);
2076 assert_eq!(
2077 document
2078 .get_node(mover_id)
2079 .unwrap()
2080 .final_layout()
2081 .location
2082 .x,
2083 120.0
2084 );
2085 }
2086
2087 fn doc_with_a_comment() -> (BaseDocument, NodeId, NodeId, NodeId) {
2090 let mut doc = BaseDocument::new(DocumentConfig {
2091 viewport: Some(Viewport::new(400, 300, 1.0, ColorScheme::Light)),
2092 ..Default::default()
2093 });
2094 let root_id = doc.root_node().id;
2095
2096 let mut mutr = doc.mutate();
2097 let html = mutr.create_element(qual_name!("html"), vec![]);
2098 let body = mutr.create_element(qual_name!("body"), vec![]);
2099 let container = mutr.create_element(qual_name!("div"), vec![]);
2100 let text = mutr.create_text_node("text");
2101 let comment = mutr.create_comment_node("comment");
2102 mutr.append_children(container, &[text, comment]);
2103 mutr.append_children(body, &[container]);
2104 mutr.append_children(html, &[body]);
2105 mutr.append_children(root_id, &[html]);
2106 drop(mutr);
2107
2108 doc.resolve(0.0);
2109 (doc, container, text, comment)
2110 }
2111
2112 #[test]
2116 fn setting_a_comments_data_writes_the_contents() {
2117 let (mut doc, _container, _text, comment) = doc_with_a_comment();
2118
2119 doc.mutate().set_node_text(comment, "rewritten");
2120
2121 let NodeData::Comment { contents } = &doc.get_node(comment).unwrap().data else {
2122 panic!("expected a comment node");
2123 };
2124 assert_eq!(contents, "rewritten");
2125 }
2126
2127 #[test]
2134 fn setting_a_comments_data_does_not_dirty_layout() {
2135 let (mut doc, container, text, comment) = doc_with_a_comment();
2136
2137 let container_damage_before = doc.get_node(container).unwrap().damage();
2138 let comment_damage_before = doc.get_node(comment).unwrap().damage();
2139
2140 doc.mutate().set_node_text(comment, "rewritten");
2141
2142 assert_eq!(
2143 doc.get_node(comment).unwrap().damage(),
2144 comment_damage_before,
2145 "writing a comment's data damaged the comment"
2146 );
2147 assert_eq!(
2148 doc.get_node(container).unwrap().damage(),
2149 container_damage_before,
2150 "writing a comment's data damaged its parent, scheduling a relayout \
2151 for a change that cannot affect a pixel"
2152 );
2153
2154 doc.mutate().set_node_text(text, "rewritten");
2155 assert_ne!(
2156 doc.get_node(container).unwrap().damage(),
2157 container_damage_before,
2158 "a text write should damage the parent, so the assertions above can fail"
2159 );
2160 }
2161
2162 #[test]
2165 fn rewriting_a_comment_with_its_own_contents_is_inert() {
2166 let (mut doc, container, _text, comment) = doc_with_a_comment();
2167 let container_damage_before = doc.get_node(container).unwrap().damage();
2168
2169 doc.mutate().set_node_text(comment, "comment");
2170
2171 let NodeData::Comment { contents } = &doc.get_node(comment).unwrap().data else {
2172 panic!("expected a comment node");
2173 };
2174 assert_eq!(contents, "comment");
2175 assert_eq!(
2176 doc.get_node(container).unwrap().damage(),
2177 container_damage_before
2178 );
2179 }
2180}