1use apace_traits::node_id::NodeId;
2use std::collections::HashSet;
3use std::mem;
4use std::ops::{Deref, DerefMut};
5
6use crate::layout::damage::ALL_DAMAGE;
7use crate::net::{ImageHandler, ResourceHandler, StylesheetHandler};
8use crate::node::{CanvasData, NodeFlags, SpecialElementData};
9use crate::stylo_device::DeviceChanges;
10use crate::util::ImageType;
11use crate::{
12 Attribute, BaseDocument, Document, ElementData, Node, NodeData, QualName, local_name, qual_name,
13};
14use apace_traits::shell::Viewport;
15use style::Atom;
16use style::invalidation::element::restyle_hints::RestyleHint;
17use style::stylesheets::OriginSet;
18use thin_vec::ThinVec;
19
20macro_rules! tag_and_attr {
21 ($tag:tt, $attr:tt) => {
22 (&local_name!($tag), &local_name!($attr))
23 };
24}
25
26#[derive(Debug, Clone)]
27pub enum AppendTextErr {
28 NotTextNode,
30}
31
32enum SpecialOp {
35 LoadImage(NodeId),
36 LoadIframe(NodeId),
37 LoadStylesheet(NodeId),
38 UnloadStylesheet(NodeId),
39 LoadCustomPaintSource(NodeId),
40 ProcessButtonInput(NodeId),
41 UnloadSubDocument(NodeId),
42 #[cfg(feature = "custom-widget")]
43 UnloadCustomWidget(NodeId),
44}
45
46pub struct DocumentMutator<'doc> {
47 pub doc: &'doc mut BaseDocument,
50
51 eager_op_queue: Vec<SpecialOp>,
52
53 title_node: Option<NodeId>,
55 style_nodes: HashSet<NodeId>,
56 form_nodes: HashSet<NodeId>,
57
58 recompute_is_animating: bool,
60
61 mutations_occurred: bool,
63 previous_focus_path: Option<Vec<NodeId>>,
65
66 #[cfg(feature = "autofocus")]
68 node_to_autofocus: Option<NodeId>,
69}
70
71impl Drop for DocumentMutator<'_> {
72 fn drop(&mut self) {
73 self.flush(); if self.mutations_occurred {
75 self.doc.shell_provider.request_redraw();
76 }
77 }
78}
79
80impl DocumentMutator<'_> {
81 pub fn new<'doc>(doc: &'doc mut BaseDocument) -> DocumentMutator<'doc> {
82 DocumentMutator {
83 doc,
84 eager_op_queue: Vec::new(),
85 title_node: None,
86 style_nodes: HashSet::new(),
87 form_nodes: HashSet::new(),
88 recompute_is_animating: false,
89 mutations_occurred: false,
90 previous_focus_path: None,
91 #[cfg(feature = "autofocus")]
92 node_to_autofocus: None,
93 }
94 }
95
96 pub fn set_input_checked(&mut self, node_id: NodeId, value: bool) -> bool {
99 self.doc.set_input_checked(node_id, value)
100 }
101
102 pub fn set_input_indeterminate(&mut self, node_id: NodeId, value: bool) -> bool {
105 let Some(previous) = self
106 .doc
107 .nodes
108 .get(node_id)
109 .and_then(|node| node.element_data())
110 .and_then(ElementData::checkbox_input_indeterminate)
111 else {
112 return false;
113 };
114 if previous == value {
115 return true;
116 }
117 self.mutations_occurred = true;
118 self.doc
119 .snapshot_node_and(node_id, style_dom::ElementState::INDETERMINATE, |node| {
120 node.element_data_mut()
121 .unwrap()
122 .set_checkbox_input_indeterminate(value);
123 node.mark_ancestors_dirty();
124 node.insert_damage(ALL_DAMAGE);
125 });
126 true
127 }
128
129 pub fn node_has_parent(&self, node_id: NodeId) -> bool {
130 self.doc.nodes[node_id].parent.is_some()
131 }
132
133 pub fn previous_sibling_id(&self, node_id: NodeId) -> Option<NodeId> {
134 self.doc.nodes[node_id].backward(1).map(|node| node.id)
135 }
136
137 pub fn next_sibling_id(&self, node_id: NodeId) -> Option<NodeId> {
138 self.doc.nodes[node_id].forward(1).map(|node| node.id)
139 }
140
141 pub fn parent_id(&self, node_id: NodeId) -> Option<NodeId> {
142 self.doc.nodes[node_id].parent
143 }
144
145 pub fn last_child_id(&self, node_id: NodeId) -> Option<NodeId> {
146 self.doc.nodes[node_id].children.last().copied()
147 }
148
149 pub fn child_ids(&self, node_id: NodeId) -> ThinVec<NodeId> {
150 self.doc.nodes[node_id].children.clone()
151 }
152
153 pub fn element_name(&self, node_id: NodeId) -> Option<&QualName> {
154 self.doc.nodes[node_id].element_data().map(|el| &el.name)
155 }
156
157 pub fn node_at_path(&self, start_node_id: NodeId, path: &[u8]) -> NodeId {
158 let mut current = &self.doc.nodes[start_node_id];
159 for i in path {
160 let new_id = current.children[*i as usize];
161 current = &self.doc.nodes[new_id];
162 }
163 current.id
164 }
165
166 pub fn create_comment_node(&mut self, contents: &str) -> NodeId {
169 self.doc.create_node(NodeData::Comment {
170 contents: contents.to_string(),
171 })
172 }
173
174 pub fn create_text_node(&mut self, text: &str) -> NodeId {
175 self.doc.create_text_node(text)
176 }
177
178 pub fn create_element(&mut self, name: QualName, attrs: Vec<Attribute>) -> NodeId {
179 let mut data = ElementData::new(name, attrs);
180 data.flush_style_attribute(self.doc.guard(), &self.doc.url.url_extra_data());
181
182 let id = self.doc.create_node(NodeData::Element(Box::new(data)));
183 let node = self.doc.get_node_mut(id).unwrap();
184
185 *node.stylo_element_data_mut().ensure_init_mut() = style::data::ElementData {
187 damage: ALL_DAMAGE,
188 ..Default::default()
189 };
190
191 id
192 }
193
194 pub fn deep_clone_node(&mut self, node_id: NodeId) -> NodeId {
195 self.doc.deep_clone_node(node_id)
196 }
197
198 pub fn template_contents(&mut self, template_id: NodeId) -> NodeId {
203 if let Some(contents_id) = self.try_template_contents(template_id) {
204 return contents_id;
205 }
206 let name = QualName::new(
207 None,
208 markup5ever::ns!(html),
209 markup5ever::LocalName::from("#document-fragment"),
210 );
211 let contents_id = self.create_element(name, Vec::new());
212 if let Some(element) = self
213 .doc
214 .get_node_mut(template_id)
215 .and_then(|node| node.element_data_mut())
216 {
217 element.template_contents = Some(contents_id);
218 }
219 contents_id
220 }
221
222 pub fn try_template_contents(&self, template_id: NodeId) -> Option<NodeId> {
224 self.doc
225 .get_node(template_id)
226 .and_then(|node| node.element_data())
227 .and_then(|element| element.template_contents)
228 }
229
230 pub fn set_node_text(&mut self, node_id: NodeId, value: &str) {
233 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
234 let node = &mut self.doc.nodes[node_id];
235
236 let text = match node.data {
237 NodeData::Text(ref mut text) => text,
238 _ => return,
240 };
241
242 let changed = text.content != value;
243 if changed {
244 self.mutations_occurred |= node_is_in_document;
245 text.content.clear();
246 text.content.push_str(value);
247 node.insert_damage(ALL_DAMAGE);
248 node.mark_ancestors_dirty();
251 let parent_id = node.parent;
252
253 if let Some(parent_id) = parent_id {
256 let parent = &mut self.doc.nodes[parent_id];
257 parent.insert_damage(ALL_DAMAGE);
258 }
259
260 self.maybe_record_node(parent_id);
261 if let Some(parent_id) = parent_id {
262 self.doc.sync_placeholder_state(parent_id);
263 }
264 }
265 }
266
267 pub fn append_text_to_node(
268 &mut self,
269 node_id: NodeId,
270 text: &str,
271 ) -> Result<(), AppendTextErr> {
272 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
273 let node = &mut self.doc.nodes[node_id];
274 node.insert_damage(ALL_DAMAGE);
275 node.mark_ancestors_dirty();
276 match node.text_data_mut() {
277 Some(data) => {
278 data.content += text;
279 self.mutations_occurred |= node_is_in_document;
280 Ok(())
281 }
282 None => Err(AppendTextErr::NotTextNode),
283 }
284 }
285
286 pub fn add_attrs_if_missing(&mut self, node_id: NodeId, attrs: Vec<Attribute>) {
287 let node = &mut self.doc.nodes[node_id];
288 node.insert_damage(ALL_DAMAGE);
289 let element_data = node.element_data_mut().expect("Not an element");
290
291 let existing_names = element_data
292 .attrs
293 .iter()
294 .map(|e| e.name.clone())
295 .collect::<HashSet<_>>();
296
297 for attr in attrs
298 .into_iter()
299 .filter(|attr| !existing_names.contains(&attr.name))
300 {
301 self.set_attribute(node_id, attr.name, &attr.value);
302 }
303 }
304
305 pub fn set_attribute(&mut self, node_id: NodeId, name: QualName, value: &str) {
306 let computed_damage_only_attribute = name.local == local_name!("style")
312 || name.local == local_name!("placeholder")
313 && self.doc.nodes[node_id]
314 .element_data()
315 .is_some_and(|e| e.text_input_data().is_some());
316 #[cfg(feature = "custom-widget")]
317 let computed_damage_only_attribute = computed_damage_only_attribute
318 && !self.doc.nodes[node_id]
319 .element_data()
320 .is_some_and(|element| {
321 matches!(element.special_data, SpecialElementData::CustomWidget(_))
322 });
323 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
324 if node_is_in_document {
325 self.doc.snapshot_node(node_id);
326
327 let node = &mut self.doc.nodes[node_id];
328 if let Some(mut data) = node.try_stylo_element_data_mut().and_then(|s| s.get_mut()) {
329 data.hint |= RestyleHint::restyle_subtree();
330 if !computed_damage_only_attribute {
331 data.damage.insert(ALL_DAMAGE);
332 }
333 }
334 node.mark_damaged();
335
336 let parent = node.parent;
338 if let Some(parent_id) = parent {
339 let parent = &mut self.doc.nodes[parent_id];
340 if let Some(mut data) = parent
341 .try_stylo_element_data_mut()
342 .and_then(|s| s.get_mut())
343 {
344 data.hint |= RestyleHint::restyle_subtree();
345 }
346 }
347
348 self.doc.nodes[node_id].mark_ancestors_dirty();
352 }
353
354 if name.local == local_name!("id") && node_is_in_document {
355 if let Some(old_id) = self.doc.nodes[node_id]
356 .element_data()
357 .map(|element| element.id.clone())
358 {
359 if let Some(old_id) = old_id {
360 self.doc.remove_from_id_map(&old_id, node_id);
361 }
362 self.doc.add_to_id_map(value, node_id);
363 }
364 }
365
366 let node = &mut self.doc.nodes[node_id];
367
368 let NodeData::Element(ref mut element) = node.data else {
369 return;
370 };
371
372 self.mutations_occurred |= node_is_in_document;
373 #[cfg(feature = "custom-widget")]
375 if let SpecialElementData::CustomWidget(widget_data) = &mut element.special_data {
376 let old_value = element.attrs.get(&name).as_ref().map(|attr| &*attr.value);
377 widget_data
378 .widget
379 .attribute_changed(&name.local, old_value, Some(value));
380 }
381
382 element.attrs.set(name.clone(), value);
383 if name.local == local_name!("type") && element.name.local == local_name!("input") {
384 if value != "checkbox" {
385 element
386 .element_state
387 .remove(style_dom::ElementState::INDETERMINATE);
388 }
389 if let Some(input) = element.text_input_data_mut() {
390 input.set_password(value == "password");
391 input.refresh_display_config(
392 &mut self.doc.font_ctx.lock().unwrap(),
393 &mut self.doc.layout_ctx,
394 );
395 }
396 }
397
398 if name.local == local_name!("tabindex")
403 || name.local == local_name!("href")
404 || name.local == local_name!("disabled")
405 {
406 element.flush_is_focussable();
407 }
408
409 if name.local == local_name!("href") {
410 element.flush_link_state();
411 }
412
413 let tag = &element.name.local;
414 let attr = &name.local;
415
416 if *attr == local_name!("id") {
417 element.id = Some(Atom::from(value))
418 }
419
420 if *attr == local_name!("value") {
421 if let Some(input_data) = element.text_input_data_mut() {
422 input_data.set_text(
424 &mut self.doc.font_ctx.lock().unwrap(),
425 &mut self.doc.layout_ctx,
426 value,
427 );
428 }
429 self.doc.sync_placeholder_state(node_id);
430 return;
431 }
432
433 if *attr == local_name!("placeholder") {
434 if let Some(input) = element.text_input_data_mut() {
435 input.set_placeholder(Some(value));
436 input.refresh_display(
437 &mut self.doc.font_ctx.lock().unwrap(),
438 &mut self.doc.layout_ctx,
439 );
440 }
441 self.doc.sync_placeholder_state(node_id);
442 return;
443 }
444
445 if *attr == local_name!("style") {
446 element.flush_style_attribute(&self.doc.guard, &self.doc.url.url_extra_data());
447 node.set_restyle_hint(RestyleHint::RESTYLE_STYLE_ATTRIBUTE);
448 return;
449 }
450
451 if *attr == local_name!("disabled") && element.can_be_disabled() {
452 node.disable();
453 return;
454 }
455
456 if !node.flags.is_in_document() {
459 self.doc.sync_placeholder_state(node_id);
460 if matches!(name.local.as_ref(), "name" | "form" | "type") {
461 self.doc.reconcile_radio(node_id);
462 }
463 return;
464 }
465
466 #[cfg(feature = "custom-widget")]
468 if element.custom_widget_data().is_some() {
469 return;
470 }
471
472 if (tag, attr) == tag_and_attr!("input", "checked") {
473 set_input_checked_state(element, value.to_string());
474 } else if (tag, attr) == tag_and_attr!("img", "src") {
475 node.element_data_mut().unwrap().special_data =
477 SpecialElementData::Image(Box::new(crate::node::ImageData::None));
478 self.load_image(node_id);
479 } else if (tag, attr) == tag_and_attr!("canvas", "src") {
480 self.load_custom_paint_src(node_id);
481 } else if (tag, attr) == tag_and_attr!("link", "href")
482 || (tag, attr) == tag_and_attr!("link", "rel")
483 {
484 self.load_linked_stylesheet(node_id);
485 } else if (tag, attr) == tag_and_attr!("iframe", "src")
486 || (tag, attr) == tag_and_attr!("iframe", "srcdoc")
487 {
488 self.load_iframe(node_id);
489 }
490 if matches!(name.local.as_ref(), "name" | "form" | "type" | "checked") {
491 self.doc.reconcile_radio(node_id);
492 }
493 self.doc.sync_placeholder_state(node_id);
494 }
495
496 pub fn clear_attribute(&mut self, node_id: NodeId, name: QualName) {
497 let computed_damage_only_attribute = name.local == local_name!("style")
498 || name.local == local_name!("placeholder")
499 && self.doc.nodes[node_id]
500 .element_data()
501 .is_some_and(|e| e.text_input_data().is_some());
502 #[cfg(feature = "custom-widget")]
503 let computed_damage_only_attribute = computed_damage_only_attribute
504 && !self.doc.nodes[node_id]
505 .element_data()
506 .is_some_and(|element| {
507 matches!(element.special_data, SpecialElementData::CustomWidget(_))
508 });
509 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
510 if node_is_in_document {
511 self.doc.snapshot_node(node_id);
512
513 let node = &mut self.doc.nodes[node_id];
514
515 if let Some(mut data) = node.try_stylo_element_data_mut().and_then(|s| s.get_mut()) {
516 data.hint |= RestyleHint::restyle_subtree();
517 if !computed_damage_only_attribute {
518 data.damage.insert(ALL_DAMAGE);
519 }
520 }
521 node.mark_damaged();
522
523 node.mark_ancestors_dirty();
526 }
527
528 if name.local == local_name!("id") && node_is_in_document {
529 if let Some(old_id) = self.doc.nodes[node_id]
530 .element_data()
531 .and_then(|element| element.id.clone())
532 {
533 self.doc.remove_from_id_map(&old_id, node_id);
534 }
535 }
536
537 let node = &mut self.doc.nodes[node_id];
538
539 let Some(element) = node.element_data_mut() else {
540 return;
541 };
542
543 let removed_attr = element.attrs.remove(&name);
544 let had_attr = removed_attr.is_some();
545 if !had_attr {
546 return;
547 }
548 self.mutations_occurred |= node_is_in_document;
549
550 #[cfg(feature = "custom-widget")]
552 if let SpecialElementData::CustomWidget(widget_data) = &mut element.special_data {
553 let old_value = removed_attr.as_ref().map(|attr| &*attr.value);
554 widget_data
555 .widget
556 .attribute_changed(&name.local, old_value, None);
557 }
558
559 if name.local == local_name!("id") {
560 element.id = None;
561 }
562
563 if name.local == local_name!("tabindex")
566 || name.local == local_name!("href")
567 || name.local == local_name!("disabled")
568 {
569 element.flush_is_focussable();
570 }
571
572 if name.local == local_name!("href") {
573 element.flush_link_state();
574 }
575
576 if name.local == local_name!("type") && element.name.local == local_name!("input") {
578 element
579 .element_state
580 .remove(style_dom::ElementState::INDETERMINATE);
581 if let Some(input) = element.text_input_data_mut() {
582 input.set_password(false);
583 }
584 }
585 if name.local == local_name!("value") {
586 if let Some(input_data) = element.text_input_data_mut() {
587 input_data.set_text(
588 &mut self.doc.font_ctx.lock().unwrap(),
589 &mut self.doc.layout_ctx,
590 "",
591 );
592 }
593 }
594
595 if name.local == local_name!("placeholder") {
596 if let Some(input) = element.text_input_data_mut() {
597 input.set_placeholder(None);
598 input.refresh_display(
599 &mut self.doc.font_ctx.lock().unwrap(),
600 &mut self.doc.layout_ctx,
601 );
602 }
603 self.doc.sync_placeholder_state(node_id);
604 return;
605 }
606
607 let tag = &element.name.local;
608 let attr = &name.local;
609
610 if *attr == local_name!("disabled") && element.can_be_disabled() {
611 node.enable();
612 return;
613 }
614
615 if *attr == local_name!("style") {
616 element.flush_style_attribute(&self.doc.guard, &self.doc.url.url_extra_data());
617 node.set_restyle_hint(RestyleHint::RESTYLE_STYLE_ATTRIBUTE);
618 } else if (tag, attr) == tag_and_attr!("input", "checked") {
619 element.set_checkbox_input_checked(false);
620 } else if (tag, attr) == tag_and_attr!("img", "src") {
621 element.special_data =
622 SpecialElementData::Image(Box::new(crate::node::ImageData::None));
623 node.clear_layout_cache();
624 node.insert_damage(ALL_DAMAGE);
625 self.load_image(node_id);
626 } else if (tag, attr) == tag_and_attr!("canvas", "src") {
627 self.recompute_is_animating = true;
628 } else if (tag, attr) == tag_and_attr!("link", "href") {
629 self.unload_stylesheet(node_id);
630 } else if (tag, attr) == tag_and_attr!("iframe", "srcdoc") && node_is_in_document {
631 self.load_iframe(node_id);
633 }
634 if matches!(name.local.as_ref(), "name" | "form" | "type") {
635 self.doc.reconcile_radio(node_id);
636 }
637 self.doc.sync_placeholder_state(node_id);
638 }
639
640 pub fn set_style_property(&mut self, node_id: NodeId, name: &str, value: &str) {
641 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
642 self.doc.set_style_property(node_id, name, value);
643 self.mutations_occurred |= node_is_in_document;
644 }
645
646 pub fn remove_style_property(&mut self, node_id: NodeId, name: &str) {
647 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
648 self.doc.remove_style_property(node_id, name);
649 self.mutations_occurred |= node_is_in_document;
650 }
651
652 pub fn set_sub_document(&mut self, node_id: NodeId, sub_document: Box<dyn Document>) {
653 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
654 self.doc.set_sub_document(node_id, sub_document);
655 self.mutations_occurred |= node_is_in_document;
656 }
657
658 pub fn remove_sub_document(&mut self, node_id: NodeId) {
659 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
660 self.doc.remove_sub_document(node_id);
661 self.mutations_occurred |= node_is_in_document;
662 }
663
664 #[cfg(feature = "custom-widget")]
665 pub fn set_custom_widget(&mut self, node_id: NodeId, widget: Box<dyn crate::Widget>) {
666 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
667 self.doc.set_custom_widget(node_id, widget);
668 self.mutations_occurred |= node_is_in_document;
669 }
670
671 #[cfg(feature = "custom-widget")]
672 pub fn remove_custom_widget(&mut self, node_id: NodeId) {
673 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
674 self.doc.remove_custom_widget(node_id);
675 self.mutations_occurred |= node_is_in_document;
676 }
677
678 pub fn remove_node(&mut self, node_id: NodeId) {
680 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
681 self.process_removed_subtree(node_id);
685
686 let node = &mut self.doc.nodes[node_id];
687
688 if let Some(parent_id) = node.parent.take() {
690 self.mutations_occurred |= node_is_in_document;
691 let parent = &mut self.doc.nodes[parent_id];
692 parent.insert_damage(ALL_DAMAGE);
693 parent.mark_ancestors_dirty();
695 parent.children.retain(|id| *id != node_id);
696 self.maybe_record_node(parent_id);
697 }
698 }
699
700 pub fn remove_and_drop_node(&mut self, node_id: NodeId) -> Option<Node> {
701 self.remove_and_drop_node_with(node_id, &mut |_| {})
702 }
703
704 pub fn remove_and_drop_node_with(
707 &mut self,
708 node_id: NodeId,
709 on_drop: &mut dyn FnMut(NodeId),
710 ) -> Option<Node> {
711 let node_is_in_document = self.doc.nodes[node_id].flags.is_in_document();
712 self.process_removed_subtree(node_id);
713
714 let node = self.doc.drop_node_ignoring_parent_with(node_id, on_drop);
715 self.mutations_occurred |= node_is_in_document;
716
717 if let Some(parent_id) = node.as_ref().and_then(|node| node.parent) {
719 let parent = &mut self.doc.nodes[parent_id];
720 parent.insert_damage(ALL_DAMAGE);
721 let parent_is_in_doc = parent.flags.is_in_document();
722
723 if parent_is_in_doc {
725 if let Some(mut data) = parent
726 .try_stylo_element_data_mut()
727 .and_then(|s| s.get_mut())
728 {
729 data.hint |= RestyleHint::restyle_subtree();
730 }
731 parent.mark_ancestors_dirty();
733 }
734
735 parent.children.retain(|id| *id != node_id);
736 self.maybe_record_node(parent_id);
737 }
738
739 node
740 }
741
742 pub fn remove_and_drop_all_children(&mut self, node_id: NodeId) {
743 let parent = &mut self.doc.nodes[node_id];
744 let parent_is_in_doc = parent.flags.is_in_document();
745 if !parent.children.is_empty() {
746 parent.insert_damage(ALL_DAMAGE);
750 }
751
752 if parent_is_in_doc {
754 if let Some(mut data) = parent
755 .try_stylo_element_data_mut()
756 .and_then(|s| s.get_mut())
757 {
758 data.hint |= RestyleHint::restyle_subtree();
759 }
760 parent.mark_ancestors_dirty();
762 }
763
764 let children = mem::take(&mut parent.children);
765 self.mutations_occurred |= parent_is_in_doc && !children.is_empty();
766 for child_id in children {
767 self.process_removed_subtree(child_id);
768 let _ = self.doc.drop_node_ignoring_parent(child_id);
769 }
770 self.maybe_record_node(node_id);
771 }
772
773 pub fn remove_node_if_unparented(&mut self, node_id: NodeId) {
775 self.remove_node_if_unparented_with(node_id, &mut |_| {});
776 }
777
778 pub fn remove_node_if_unparented_with(
781 &mut self,
782 node_id: NodeId,
783 on_drop: &mut dyn FnMut(NodeId),
784 ) {
785 if let Some(node) = self.doc.get_node(node_id) {
786 if node.parent.is_none() {
787 self.remove_and_drop_node_with(node_id, on_drop);
788 }
789 }
790 }
791
792 pub fn append_children(&mut self, parent_id: NodeId, child_ids: &[NodeId]) {
794 self.add_children_to_parent(parent_id, child_ids, &|parent, child_ids| {
795 parent.children.extend_from_slice(child_ids);
796 });
797 }
798
799 pub fn insert_nodes_before(&mut self, anchor_node_id: NodeId, new_node_ids: &[NodeId]) {
800 let parent_id = self.doc.nodes[anchor_node_id].parent.unwrap();
801 self.add_children_to_parent(parent_id, new_node_ids, &|parent, child_ids| {
802 let node_child_idx = parent.index_of_child(anchor_node_id).unwrap();
803 parent
804 .children
805 .splice(node_child_idx..node_child_idx, child_ids.iter().copied());
806 });
807 }
808
809 fn add_children_to_parent(
810 &mut self,
811 parent_id: NodeId,
812 child_ids: &[NodeId],
813 insert_children_fn: &dyn Fn(&mut Node, &[NodeId]),
814 ) {
815 if self.previous_focus_path.is_none()
816 && self.doc.focus_node_id.is_some()
817 && child_ids.iter().any(|id| {
818 self.doc.nodes[*id]
819 .parent
820 .is_some_and(|old| old != parent_id)
821 })
822 {
823 let path = self
824 .doc
825 .maybe_node_element_ancestors(self.doc.focus_node_id);
826 if path.iter().any(|id| child_ids.contains(id)) {
827 self.previous_focus_path = Some(path);
828 }
829 }
830 let new_parent_is_in_document = self.doc.nodes[parent_id].flags.is_in_document();
831 self.mutations_occurred |= new_parent_is_in_document && !child_ids.is_empty();
832 for child_id in child_ids.iter().copied() {
839 let child = &self.doc.nodes[child_id];
840 if child.parent.is_some()
841 || child.layout_parent.get().is_some()
842 || child.oof_containing_block.get().is_some()
843 {
844 self.doc.retire_moved_layout_references(child_id);
845 }
846 let child = &mut self.doc.nodes[child_id];
847 let child_was_in_doc = child.flags.is_in_document();
848 self.mutations_occurred |= child_was_in_doc;
849 let Some(old_parent_id) = child.parent.take() else {
850 continue;
851 };
852
853 let old_parent = &mut self.doc.nodes[old_parent_id];
854 old_parent.insert_damage(ALL_DAMAGE);
855
856 if child_was_in_doc {
858 if let Some(mut data) = old_parent
859 .try_stylo_element_data_mut()
860 .and_then(|s| s.get_mut())
861 {
862 data.hint |= RestyleHint::restyle_subtree();
863 }
864 old_parent.mark_ancestors_dirty();
866 }
867
868 old_parent.children.retain(|id| *id != child_id);
869 self.maybe_record_node(old_parent_id);
870 }
871
872 let new_parent = &mut self.doc.nodes[parent_id];
873 new_parent.insert_damage(ALL_DAMAGE);
874
875 if new_parent_is_in_document {
877 if let Some(mut data) = new_parent
878 .try_stylo_element_data_mut()
879 .and_then(|s| s.get_mut())
880 {
881 data.hint |= RestyleHint::restyle_subtree();
882 }
883 new_parent.mark_ancestors_dirty();
885 }
886
887 insert_children_fn(new_parent, child_ids);
888
889 for child_id in child_ids.iter().copied() {
890 let child = &mut self.doc.nodes[child_id];
891 let child_was_in_doc = child.flags.is_in_document();
892 child.parent = Some(parent_id);
893
894 if new_parent_is_in_document && !child_was_in_doc {
895 self.process_added_subtree(child_id);
896 } else if !new_parent_is_in_document && child_was_in_doc {
897 self.process_removed_subtree(child_id);
898 }
899 }
900
901 self.doc.reconcile_radio_subtrees(child_ids);
902
903 self.maybe_record_node(parent_id);
904 }
905
906 pub fn insert_nodes_after(&mut self, anchor_node_id: NodeId, new_node_ids: &[NodeId]) {
908 match self.next_sibling_id(anchor_node_id) {
909 Some(id) => self.insert_nodes_before(id, new_node_ids),
910 None => {
911 let parent_id = self.parent_id(anchor_node_id).unwrap();
912 self.append_children(parent_id, new_node_ids)
913 }
914 }
915 }
916
917 pub fn reparent_children(&mut self, old_parent_id: NodeId, new_parent_id: NodeId) {
918 let child_ids = std::mem::take(&mut self.doc.nodes[old_parent_id].children);
919 self.maybe_record_node(old_parent_id);
920 self.append_children(new_parent_id, &child_ids);
921 }
922
923 pub fn replace_node_with(&mut self, anchor_node_id: NodeId, new_node_ids: &[NodeId]) {
924 self.insert_nodes_before(anchor_node_id, new_node_ids);
925 self.remove_node(anchor_node_id);
926 }
927
928 fn detach_all(&mut self, node_ids: &[NodeId]) {
940 for node_id in node_ids {
941 if self.node_has_parent(*node_id) {
942 self.remove_node(*node_id);
943 }
944 }
945 }
946
947 fn viable_sibling(
951 &self,
952 anchor_id: NodeId,
953 offset: isize,
954 excluded: &[NodeId],
955 ) -> Option<NodeId> {
956 let node = self.doc.get_node(anchor_id)?;
957 let parent = self.doc.get_node(node.parent?)?;
958 let mut index = parent.index_of_child(anchor_id)?;
959 loop {
960 index = index.checked_add_signed(offset)?;
961 let sibling_id = *parent.children.get(index)?;
962 if !excluded.contains(&sibling_id) {
963 return Some(sibling_id);
964 }
965 }
966 }
967
968 pub fn prepend_nodes(&mut self, parent_id: NodeId, node_ids: &[NodeId]) {
971 self.detach_all(node_ids);
972 match self.child_ids(parent_id).first().copied() {
973 Some(first_child_id) => self.insert_nodes_before(first_child_id, node_ids),
974 None => self.append_children(parent_id, node_ids),
975 }
976 }
977
978 pub fn replace_children(&mut self, parent_id: NodeId, node_ids: &[NodeId]) {
982 self.detach_all(node_ids);
983 for child_id in self.child_ids(parent_id) {
984 self.remove_node(child_id);
985 }
986 self.append_children(parent_id, node_ids);
987 }
988
989 pub fn before_node(&mut self, anchor_id: NodeId, node_ids: &[NodeId]) {
994 let Some(parent_id) = self.parent_id(anchor_id) else {
995 return;
996 };
997 let viable_prev = self.viable_sibling(anchor_id, -1, node_ids);
998 self.detach_all(node_ids);
999 match viable_prev {
1000 Some(prev_id) => self.insert_nodes_after(prev_id, node_ids),
1001 None => match self.child_ids(parent_id).first().copied() {
1002 Some(first_child_id) => self.insert_nodes_before(first_child_id, node_ids),
1003 None => self.append_children(parent_id, node_ids),
1004 },
1005 }
1006 }
1007
1008 pub fn after_node(&mut self, anchor_id: NodeId, node_ids: &[NodeId]) {
1013 let Some(parent_id) = self.parent_id(anchor_id) else {
1014 return;
1015 };
1016 let viable_next = self.viable_sibling(anchor_id, 1, node_ids);
1017 self.detach_all(node_ids);
1018 match viable_next {
1019 Some(next_id) => self.insert_nodes_before(next_id, node_ids),
1020 None => self.append_children(parent_id, node_ids),
1021 }
1022 }
1023
1024 pub fn replace_with_nodes(&mut self, anchor_id: NodeId, node_ids: &[NodeId]) {
1030 let Some(parent_id) = self.parent_id(anchor_id) else {
1031 return;
1032 };
1033 let viable_next = self.viable_sibling(anchor_id, 1, node_ids);
1034 self.detach_all(node_ids);
1035 if self.node_has_parent(anchor_id) {
1036 self.replace_node_with(anchor_id, node_ids);
1037 } else {
1038 match viable_next {
1039 Some(next_id) => self.insert_nodes_before(next_id, node_ids),
1040 None => self.append_children(parent_id, node_ids),
1041 }
1042 }
1043 }
1044}
1045
1046impl<'doc> DocumentMutator<'doc> {
1047 pub fn flush(&mut self) {
1048 if let Some(old) = self.previous_focus_path.take() {
1049 let new = self
1050 .doc
1051 .maybe_node_element_ancestors(self.doc.focus_node_id);
1052 self.doc.update_focus_within_paths(old, new);
1053 }
1054 if self.recompute_is_animating {
1055 self.doc.has_canvas = self.doc.compute_has_canvas();
1056 }
1057
1058 if let Some(id) = self.title_node {
1059 let title = self.doc.nodes[id].text_content();
1060 self.doc.shell_provider.set_window_title(title);
1061 }
1062
1063 for id in self.style_nodes.drain() {
1065 self.doc.process_style_element(id);
1066 }
1067
1068 for id in self.form_nodes.drain() {
1069 self.doc.reset_form_owner(id);
1070 self.doc.sync_placeholder_state(id);
1071 }
1072
1073 #[cfg(feature = "autofocus")]
1074 if let Some(node_id) = self.node_to_autofocus.take() {
1075 if self.doc.get_node(node_id).is_some() {
1076 self.doc.set_focus_to(node_id);
1077 }
1078 }
1079 }
1080
1081 pub fn set_inner_html(&mut self, node_id: NodeId, html: &str) {
1082 self.remove_and_drop_all_children(node_id);
1083 self.doc
1084 .html_parser_provider
1085 .clone()
1086 .parse_inner_html(self, node_id, html);
1087 }
1088
1089 fn flush_eager_ops(&mut self) {
1090 let mut ops = mem::take(&mut self.eager_op_queue);
1091 for op in ops.drain(0..) {
1092 match op {
1093 SpecialOp::LoadImage(node_id) => self.load_image(node_id),
1094 SpecialOp::LoadIframe(node_id) => self.load_iframe(node_id),
1095 SpecialOp::LoadStylesheet(node_id) => self.load_linked_stylesheet(node_id),
1096 SpecialOp::UnloadStylesheet(node_id) => self.unload_stylesheet(node_id),
1097 SpecialOp::LoadCustomPaintSource(node_id) => self.load_custom_paint_src(node_id),
1098 SpecialOp::ProcessButtonInput(node_id) => self.process_button_input(node_id),
1099 SpecialOp::UnloadSubDocument(node_id) => self.remove_sub_document(node_id),
1100 #[cfg(feature = "custom-widget")]
1101 SpecialOp::UnloadCustomWidget(node_id) => self.remove_custom_widget(node_id),
1102 }
1103 }
1104
1105 self.eager_op_queue = ops;
1107 }
1108
1109 fn process_added_subtree(&mut self, node_id: NodeId) {
1110 self.doc.iter_subtree_mut(node_id, |node_id, doc| {
1111 let node = &mut doc.nodes[node_id];
1112 node.flags.set(NodeFlags::IS_IN_DOCUMENT, true);
1113 node.insert_damage(ALL_DAMAGE);
1114
1115 if let Some(id_attr) = node.attr(local_name!("id")).map(ToString::to_string) {
1117 doc.add_to_id_map(&id_attr, node_id);
1118 }
1119
1120 let node = &mut doc.nodes[node_id];
1121 let NodeData::Element(ref mut element) = node.data else {
1122 return;
1123 };
1124
1125 if element
1128 .background_images
1129 .iter()
1130 .chain(&element.mask_images)
1131 .flatten()
1132 .any(|image| image.status == crate::node::Status::Error)
1133 {
1134 doc.pending_style_image_nodes.push(node_id);
1135 }
1136
1137 #[cfg(feature = "custom-widget")]
1139 let has_custom_widget = element.custom_widget_data().is_some();
1140 #[cfg(not(feature = "custom-widget"))]
1141 let has_custom_widget = false;
1142
1143 let tag = element.name.local.as_ref();
1145 match tag {
1146 "title" => self.title_node = Some(node_id),
1147 "style" => {
1148 self.style_nodes.insert(node_id);
1149 }
1150 _ if has_custom_widget => {}
1151 "link" => self.eager_op_queue.push(SpecialOp::LoadStylesheet(node_id)),
1152 "img" => self.eager_op_queue.push(SpecialOp::LoadImage(node_id)),
1153 "iframe" => self.eager_op_queue.push(SpecialOp::LoadIframe(node_id)),
1154 "canvas" => self
1155 .eager_op_queue
1156 .push(SpecialOp::LoadCustomPaintSource(node_id)),
1157 "button" | "fieldset" | "input" | "select" | "textarea" | "object" | "output" => {
1158 self.eager_op_queue
1159 .push(SpecialOp::ProcessButtonInput(node_id));
1160 self.form_nodes.insert(node_id);
1161 }
1162 _ => {}
1163 }
1164
1165 #[cfg(feature = "autofocus")]
1166 if node.is_focussable() {
1167 if let NodeData::Element(ref element) = node.data {
1168 if let Some(value) = element.attr(local_name!("autofocus")) {
1172 if value != "false" {
1173 self.node_to_autofocus = Some(node_id);
1174 }
1175 }
1176 }
1177 }
1178 });
1179
1180 self.flush_eager_ops();
1181 }
1182
1183 fn process_removed_subtree(&mut self, node_id: NodeId) {
1184 self.doc.iter_subtree_mut(node_id, |node_id, doc| {
1185 doc.retire_top_layer(node_id);
1186 doc.nodes[node_id]
1187 .flags
1188 .set(NodeFlags::IS_IN_DOCUMENT, false);
1189
1190 doc.clear_interaction_state_for_removed_node(node_id);
1194
1195 let node = &mut doc.nodes[node_id];
1196
1197 if doc.text_selection.anchor.node_or_parent == Some(node_id)
1200 || doc.text_selection.focus.node_or_parent == Some(node_id)
1201 {
1202 doc.text_selection.clear();
1203 }
1204
1205 if node.has_snapshot() {
1208 let opaque_id = style::dom::TNode::opaque(&&*node);
1209 doc.snapshots.remove(&opaque_id);
1210 node.set_has_snapshot(false);
1211 }
1212
1213 if let Some(id_attr) = node.attr(local_name!("id")).map(ToString::to_string) {
1215 doc.remove_from_id_map(&id_attr, node_id);
1216 }
1217
1218 let node = &mut doc.nodes[node_id];
1219 let NodeData::Element(ref mut element) = node.data else {
1220 return;
1221 };
1222
1223 match &element.special_data {
1224 SpecialElementData::SubDocument(_) => {
1225 self.eager_op_queue
1226 .push(SpecialOp::UnloadSubDocument(node_id));
1227 }
1228 #[cfg(feature = "custom-widget")]
1229 SpecialElementData::CustomWidget(_) => {
1230 self.eager_op_queue
1231 .push(SpecialOp::UnloadCustomWidget(node_id));
1232 }
1233 SpecialElementData::Stylesheet(_) => self
1234 .eager_op_queue
1235 .push(SpecialOp::UnloadStylesheet(node_id)),
1236 SpecialElementData::Image(_) => {}
1237 SpecialElementData::Canvas(_) => {
1238 self.recompute_is_animating = true;
1239 }
1240 SpecialElementData::TableRoot(_) => {}
1241 SpecialElementData::TextInput(_) => {}
1242 SpecialElementData::CheckboxInput(_) => {}
1243 #[cfg(feature = "file-input")]
1244 SpecialElementData::FileInput(_) => {}
1245 SpecialElementData::None => {}
1246 }
1247 });
1248
1249 self.flush_eager_ops();
1250 }
1251
1252 fn maybe_record_node(&mut self, node_id: impl Into<Option<NodeId>>) {
1253 let Some(node_id) = node_id.into() else {
1254 return;
1255 };
1256
1257 let Some(tag_name) = self.doc.nodes[node_id]
1258 .data
1259 .downcast_element()
1260 .map(|elem| &elem.name.local)
1261 else {
1262 return;
1263 };
1264
1265 match tag_name.as_ref() {
1266 "title" => self.title_node = Some(node_id),
1267 "style" => {
1268 self.style_nodes.insert(node_id);
1269 }
1270 _ => {}
1271 }
1272 }
1273
1274 fn load_linked_stylesheet(&mut self, target_id: NodeId) {
1275 let node = &self.doc.nodes[target_id];
1276
1277 let mut is_in_head = false;
1278 let mut parent_id = node.parent;
1279 while let Some(id) = parent_id
1280 && !is_in_head
1281 {
1282 let parent = &self.doc.nodes[id];
1283 is_in_head |= parent.data.is_element_with_tag_name(&local_name!("head"));
1284 parent_id = parent.parent;
1285 }
1286
1287 let rel_attr = node.attr(local_name!("rel"));
1288 let href_attr = node.attr(local_name!("href"));
1289
1290 let (Some(rels), Some(href)) = (rel_attr, href_attr) else {
1291 return;
1292 };
1293 if !rels.split_ascii_whitespace().any(|rel| rel == "stylesheet") {
1294 return;
1295 }
1296
1297 let url = self.doc.resolve_url(href);
1298 let Some(handler) = ResourceHandler::new(
1299 self.doc.tx.clone(),
1300 self.doc.id(),
1301 Some(node.id),
1302 self.doc.shell_provider.clone(),
1303 StylesheetHandler {
1304 guard: self.doc.guard.clone(),
1305 net_provider: self.doc.net_provider.clone(),
1306 abort_signal: self.doc.abort_signal.clone(),
1307 },
1308 ) else {
1309 return;
1310 };
1311
1312 if is_in_head && !self.doc.net_provider.is_noop() {
1313 self.doc
1314 .pending_critical_resources
1315 .insert(handler.request_id());
1316 }
1317
1318 self.doc.net_provider.fetch(
1319 self.doc.id(),
1320 self.doc.build_request(url),
1321 Box::new(handler),
1322 );
1323 }
1324
1325 fn unload_stylesheet(&mut self, node_id: NodeId) {
1326 let node = &mut self.doc.nodes[node_id];
1327 let Some(element) = node.element_data_mut() else {
1328 unreachable!();
1329 };
1330 let SpecialElementData::Stylesheet(stylesheet) = element.special_data.take() else {
1331 unreachable!();
1332 };
1333
1334 let guard = self.doc.guard.read();
1335 self.doc.stylist.remove_stylesheet(stylesheet, &guard);
1336 self.doc
1337 .stylist
1338 .force_stylesheet_origins_dirty(OriginSet::all());
1339
1340 self.doc.nodes_to_stylesheet.remove(&node_id);
1341 self.doc.stylesheet_generation += 1;
1342 }
1343
1344 fn load_image(&mut self, target_id: NodeId) {
1345 let Some(request) = self.doc.image_observations.begin(target_id) else {
1346 self.doc.resource_counters.rejected =
1347 self.doc.resource_counters.rejected.saturating_add(1);
1348 return;
1349 };
1350 self.doc.cancel_replaced_image_consumers(target_id);
1352 if self.doc.net_provider.is_noop() {
1353 self.doc.image_observations.complete(
1354 target_id,
1355 request,
1356 crate::ImageLoadStatus::Failed,
1357 );
1358 return;
1359 }
1360 let node = &self.doc.nodes[target_id];
1361 if let Some(raw_src) = node.attr(local_name!("src")) {
1362 if !raw_src.is_empty() {
1363 let Some(src) = self.doc.url.resolve_relative(raw_src) else {
1364 self.doc.resource_counters.failures =
1365 self.doc.resource_counters.failures.saturating_add(1);
1366 self.doc.image_observations.complete(
1367 target_id,
1368 request,
1369 crate::ImageLoadStatus::Failed,
1370 );
1371 return;
1372 };
1373 let src_string = src.as_str();
1374
1375 if let Some(cached_image) = self.doc.image_cache.get(src_string) {
1377 #[cfg(feature = "tracing")]
1378 tracing::info!("Loading image {src_string} from cache");
1379 let node = &mut self.doc.nodes[target_id];
1380 node.element_data_mut().unwrap().special_data =
1381 SpecialElementData::Image(Box::new(cached_image.clone()));
1382 node.clear_layout_cache();
1383 node.insert_damage(ALL_DAMAGE);
1384 self.doc.image_observations.complete(
1385 target_id,
1386 request,
1387 crate::ImageLoadStatus::Loaded,
1388 );
1389 return;
1390 }
1391
1392 if let Some(waiting_list) = self.doc.pending_images.get_mut(src_string) {
1394 #[cfg(feature = "tracing")]
1395 tracing::info!("Image {src_string} already pending, queueing node {target_id}");
1396 if waiting_list.waiters.len() < self.doc.resource_limits.image_waiters {
1397 waiting_list
1398 .waiters
1399 .push((target_id, ImageType::Image, Some(request)));
1400 } else {
1401 self.doc.resource_counters.rejected =
1402 self.doc.resource_counters.rejected.saturating_add(1);
1403 self.doc.image_observations.complete(
1404 target_id,
1405 request,
1406 crate::ImageLoadStatus::Failed,
1407 );
1408 }
1409 return;
1410 }
1411
1412 #[cfg(feature = "tracing")]
1414 tracing::info!("Fetching image {src_string}");
1415 if self.doc.pending_images.len() >= self.doc.resource_limits.pending_images
1416 || self.doc.resource_limits.image_waiters == 0
1417 {
1418 self.doc.resource_counters.rejected =
1419 self.doc.resource_counters.rejected.saturating_add(1);
1420 self.doc.image_observations.complete(
1421 target_id,
1422 request,
1423 crate::ImageLoadStatus::Failed,
1424 );
1425 return;
1426 }
1427 let Some(handler) = ResourceHandler::new(
1428 self.doc.tx.clone(),
1429 self.doc.id(),
1430 None,
1431 self.doc.shell_provider.clone(),
1432 ImageHandler::with_limits(ImageType::Image, self.doc.resource_limits),
1433 ) else {
1434 self.doc.image_observations.complete(
1435 target_id,
1436 request,
1437 crate::ImageLoadStatus::Failed,
1438 );
1439 return;
1440 };
1441 self.doc.pending_images.insert(
1442 src_string.to_string(),
1443 crate::resources::PendingImage {
1444 request_id: handler.request_id(),
1445 waiters: vec![(target_id, ImageType::Image, Some(request))],
1446 },
1447 );
1448
1449 self.doc.net_provider.fetch(
1450 self.doc.id(),
1451 self.doc.build_request(src),
1452 Box::new(handler),
1453 );
1454 return;
1455 }
1456 }
1457 self.doc
1458 .image_observations
1459 .complete(target_id, request, crate::ImageLoadStatus::Failed);
1460 }
1461
1462 fn load_iframe(&mut self, target_id: NodeId) {
1463 if self.doc.subdocument_depth >= crate::iframe::MAX_SUBDOCUMENT_DEPTH {
1464 #[cfg(feature = "tracing")]
1465 tracing::warn!(
1466 "Not loading iframe: max sub-document nesting depth ({}) reached",
1467 crate::iframe::MAX_SUBDOCUMENT_DEPTH
1468 );
1469 return;
1470 }
1471
1472 let node = &self.doc.nodes[target_id];
1473 let Some(element) = node.element_data() else {
1474 return;
1475 };
1476
1477 if let Some(srcdoc) = element.attr(local_name!("srcdoc")) {
1479 let srcdoc = srcdoc.to_string();
1480 self.doc.load_iframe_srcdoc(target_id, &srcdoc);
1481 return;
1482 }
1483
1484 let Some(raw_src) = element.attr(local_name!("src")) else {
1485 return;
1486 };
1487 if raw_src.is_empty() {
1488 return;
1489 }
1490 let Some(url) = self.doc.url.resolve_relative(raw_src) else {
1491 #[cfg(feature = "tracing")]
1492 tracing::warn!("Not loading iframe: could not resolve url {raw_src}");
1493 return;
1494 };
1495 self.doc.start_iframe_load(target_id, url);
1496 }
1497
1498 fn load_custom_paint_src(&mut self, target_id: NodeId) {
1499 let node = &mut self.doc.nodes[target_id];
1500 if let Some(raw_src) = node.attr(local_name!("src")) {
1501 if let Ok(custom_paint_source_id) = raw_src.parse::<u64>() {
1502 self.recompute_is_animating = true;
1503 let canvas_data = SpecialElementData::Canvas(CanvasData {
1504 custom_paint_source_id,
1505 });
1506 node.element_data_mut().unwrap().special_data = canvas_data;
1507 }
1508 }
1509 }
1510
1511 fn process_button_input(&mut self, target_id: NodeId) {
1512 let node = &self.doc.nodes[target_id];
1513 let Some(data) = node.element_data() else {
1514 return;
1515 };
1516
1517 let tagname = data.name.local.as_ref();
1518 let type_attr = data.attr(local_name!("type"));
1519 let value = data.attr(local_name!("value"));
1520
1521 if let ("input", Some("button" | "submit" | "reset"), Some(value)) =
1524 (tagname, type_attr, value)
1525 {
1526 let value = value.to_string();
1527 let id = self.create_text_node(&value);
1528 self.append_children(target_id, &[id]);
1529 return;
1530 }
1531 #[cfg(feature = "file-input")]
1532 if let ("input", Some("file")) = (tagname, type_attr) {
1533 let button_id = self.create_element(
1534 qual_name!("button", html),
1535 vec![
1536 Attribute {
1537 name: qual_name!("type", html),
1538 value: "button".to_string(),
1539 },
1540 Attribute {
1541 name: qual_name!("tabindex", html),
1542 value: "-1".to_string(),
1543 },
1544 ],
1545 );
1546 let label_id = self.create_element(qual_name!("label", html), vec![]);
1547 let text_id = self.create_text_node("No File Selected");
1548 let button_text_id = self.create_text_node("Browse");
1549 self.append_children(target_id, &[button_id, label_id]);
1550 self.append_children(label_id, &[text_id]);
1551 self.append_children(button_id, &[button_text_id]);
1552 }
1553 }
1554}
1555
1556fn set_input_checked_state(element: &mut ElementData, value: String) {
1558 let Ok(checked) = value.parse() else {
1559 return;
1560 };
1561 match element.special_data {
1562 SpecialElementData::CheckboxInput(_) => element.set_checkbox_input_checked(checked),
1563 SpecialElementData::None => element.attrs.push(Attribute {
1568 name: qual_name!("checked", html),
1569 value: checked.to_string(),
1570 }),
1571 _ => {}
1572 }
1573}
1574
1575pub struct ViewportMut<'doc> {
1578 doc: &'doc mut BaseDocument,
1579 initial_viewport: Viewport,
1580}
1581impl ViewportMut<'_> {
1582 pub fn new(doc: &mut BaseDocument) -> ViewportMut<'_> {
1583 let initial_viewport = doc.viewport.clone();
1584 ViewportMut {
1585 doc,
1586 initial_viewport,
1587 }
1588 }
1589}
1590impl Deref for ViewportMut<'_> {
1591 type Target = Viewport;
1592
1593 fn deref(&self) -> &Self::Target {
1594 &self.doc.viewport
1595 }
1596}
1597impl DerefMut for ViewportMut<'_> {
1598 fn deref_mut(&mut self) -> &mut Self::Target {
1599 &mut self.doc.viewport
1600 }
1601}
1602impl Drop for ViewportMut<'_> {
1603 fn drop(&mut self) {
1604 if self.doc.viewport == self.initial_viewport {
1605 return;
1606 }
1607
1608 let changes = DeviceChanges::from_viewports(&self.initial_viewport, &self.doc.viewport);
1609 self.doc.queue_device_changes(changes);
1610 }
1611}
1612
1613#[cfg(test)]
1614mod test {
1615 use style::media_queries::MediaType;
1616 use style::servo_arc::Arc as ServoArc;
1617 use style_dom::ElementState;
1618
1619 use std::sync::{
1620 Arc,
1621 atomic::{AtomicUsize, Ordering},
1622 };
1623
1624 use apace_traits::shell::{ColorScheme, ShellProvider, Viewport};
1625
1626 use crate::{Attribute, BaseDocument, DocumentConfig, ElementData, NodeData, qual_name};
1627
1628 #[test]
1629 fn media_type_defaults_to_screen() {
1630 let mut document = BaseDocument::new(DocumentConfig::default());
1631 assert_eq!(*document.media_type(), MediaType::screen());
1632 assert_eq!(document.stylist_device().media_type(), MediaType::screen());
1633 }
1634
1635 #[test]
1636 fn media_type_honors_config() {
1637 let mut document = BaseDocument::new(DocumentConfig {
1638 media_type: Some(MediaType::print()),
1639 ..Default::default()
1640 });
1641 assert_eq!(*document.media_type(), MediaType::print());
1642 assert_eq!(document.stylist_device().media_type(), MediaType::print());
1643 }
1644
1645 #[test]
1646 fn set_media_type_updates_stylist_device() {
1647 let mut document = BaseDocument::new(DocumentConfig::default());
1648 assert_eq!(document.stylist_device().media_type(), MediaType::screen());
1649
1650 document.set_media_type(MediaType::print());
1651 assert_eq!(*document.media_type(), MediaType::print());
1652 assert_eq!(document.stylist_device().media_type(), MediaType::print());
1653 }
1654
1655 #[test]
1656 fn mutator_remove_disabled() {
1657 let mut document = BaseDocument::new(DocumentConfig::default());
1658 let id = document.create_node(NodeData::Element(Box::new(ElementData::new(
1659 qual_name!("button"),
1660 vec![Attribute {
1661 name: qual_name!("disabled"),
1662 value: "".into(),
1663 }],
1664 ))));
1665
1666 let node = document.get_node(id).unwrap();
1667 assert!(
1668 node.element_state().contains(ElementState::DISABLED),
1669 "form node is disabled"
1670 );
1671 assert!(
1672 !node.element_state().contains(ElementState::ENABLED),
1673 "form node is not enabled yet"
1674 );
1675
1676 let mut mutator = document.mutate();
1677 mutator.clear_attribute(id, qual_name!("disabled"));
1678 drop(mutator);
1679
1680 let node = document.get_node(id).unwrap();
1681 assert!(
1682 !node.element_state().contains(ElementState::DISABLED),
1683 "form node is no longer disabled"
1684 );
1685 assert!(
1686 node.element_state().contains(ElementState::ENABLED),
1687 "form node is enabled"
1688 );
1689 }
1690
1691 #[test]
1692 fn mutator_set_disabled() {
1693 let mut document = BaseDocument::new(DocumentConfig::default());
1694 let id = document.create_node(NodeData::Element(Box::new(ElementData::new(
1695 qual_name!("button"),
1696 vec![],
1697 ))));
1698
1699 let node = document.get_node(id).unwrap();
1700 assert!(
1701 !node.element_state().contains(ElementState::DISABLED),
1702 "form node is not disabled"
1703 );
1704 assert!(
1705 node.element_state().contains(ElementState::ENABLED),
1706 "form node is enabled"
1707 );
1708
1709 let mut mutator = document.mutate();
1710 mutator.set_attribute(id, qual_name!("disabled"), "");
1711 drop(mutator);
1712
1713 let node = document.get_node(id).unwrap();
1714
1715 assert!(
1716 node.element_state().contains(ElementState::DISABLED),
1717 "form node is disabled"
1718 );
1719 assert!(
1720 !node.element_state().contains(ElementState::ENABLED),
1721 "form node is no longer enabled enabled"
1722 );
1723 }
1724
1725 #[test]
1726 fn mutator_set_disabled_invalid_node() {
1727 let mut document = BaseDocument::new(DocumentConfig::default());
1728 let id = document.create_node(NodeData::Element(Box::new(ElementData::new(
1729 qual_name!("a"),
1730 vec![],
1731 ))));
1732
1733 let node = document.get_node(id).unwrap();
1734 assert!(
1735 !node.element_state().contains(ElementState::DISABLED),
1736 "form node is not disabled"
1737 );
1738 assert!(
1739 !node.element_state().contains(ElementState::ENABLED),
1740 "form node is enabled"
1741 );
1742
1743 let mut mutator = document.mutate();
1744 mutator.set_attribute(id, qual_name!("disabled"), "");
1745 drop(mutator);
1746
1747 let node = document.get_node(id).unwrap();
1748 assert!(
1749 !node.element_state().contains(ElementState::DISABLED),
1750 "form node is not disabled"
1751 );
1752 assert!(
1753 !node.element_state().contains(ElementState::ENABLED),
1754 "form node is enabled"
1755 );
1756 }
1757
1758 #[test]
1759 fn mutator_id_attribute_updates_id_map() {
1760 let mut document = BaseDocument::new(DocumentConfig::default());
1761 let root_id = document.root_node().id;
1762
1763 let node_id = {
1764 let mut mutator = document.mutate();
1765 let node_id = mutator.create_element(
1766 qual_name!("div"),
1767 vec![Attribute {
1768 name: qual_name!("id"),
1769 value: "old".into(),
1770 }],
1771 );
1772 mutator.append_children(root_id, &[node_id]);
1773 node_id
1774 };
1775 assert_eq!(document.get_element_by_id("old"), Some(node_id));
1776
1777 {
1778 let mut mutator = document.mutate();
1779 mutator.set_attribute(node_id, qual_name!("id"), "new");
1780 }
1781 assert_eq!(document.get_element_by_id("new"), Some(node_id));
1782 assert_eq!(document.get_element_by_id("old"), None);
1783
1784 {
1785 let mut mutator = document.mutate();
1786 mutator.clear_attribute(node_id, qual_name!("id"));
1787 }
1788 assert_eq!(document.get_element_by_id("new"), None);
1789 }
1790
1791 #[test]
1792 fn get_element_by_id_duplicate_ids_first_in_tree_order_wins() {
1793 let mut document = BaseDocument::new(DocumentConfig::default());
1794 let root_id = document.root_node().id;
1795
1796 let (first_id, second_id) = {
1797 let mut mutator = document.mutate();
1798 let first_id = mutator.create_element(qual_name!("div"), vec![]);
1799 let second_id = mutator.create_element(qual_name!("div"), vec![]);
1800 mutator.append_children(root_id, &[first_id, second_id]);
1801 mutator.set_attribute(second_id, qual_name!("id"), "dup");
1804 mutator.set_attribute(first_id, qual_name!("id"), "dup");
1805 (first_id, second_id)
1806 };
1807 assert_eq!(document.get_element_by_id("dup"), Some(first_id));
1808
1809 {
1810 let mut mutator = document.mutate();
1811 mutator.remove_node(first_id);
1812 }
1813 assert_eq!(document.get_element_by_id("dup"), Some(second_id));
1814 }
1815
1816 #[derive(Default)]
1817 struct RedrawShell {
1818 redraw_requests: AtomicUsize,
1819 }
1820
1821 impl ShellProvider for RedrawShell {
1822 fn request_redraw(&self) {
1823 self.redraw_requests.fetch_add(1, Ordering::Relaxed);
1824 }
1825 }
1826
1827 #[test]
1828 fn mutator_requests_redraw_only_after_mutation() {
1829 let shell = Arc::new(RedrawShell::default());
1830 let mut document = BaseDocument::new(DocumentConfig {
1831 shell_provider: Some(shell.clone()),
1832 ..Default::default()
1833 });
1834 let root_id = document.root_node().id;
1835
1836 {
1837 let mut mutator = document.mutate();
1838 let parent_id = mutator.create_element(qual_name!("div"), vec![]);
1839 let child_id = mutator.create_element(qual_name!("span"), vec![]);
1840 mutator.append_children(parent_id, &[child_id]);
1841 mutator.remove_and_drop_all_children(parent_id);
1842 mutator.set_attribute(parent_id, qual_name!("id"), "detached");
1843 }
1844 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 0);
1845
1846 {
1847 let mutator = document.mutate();
1848 assert_eq!(mutator.child_ids(root_id).len(), 0);
1849 }
1850
1851 {
1852 let mut mutator = document.mutate();
1853 let node_id = mutator.create_element(qual_name!("div"), vec![]);
1854 mutator.append_children(root_id, &[node_id]);
1855 mutator.set_attribute(node_id, qual_name!("id"), "in-document");
1856 }
1857 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 1);
1858
1859 {
1860 let mut mutator = document.mutate();
1861 let parent_id = mutator.create_element(qual_name!("div"), vec![]);
1862 let child_id = mutator.create_element(qual_name!("span"), vec![]);
1863 mutator.append_children(root_id, &[parent_id]);
1864 mutator.append_children(parent_id, &[child_id]);
1865 mutator.remove_and_drop_all_children(parent_id);
1866 }
1867 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 2);
1868
1869 {
1870 let mut mutator = document.mutate();
1871 let parent_id = mutator.create_element(qual_name!("div"), vec![]);
1872 let child_id = mutator.create_element(qual_name!("span"), vec![]);
1873 let detached_target_id = mutator.create_element(qual_name!("div"), vec![]);
1874 mutator.append_children(root_id, &[parent_id]);
1875 mutator.append_children(parent_id, &[child_id]);
1876 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 2);
1877 mutator.append_children(detached_target_id, &[child_id]);
1878 }
1879 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 3);
1880 }
1881
1882 #[test]
1883 fn moving_subtree_out_of_document_clears_in_document_flag() {
1884 let shell = Arc::new(RedrawShell::default());
1885 let mut document = BaseDocument::new(DocumentConfig {
1886 shell_provider: Some(shell.clone()),
1887 ..Default::default()
1888 });
1889 let root_id = document.root_node().id;
1890 let (child_id, grandchild_id, detached_parent_id) = {
1891 let mut mutator = document.mutate();
1892 let in_document_parent_id = mutator.create_element(qual_name!("div"), vec![]);
1893 let child_id = mutator.create_element(qual_name!("div"), vec![]);
1894 let grandchild_id = mutator.create_element(qual_name!("span"), vec![]);
1895 let detached_parent_id = mutator.create_element(qual_name!("section"), vec![]);
1896 mutator.append_children(root_id, &[in_document_parent_id]);
1897 mutator.append_children(in_document_parent_id, &[child_id]);
1898 mutator.append_children(child_id, &[grandchild_id]);
1899 (child_id, grandchild_id, detached_parent_id)
1900 };
1901 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 1);
1902 assert!(document.get_node(child_id).unwrap().flags.is_in_document());
1903 assert!(
1904 document
1905 .get_node(grandchild_id)
1906 .unwrap()
1907 .flags
1908 .is_in_document()
1909 );
1910
1911 {
1912 let mut mutator = document.mutate();
1913 mutator.append_children(detached_parent_id, &[child_id]);
1914 }
1915 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 2);
1916 assert!(!document.get_node(child_id).unwrap().flags.is_in_document());
1917 assert!(
1918 !document
1919 .get_node(grandchild_id)
1920 .unwrap()
1921 .flags
1922 .is_in_document()
1923 );
1924
1925 {
1926 let mut mutator = document.mutate();
1927 mutator.set_attribute(child_id, qual_name!("id"), "detached");
1928 }
1929 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 2);
1930
1931 {
1932 let mut mutator = document.mutate();
1933 mutator.append_children(root_id, &[child_id]);
1934 }
1935 assert_eq!(shell.redraw_requests.load(Ordering::Relaxed), 3);
1936 assert!(document.get_node(child_id).unwrap().flags.is_in_document());
1937 assert!(
1938 document
1939 .get_node(grandchild_id)
1940 .unwrap()
1941 .flags
1942 .is_in_document()
1943 );
1944 }
1945
1946 #[test]
1947 fn style_property_updates_nested_layout() {
1948 let mut document = BaseDocument::new(DocumentConfig {
1949 viewport: Some(Viewport::new(800, 600, 1.0, ColorScheme::Light)),
1950 ..Default::default()
1951 });
1952 let root_id = document.root_node().id;
1953
1954 let mover_id = {
1955 let mut mutator = document.mutate();
1956 let parent_id = mutator.create_element(qual_name!("div"), vec![]);
1957 let mover_id = mutator.create_element(qual_name!("div"), vec![]);
1958 mutator.set_style_property(parent_id, "position", "relative");
1959 mutator.set_style_property(parent_id, "width", "800px");
1960 mutator.set_style_property(parent_id, "height", "600px");
1961 mutator.set_style_property(mover_id, "position", "absolute");
1962 mutator.set_style_property(mover_id, "left", "0px");
1963 mutator.set_style_property(mover_id, "top", "0px");
1964 mutator.append_children(parent_id, &[mover_id]);
1965 mutator.append_children(root_id, &[parent_id]);
1966 mover_id
1967 };
1968
1969 document.resolve(0.0);
1970 assert_eq!(
1971 document
1972 .get_node(mover_id)
1973 .unwrap()
1974 .final_layout()
1975 .location
1976 .x,
1977 0.0
1978 );
1979
1980 {
1981 let mut mutator = document.mutate();
1982 mutator.set_style_property(mover_id, "left", "120px");
1983 }
1984
1985 document.resolve(0.0);
1986 assert_eq!(
1987 document
1988 .get_node(mover_id)
1989 .unwrap()
1990 .final_layout()
1991 .location
1992 .x,
1993 120.0
1994 );
1995 }
1996
1997 #[test]
1998 fn style_property_mutation_copies_blocks_in_rule_tree() {
1999 let mut document = BaseDocument::new(DocumentConfig {
2000 viewport: Some(Viewport::new(800, 600, 1.0, ColorScheme::Light)),
2001 ..Default::default()
2002 });
2003 let root_id = document.root_node().id;
2004
2005 let div_id = {
2006 let mut mutator = document.mutate();
2007 let div_id = mutator.create_element(qual_name!("div"), vec![]);
2008 mutator.set_style_property(div_id, "width", "100px");
2009 mutator.set_style_property(div_id, "height", "50px");
2010 mutator.append_children(root_id, &[div_id]);
2011 div_id
2012 };
2013
2014 let style_block = |document: &BaseDocument| {
2015 document
2016 .get_node(div_id)
2017 .unwrap()
2018 .element_data()
2019 .unwrap()
2020 .style_attribute
2021 .clone()
2022 .unwrap()
2023 };
2024
2025 let before_resolve = style_block(&document);
2026 {
2027 let mut mutator = document.mutate();
2028 mutator.set_style_property(div_id, "height", "60px");
2029 }
2030 assert!(ServoArc::ptr_eq(&before_resolve, &style_block(&document)));
2032
2033 document.resolve(0.0);
2034 let in_rule_tree = style_block(&document);
2035 assert!(
2036 in_rule_tree
2037 .read_with(&document.guard.read())
2038 .immutable
2039 .load(Ordering::Relaxed)
2040 );
2041
2042 {
2043 let mut mutator = document.mutate();
2044 mutator.set_style_property(div_id, "width", "200px");
2045 }
2046 let after_set = style_block(&document);
2047 assert!(!ServoArc::ptr_eq(&in_rule_tree, &after_set));
2048 {
2049 let guard = document.guard.read();
2050 let old = in_rule_tree.read_with(&guard);
2051 let new = after_set.read_with(&guard);
2052 assert_eq!(old.declarations().len(), 2);
2053 assert_eq!(new.declarations().len(), 2);
2054 assert_ne!(old, new);
2055 }
2056
2057 document.resolve(0.0);
2058 assert_eq!(
2059 document.get_node(div_id).unwrap().final_layout().size.width,
2060 200.0
2061 );
2062
2063 let in_rule_tree = style_block(&document);
2064 {
2065 let mut mutator = document.mutate();
2066 mutator.remove_style_property(div_id, "height");
2067 }
2068 let after_remove = style_block(&document);
2069 assert!(!ServoArc::ptr_eq(&in_rule_tree, &after_remove));
2070 {
2071 let guard = document.guard.read();
2072 assert_eq!(in_rule_tree.read_with(&guard).declarations().len(), 2);
2073 assert_eq!(after_remove.read_with(&guard).declarations().len(), 1);
2074 }
2075 }
2076}