1use std::collections::HashSet;
2use std::mem;
3use std::ops::{Deref, DerefMut};
4
5use crate::document::make_device;
6use crate::layout::damage::ALL_DAMAGE;
7use crate::net::{ImageHandler, ResourceHandler, StylesheetHandler};
8use crate::node::{CanvasData, NodeFlags, SpecialElementData};
9use crate::util::ImageType;
10use crate::{
11 Attribute, BaseDocument, Document, ElementData, Node, NodeData, QualName, local_name, qual_name,
12};
13use bliss_traits::net::Request;
14use bliss_traits::shell::Viewport;
15use style::Atom;
16use style::invalidation::element::restyle_hints::RestyleHint;
17use style::stylesheets::OriginSet;
18
19macro_rules! tag_and_attr {
20 ($tag:tt, $attr:tt) => {
21 (&local_name!($tag), &local_name!($attr))
22 };
23}
24
25#[derive(Debug, Clone)]
26pub enum AppendTextErr {
27 NotTextNode,
29}
30
31enum SpecialOp {
34 LoadImage(usize),
35 LoadStylesheet(usize),
36 UnloadStylesheet(usize),
37 LoadCustomPaintSource(usize),
38 ProcessButtonInput(usize),
39}
40
41pub struct DocumentMutator<'doc> {
42 pub doc: &'doc mut BaseDocument,
45
46 eager_op_queue: Vec<SpecialOp>,
47
48 title_node: Option<usize>,
50 style_nodes: HashSet<usize>,
51 form_nodes: HashSet<usize>,
52
53 recompute_is_animating: bool,
55
56 #[cfg(feature = "autofocus")]
58 node_to_autofocus: Option<usize>,
59}
60
61impl Drop for DocumentMutator<'_> {
62 fn drop(&mut self) {
63 self.flush(); }
65}
66
67impl DocumentMutator<'_> {
68 pub fn new<'doc>(doc: &'doc mut BaseDocument) -> DocumentMutator<'doc> {
69 DocumentMutator {
70 doc,
71 eager_op_queue: Vec::new(),
72 title_node: None,
73 style_nodes: HashSet::new(),
74 form_nodes: HashSet::new(),
75 recompute_is_animating: false,
76 #[cfg(feature = "autofocus")]
77 node_to_autofocus: None,
78 }
79 }
80
81 pub fn node_has_parent(&self, node_id: usize) -> bool {
84 self.doc.get_node(node_id).is_some_and(|n| n.parent.is_some())
85 }
86
87 pub fn previous_sibling_id(&self, node_id: usize) -> Option<usize> {
88 self.doc.get_node(node_id)?.backward(1).map(|node| node.id)
89 }
90
91 pub fn next_sibling_id(&self, node_id: usize) -> Option<usize> {
92 self.doc.get_node(node_id)?.forward(1).map(|node| node.id)
93 }
94
95 pub fn parent_id(&self, node_id: usize) -> Option<usize> {
96 self.doc.get_node(node_id)?.parent
97 }
98
99 pub fn last_child_id(&self, node_id: usize) -> Option<usize> {
100 self.doc.get_node(node_id)?.children.last().copied()
101 }
102
103 pub fn child_ids(&self, node_id: usize) -> Vec<usize> {
104 self.doc.get_node(node_id).map(|n| n.children.clone()).unwrap_or_default()
105 }
106
107 pub fn element_name(&self, node_id: usize) -> Option<&QualName> {
108 self.doc.get_node(node_id)?.element_data().map(|el| &el.name)
109 }
110
111 pub fn node_at_path(&self, start_node_id: usize, path: &[u8]) -> usize {
112 let Some(mut current) = self.doc.get_node(start_node_id) else {
113 return start_node_id;
114 };
115 for i in path {
116 let Some(new_id) = current.children.get(*i as usize) else {
117 return current.id;
118 };
119 let Some(next) = self.doc.get_node(*new_id) else {
120 return current.id;
121 };
122 current = next;
123 }
124 current.id
125 }
126
127 pub fn create_comment_node(&mut self) -> usize {
130 self.doc.create_node(NodeData::Comment)
131 }
132
133 pub fn create_text_node(&mut self, text: &str) -> usize {
134 self.doc.create_text_node(text)
135 }
136
137 pub fn attach_shadow(&mut self, host_id: usize) -> usize {
142 if self.doc.get_node(host_id).is_none() {
143 debug_assert!(false, "attach_shadow called with stale host_id={host_id}");
144 return 0;
145 }
146 let shadow_root_id = self.doc.create_node(NodeData::ShadowRoot { host: host_id });
147 let shadow_root = &mut self.doc.nodes[shadow_root_id];
148 debug_assert_eq!(shadow_root.id, shadow_root_id);
149 shadow_root.parent = Some(host_id);
150 shadow_root.flags.insert(NodeFlags::IS_IN_DOCUMENT);
151 self.doc.nodes[host_id].children.push(shadow_root_id);
152 shadow_root_id
153 }
154
155 pub fn create_element(&mut self, name: QualName, attrs: Vec<Attribute>) -> usize {
156 let mut data = ElementData::new(name, attrs);
157 data.flush_style_attribute(self.doc.guard(), &self.doc.url.url_extra_data());
158
159 let id = self.doc.create_node(NodeData::Element(data));
160 let node = self
165 .doc
166 .get_node(id)
167 .expect("create_node returned stale id");
168
169 *node.stylo_element_data.borrow_mut() = Some(style::data::ElementData {
171 damage: ALL_DAMAGE,
172 ..Default::default()
173 });
174
175 id
176 }
177
178 pub fn deep_clone_node(&mut self, node_id: usize) -> usize {
183 self.doc.deep_clone_node(node_id)
184 }
185
186 pub fn set_node_text(&mut self, node_id: usize, value: &str) {
189 let Some(node) = self.doc.get_node_mut(node_id) else {
190 return;
191 };
192
193 let text = match node.data {
194 NodeData::Text(ref mut text) => text,
195 _ => return,
197 };
198
199 let changed = text.content != value;
200 if changed {
201 text.content.clear();
202 text.content.push_str(value);
203 node.insert_damage(ALL_DAMAGE);
204 node.mark_ancestors_dirty();
207 let parent_id = node.parent;
208
209 if let Some(parent_id) = parent_id {
212 if let Some(parent) = self.doc.get_node_mut(parent_id) {
213 parent.insert_damage(ALL_DAMAGE);
214 } else {
215 debug_assert!(false, "set_node_text parent_id={parent_id} stale after get_node_mut on child");
216 }
217 }
218
219 self.maybe_record_node(parent_id);
220 }
221 }
222
223 pub fn append_text_to_node(&mut self, node_id: usize, text: &str) -> Result<(), AppendTextErr> {
224 let Some(node) = self.doc.get_node_mut(node_id) else {
225 return Err(AppendTextErr::NotTextNode);
226 };
227 node.insert_damage(ALL_DAMAGE);
228 node.mark_ancestors_dirty();
229 match node.text_data_mut() {
230 Some(data) => {
231 data.content += text;
232 Ok(())
233 }
234 None => Err(AppendTextErr::NotTextNode),
235 }
236 }
237
238 pub fn add_attrs_if_missing(&mut self, node_id: usize, attrs: Vec<Attribute>) {
239 let Some(node) = self.doc.get_node_mut(node_id) else {
240 return;
241 };
242 node.insert_damage(ALL_DAMAGE);
243 let Some(element_data) = node.element_data_mut() else {
244 debug_assert!(false, "add_attrs_if_missing called on non-element node_id={node_id}");
245 return;
246 };
247
248 let existing_names = element_data
249 .attrs
250 .iter()
251 .map(|e| e.name.clone())
252 .collect::<HashSet<_>>();
253
254 for attr in attrs
255 .into_iter()
256 .filter(|attr| !existing_names.contains(&attr.name))
257 {
258 self.set_attribute(node_id, attr.name, &attr.value);
259 }
260 }
261
262 pub fn set_attribute(&mut self, node_id: usize, name: QualName, value: &str) {
263 if self.doc.nodes.get(node_id).is_none() {
264 return;
265 }
266 self.doc.snapshot_node(node_id);
267
268 let node = &mut self.doc.nodes[node_id];
269 if let Some(data) = &mut *node.stylo_element_data.borrow_mut() {
270 data.hint |= RestyleHint::RESTYLE_SELF;
273 data.damage.insert(ALL_DAMAGE);
274 }
275
276 let parent = node.parent;
280 if let Some(parent_id) = parent {
281 let parent = &mut self.doc.nodes[parent_id];
282 if let Some(data) = &mut *parent.stylo_element_data.borrow_mut() {
283 data.hint |= RestyleHint::restyle_subtree();
284 }
285 }
286
287 self.doc.nodes[node_id].mark_ancestors_dirty();
291
292 let node = &mut self.doc.nodes[node_id];
293
294 let NodeData::Element(ref mut element) = node.data else {
295 return;
296 };
297
298 element.attrs.set(name.clone(), value);
299
300 let tag = &element.name.local;
301 let attr = &name.local;
302
303 if *attr == local_name!("id") {
304 element.id = Some(Atom::from(value))
305 }
306
307 if *attr == local_name!("value") {
308 if let Some(input_data) = element.text_input_data_mut() {
309 input_data.set_text(
311 &mut self.doc.font_ctx.lock().unwrap_or_else(|e| e.into_inner()),
312 &mut self.doc.layout_ctx,
313 value,
314 );
315 }
316 return;
317 }
318
319 if *attr == local_name!("style") {
320 element.flush_style_attribute(&self.doc.guard, &self.doc.url.url_extra_data());
321 return;
322 }
323
324 if *attr == local_name!("disabled") && element.can_be_disabled() {
325 node.disable();
326 return;
327 }
328
329 if !node.flags.is_in_document() {
332 return;
333 }
334
335 if (tag, attr) == tag_and_attr!("input", "checked") {
336 set_input_checked_state(element, value.to_string());
337 } else if (tag, attr) == tag_and_attr!("img", "src") {
338 self.load_image(node_id);
339 } else if (tag, attr) == tag_and_attr!("canvas", "src") {
340 self.load_custom_paint_src(node_id);
341 } else if (tag, attr) == tag_and_attr!("link", "href") {
342 self.load_linked_stylesheet(node_id);
343 }
344 }
345
346 pub fn clear_attribute(&mut self, node_id: usize, name: QualName) {
347 if self.doc.nodes.get(node_id).is_none() {
348 return;
349 }
350 self.doc.snapshot_node(node_id);
351
352 let node = &mut self.doc.nodes[node_id];
353
354 let mut stylo_element_data = node.stylo_element_data.borrow_mut();
355 if let Some(data) = &mut *stylo_element_data {
356 data.hint |= RestyleHint::RESTYLE_SELF;
358 data.damage.insert(ALL_DAMAGE);
359 }
360 drop(stylo_element_data);
361
362 node.mark_ancestors_dirty();
365
366 let Some(element) = node.element_data_mut() else {
367 return;
368 };
369
370 let removed_attr = element.attrs.remove(&name);
371 let had_attr = removed_attr.is_some();
372 if !had_attr {
373 return;
374 }
375
376 if name.local == local_name!("id") {
377 element.id = None;
378 }
379
380 if name.local == local_name!("value") {
382 if let Some(input_data) = element.text_input_data_mut() {
383 input_data.set_text(
384 &mut self.doc.font_ctx.lock().unwrap_or_else(|e| e.into_inner()),
385 &mut self.doc.layout_ctx,
386 "",
387 );
388 }
389 }
390
391 let tag = &element.name.local;
392 let attr = &name.local;
393
394 if *attr == local_name!("disabled") && element.can_be_disabled() {
395 node.enable();
396 return;
397 }
398
399 if *attr == local_name!("style") {
400 element.flush_style_attribute(&self.doc.guard, &self.doc.url.url_extra_data());
401 } else if (tag, attr) == tag_and_attr!("canvas", "src") {
402 self.recompute_is_animating = true;
403 } else if (tag, attr) == tag_and_attr!("link", "href") {
404 self.unload_stylesheet(node_id);
405 }
406 }
407
408 pub fn set_style_property(&mut self, node_id: usize, name: &str, value: &str) {
409 self.doc.set_style_property(node_id, name, value)
410 }
411
412 pub fn remove_style_property(&mut self, node_id: usize, name: &str) {
413 self.doc.remove_style_property(node_id, name)
414 }
415
416 pub fn set_sub_document(&mut self, node_id: usize, sub_document: Box<dyn Document>) {
417 self.doc.set_sub_document(node_id, sub_document)
418 }
419
420 pub fn remove_sub_document(&mut self, node_id: usize) {
421 self.doc.remove_sub_document(node_id)
422 }
423
424 pub fn remove_node(&mut self, node_id: usize) {
426 let Some(node) = self.doc.get_node_mut(node_id) else {
427 return;
428 };
429
430 if let Some(parent_id) = node.parent.take() {
432 if let Some(parent) = self.doc.get_node_mut(parent_id) {
433 parent.insert_damage(ALL_DAMAGE);
434 parent.mark_ancestors_dirty();
436 parent.children.retain(|id| *id != node_id);
437 self.maybe_record_node(parent_id);
438 } else {
439 debug_assert!(false, "remove_node: node {node_id} reports stale parent {parent_id}");
440 }
441 }
442
443 self.process_removed_subtree(node_id);
444 }
445
446 pub fn remove_and_drop_node(&mut self, node_id: usize) -> Option<Node> {
447 if self.doc.get_node(node_id).is_none() {
448 return None;
449 }
450 self.process_removed_subtree(node_id);
451
452 let node = self.doc.drop_node_ignoring_parent(node_id);
453
454 if let Some(parent_id) = node.as_ref().and_then(|node| node.parent) {
456 let Some(parent) = self.doc.get_node_mut(parent_id) else {
457 debug_assert!(false, "remove_and_drop_node: node {node_id} reports stale parent {parent_id}");
458 return node;
459 };
460 parent.insert_damage(ALL_DAMAGE);
461 let parent_is_in_doc = parent.flags.is_in_document();
462
463 if parent_is_in_doc {
468 if let Some(data) = &mut *parent.stylo_element_data.borrow_mut() {
469 data.hint |= RestyleHint::RESTYLE_SELF;
470 }
471 parent.mark_ancestors_dirty();
473 }
474
475 parent.children.retain(|id| *id != node_id);
476 self.maybe_record_node(parent_id);
477 }
478
479 node
480 }
481
482 pub fn remove_and_drop_all_children(&mut self, node_id: usize) {
483 let Some(parent) = self.doc.get_node_mut(node_id) else {
484 return;
485 };
486 let parent_is_in_doc = parent.flags.is_in_document();
487
488 if parent_is_in_doc {
490 if let Some(data) = &mut *parent.stylo_element_data.borrow_mut() {
491 data.hint |= RestyleHint::restyle_subtree();
492 }
493 parent.mark_ancestors_dirty();
495 }
496
497 let children = mem::take(&mut parent.children);
498 for child_id in children {
499 self.process_removed_subtree(child_id);
500 let _ = self.doc.drop_node_ignoring_parent(child_id);
501 }
502 self.maybe_record_node(node_id);
503 }
504
505 pub fn remove_node_if_unparented(&mut self, node_id: usize) {
507 if let Some(node) = self.doc.get_node(node_id) {
508 if node.parent.is_none() {
509 self.remove_and_drop_node(node_id);
510 }
511 }
512 }
513
514 pub fn append_children(&mut self, parent_id: usize, child_ids: &[usize]) {
516 self.add_children_to_parent(parent_id, child_ids, &|parent, child_ids| {
517 parent.children.extend_from_slice(child_ids);
518 });
519 }
520
521 pub fn insert_nodes_before(&mut self, anchor_node_id: usize, new_node_ids: &[usize]) {
522 let Some(parent_id) = self.doc.get_node(anchor_node_id).and_then(|n| n.parent) else {
523 return;
524 };
525 self.add_children_to_parent(parent_id, new_node_ids, &|parent, child_ids| {
526 let Some(node_child_idx) = parent.index_of_child(anchor_node_id) else {
530 parent.children.extend_from_slice(child_ids);
531 return;
532 };
533 parent
534 .children
535 .splice(node_child_idx..node_child_idx, child_ids.iter().copied());
536 });
537 }
538
539 fn add_children_to_parent(
540 &mut self,
541 parent_id: usize,
542 child_ids: &[usize],
543 insert_children_fn: &dyn Fn(&mut Node, &[usize]),
544 ) {
545 if self.doc.get_node(parent_id).is_none() {
549 debug_assert!(
550 false,
551 "add_children_to_parent called with stale parent_id={parent_id}"
552 );
553 return;
554 }
555 if !child_ids
556 .iter()
557 .all(|&id| self.doc.get_node(id).is_some())
558 {
559 debug_assert!(
560 false,
561 "add_children_to_parent: at least one child_id is stale; aborting"
562 );
563 return;
564 }
565 let new_parent = &mut self.doc.nodes[parent_id];
566 new_parent.insert_damage(ALL_DAMAGE);
567 let new_parent_is_in_doc = new_parent.flags.is_in_document();
568
569 if new_parent_is_in_doc {
571 if let Some(data) = &mut *new_parent.stylo_element_data.borrow_mut() {
572 data.hint |= RestyleHint::restyle_subtree();
573 }
574 new_parent.mark_ancestors_dirty();
576 }
577
578 insert_children_fn(new_parent, child_ids);
579
580 for child_id in child_ids.iter().copied() {
581 let Some(child) = self.doc.get_node_mut(child_id) else {
582 debug_assert!(false, "add_children_to_parent: stale child_id={child_id}");
583 continue;
584 };
585 let old_parent_id = child.parent.replace(parent_id);
586
587 let child_was_in_doc = child.flags.is_in_document();
588 if new_parent_is_in_doc != child_was_in_doc {
589 self.process_added_subtree(child_id);
590 }
591
592 if let Some(old_parent_id) = old_parent_id {
593 let Some(old_parent) = self.doc.get_node_mut(old_parent_id) else {
594 debug_assert!(false, "add_children_to_parent: stale old_parent_id={old_parent_id}");
595 continue;
596 };
597 old_parent.insert_damage(ALL_DAMAGE);
598
599 if child_was_in_doc {
602 if let Some(data) = &mut *old_parent.stylo_element_data.borrow_mut() {
603 data.hint |= RestyleHint::RESTYLE_SELF;
604 }
605 old_parent.mark_ancestors_dirty();
607 }
608
609 old_parent.children.retain(|id| *id != child_id);
610 self.maybe_record_node(old_parent_id);
611 }
612 }
613
614 self.maybe_record_node(parent_id);
615 }
616
617 pub fn insert_nodes_after(&mut self, anchor_node_id: usize, new_node_ids: &[usize]) {
619 match self.next_sibling_id(anchor_node_id) {
620 Some(id) => self.insert_nodes_before(id, new_node_ids),
621 None => match self.parent_id(anchor_node_id) {
622 Some(parent_id) => self.append_children(parent_id, new_node_ids),
623 None => {
624 debug_assert!(false, "insert_nodes_after called with orphan anchor_node_id={anchor_node_id}");
625 }
626 },
627 }
628 }
629
630 pub fn reparent_children(&mut self, old_parent_id: usize, new_parent_id: usize) {
631 let Some(old_parent) = self.doc.get_node_mut(old_parent_id) else {
632 return;
633 };
634 let child_ids = std::mem::take(&mut old_parent.children);
635 self.maybe_record_node(old_parent_id);
636 self.append_children(new_parent_id, &child_ids);
637 }
638
639 pub fn replace_node_with(&mut self, anchor_node_id: usize, new_node_ids: &[usize]) {
640 if self.doc.get_node(anchor_node_id).is_none() {
641 return;
642 }
643 self.insert_nodes_before(anchor_node_id, new_node_ids);
644 self.remove_node(anchor_node_id);
645 }
646}
647
648impl<'doc> DocumentMutator<'doc> {
649 pub fn flush(&mut self) {
650 if self.recompute_is_animating {
651 self.doc.has_canvas = self.doc.compute_has_canvas();
652 }
653
654 if let Some(id) = self.title_node {
655 if self.doc.get_node(id).is_none() {
656 debug_assert!(
657 false,
658 "flush: stale title_node={id} (removed before flush); clearing"
659 );
660 self.title_node = None;
661 } else if let Some(node) = self.doc.get_node(id) {
662 let title = node.text_content();
663 self.doc.shell_provider.set_window_title(title);
664 }
665 }
666
667 for id in self.style_nodes.drain() {
669 if self.doc.get_node(id).is_none() {
670 debug_assert!(
671 false,
672 "flush: stale style_node={id} (removed before flush); skipped"
673 );
674 continue;
675 }
676 self.doc.process_style_element(id);
677 }
678
679 for id in self.form_nodes.drain() {
680 if self.doc.get_node(id).is_none() {
681 debug_assert!(
682 false,
683 "flush: stale form_node={id} (removed before flush); skipped"
684 );
685 continue;
686 }
687 self.doc.reset_form_owner(id);
688 }
689
690 #[cfg(feature = "autofocus")]
691 if let Some(node_id) = self.node_to_autofocus.take() {
692 if self.doc.get_node(node_id).is_some() {
693 self.doc.set_focus_to(node_id);
694 }
695 }
696 }
697
698 pub fn set_inner_html(&mut self, node_id: usize, html: &str) {
699 if self.doc.get_node(node_id).is_none() {
700 return;
701 }
702 self.remove_and_drop_all_children(node_id);
703 self.doc
704 .html_parser_provider
705 .clone()
706 .parse_inner_html(self, node_id, html);
707 }
708
709 fn flush_eager_ops(&mut self) {
710 let mut ops = mem::take(&mut self.eager_op_queue);
711 for op in ops.drain(0..) {
712 match op {
713 SpecialOp::LoadImage(node_id) => self.load_image(node_id),
714 SpecialOp::LoadStylesheet(node_id) => self.load_linked_stylesheet(node_id),
715 SpecialOp::UnloadStylesheet(node_id) => self.unload_stylesheet(node_id),
716 SpecialOp::LoadCustomPaintSource(node_id) => self.load_custom_paint_src(node_id),
717 SpecialOp::ProcessButtonInput(node_id) => self.process_button_input(node_id),
718 }
719 }
720
721 self.eager_op_queue = ops;
723 }
724
725 fn process_added_subtree(&mut self, node_id: usize) {
726 self.doc.iter_subtree_mut(node_id, |node_id, doc| {
727 let node = &mut doc.nodes[node_id];
728 node.flags.set(NodeFlags::IS_IN_DOCUMENT, true);
729 node.insert_damage(ALL_DAMAGE);
730
731 if let Some(id_attr) = node.attr(local_name!("id")) {
733 doc.nodes_to_id.insert(id_attr.to_string(), node_id);
734 }
735
736 let NodeData::Element(ref mut element) = node.data else {
737 return;
738 };
739
740 let tag = element.name.local.as_ref();
742 match tag {
743 "title" => self.title_node = Some(node_id),
744 "link" => self.eager_op_queue.push(SpecialOp::LoadStylesheet(node_id)),
745 "img" => self.eager_op_queue.push(SpecialOp::LoadImage(node_id)),
746 "canvas" => self
747 .eager_op_queue
748 .push(SpecialOp::LoadCustomPaintSource(node_id)),
749 "style" => {
750 self.style_nodes.insert(node_id);
751 }
752 "button" | "fieldset" | "input" | "select" | "textarea" | "object" | "output" => {
753 self.eager_op_queue
754 .push(SpecialOp::ProcessButtonInput(node_id));
755 self.form_nodes.insert(node_id);
756 }
757 _ => {}
758 }
759
760 #[cfg(feature = "autofocus")]
761 if node.is_focussable() {
762 if let NodeData::Element(ref element) = node.data {
763 if let Some(value) = element.attr(local_name!("autofocus")) {
764 if value == "true" {
765 self.node_to_autofocus = Some(node_id);
766 }
767 }
768 }
769 }
770 });
771
772 self.flush_eager_ops();
773 }
774
775 fn process_removed_subtree(&mut self, node_id: usize) {
776 self.doc.iter_subtree_mut(node_id, |node_id, doc| {
777 let node = &mut doc.nodes[node_id];
778 node.flags.set(NodeFlags::IS_IN_DOCUMENT, false);
779
780 if let Some(id_attr) = node.attr(local_name!("id")) {
782 doc.nodes_to_id.remove(id_attr);
783 }
784
785 let NodeData::Element(ref mut element) = node.data else {
786 return;
787 };
788
789 match &element.special_data {
790 SpecialElementData::SubDocument(_) => {}
791 SpecialElementData::Stylesheet(_) => self
792 .eager_op_queue
793 .push(SpecialOp::UnloadStylesheet(node_id)),
794 SpecialElementData::Image(_) => {}
795 SpecialElementData::Canvas(_) => {
796 self.recompute_is_animating = true;
797 }
798 SpecialElementData::TableRoot(_) => {}
799 SpecialElementData::TextInput(_) => {}
800 SpecialElementData::CheckboxInput(_) => {}
801 #[cfg(feature = "file_input")]
802 SpecialElementData::FileInput(_) => {}
803 SpecialElementData::None => {}
804 }
805 });
806
807 self.flush_eager_ops();
808 }
809
810 fn maybe_record_node(&mut self, node_id: impl Into<Option<usize>>) {
811 let Some(node_id) = node_id.into() else {
812 return;
813 };
814
815 let Some(node) = self.doc.get_node(node_id) else {
816 debug_assert!(false, "maybe_record_node called with stale node_id={node_id}");
817 return;
818 };
819 let Some(tag_name) = node.data.downcast_element().map(|elem| &elem.name.local) else {
820 return;
821 };
822
823 match tag_name.as_ref() {
824 "title" => self.title_node = Some(node_id),
825 "style" => {
826 self.style_nodes.insert(node_id);
827 }
828 _ => {}
829 }
830 }
831
832 fn load_linked_stylesheet(&mut self, target_id: usize) {
833 let Some(node) = self.doc.get_node(target_id) else {
834 debug_assert!(false, "load_linked_stylesheet: stale target_id={target_id}");
835 return;
836 };
837
838 let rel_attr = node.attr(local_name!("rel"));
839 let href_attr = node.attr(local_name!("href"));
840
841 let (Some(rels), Some(href)) = (rel_attr, href_attr) else {
842 return;
843 };
844 if !rels.split_ascii_whitespace().any(|rel| rel == "stylesheet") {
845 return;
846 }
847
848 let Some(url) = self.doc.resolve_url(href) else {
849 return;
850 };
851 self.doc.net_provider.fetch(
852 self.doc.id(),
853 Request::get(url.clone()),
854 ResourceHandler::boxed(
855 self.doc.tx.clone(),
856 self.doc.id(),
857 Some(node.id),
858 self.doc.shell_provider.clone(),
859 StylesheetHandler {
860 source_url: url,
861 guard: self.doc.guard.clone(),
862 net_provider: self.doc.net_provider.clone(),
863 },
864 ),
865 );
866 }
867
868 fn unload_stylesheet(&mut self, node_id: usize) {
869 let Some(node) = self.doc.get_node_mut(node_id) else {
870 debug_assert!(false, "unload_stylesheet: stale node_id={node_id}");
871 return;
872 };
873 let Some(element) = node.element_data_mut() else {
874 debug_assert!(false, "unload_stylesheet: node {node_id} is not an element");
875 return;
876 };
877 let SpecialElementData::Stylesheet(stylesheet) = element.special_data.take() else {
878 debug_assert!(false, "unload_stylesheet: node {node_id} carries non-Stylesheet special_data");
879 return;
880 };
881
882 let guard = self.doc.guard.read();
883 self.doc.stylist.remove_stylesheet(stylesheet, &guard);
884 self.doc
885 .stylist
886 .force_stylesheet_origins_dirty(OriginSet::all());
887
888 self.doc.nodes_to_stylesheet.remove(&node_id);
889 }
890
891 fn load_image(&mut self, target_id: usize) {
892 let Some(node) = self.doc.get_node(target_id) else {
893 debug_assert!(false, "load_image: stale target_id={target_id}");
894 return;
895 };
896 if let Some(raw_src) = node.attr(local_name!("src")) {
897 if !raw_src.is_empty() {
898 let Some(src) = self.doc.resolve_url(raw_src) else {
899 return;
900 };
901 let src_string = src.as_str();
902
903 if let Some(cached_image) = self.doc.image_cache.get(src_string).cloned() {
906 #[cfg(feature = "tracing")]
907 tracing::info!("Loading image {src_string} from cache");
908 if let Some(node) = self.doc.get_node_mut(target_id) {
909 if let Some(element_data) = node.element_data_mut() {
910 element_data.special_data =
911 SpecialElementData::Image(Box::new(cached_image));
912 } else {
913 debug_assert!(false, "load_image (cache): node {target_id} not element");
914 return;
915 }
916 node.cache.clear();
917 node.insert_damage(ALL_DAMAGE);
918 }
919 return;
920 }
921
922 if let Some(waiting_list) = self.doc.pending_images.get_mut(src_string) {
924 #[cfg(feature = "tracing")]
925 tracing::info!("Image {src_string} already pending, queueing node {target_id}");
926 waiting_list.push((target_id, ImageType::Image));
927 return;
928 }
929
930 #[cfg(feature = "tracing")]
932 tracing::info!("Fetching image {src_string}");
933 self.doc
934 .pending_images
935 .insert(src_string.to_string(), vec![(target_id, ImageType::Image)]);
936
937 self.doc.net_provider.fetch(
938 self.doc.id(),
939 Request::get(src),
940 ResourceHandler::boxed(
941 self.doc.tx.clone(),
942 self.doc.id(),
943 None, self.doc.shell_provider.clone(),
945 ImageHandler::new(ImageType::Image),
946 ),
947 );
948 }
949 }
950 }
951
952 fn load_custom_paint_src(&mut self, target_id: usize) {
953 let Some(node) = self.doc.get_node_mut(target_id) else {
954 debug_assert!(false, "load_custom_paint_src: stale target_id={target_id}");
955 return;
956 };
957 if let Some(raw_src) = node.attr(local_name!("src")) {
958 if let Ok(custom_paint_source_id) = raw_src.parse::<u64>() {
959 self.recompute_is_animating = true;
960 let canvas_data = SpecialElementData::Canvas(CanvasData {
961 custom_paint_source_id,
962 });
963 if let Some(element_data) = node.element_data_mut() {
964 element_data.special_data = canvas_data;
965 } else {
966 debug_assert!(false, "load_custom_paint_src: node {target_id} not element");
967 }
968 }
969 }
970 }
971
972 fn process_button_input(&mut self, target_id: usize) {
973 let Some(node) = self.doc.get_node(target_id) else {
974 debug_assert!(false, "process_button_input: stale target_id={target_id}");
975 return;
976 };
977 let Some(data) = node.element_data() else {
978 return;
979 };
980
981 let tagname = data.name.local.as_ref();
982 let type_attr = data.attr(local_name!("type"));
983 let value = data.attr(local_name!("value"));
984
985 if let ("input", Some("button" | "submit" | "reset"), Some(value)) =
988 (tagname, type_attr, value)
989 {
990 let value = value.to_string();
991 let id = self.create_text_node(&value);
992 self.append_children(target_id, &[id]);
993 }
994 #[cfg(feature = "file_input")]
995 if let ("input", Some("file")) = (tagname, type_attr) {
996 let button_id = self.create_element(
997 qual_name!("button", html),
998 vec![
999 Attribute {
1000 name: qual_name!("type", html),
1001 value: "button".to_string(),
1002 },
1003 Attribute {
1004 name: qual_name!("tabindex", html),
1005 value: "-1".to_string(),
1006 },
1007 ],
1008 );
1009 let label_id = self.create_element(qual_name!("label", html), vec![]);
1010 let text_id = self.create_text_node("No File Selected");
1011 let button_text_id = self.create_text_node("Browse");
1012 self.append_children(target_id, &[button_id, label_id]);
1013 self.append_children(label_id, &[text_id]);
1014 self.append_children(button_id, &[button_text_id]);
1015 }
1016 }
1017}
1018
1019fn set_input_checked_state(element: &mut ElementData, value: String) {
1021 let Ok(checked) = value.parse() else {
1022 return;
1023 };
1024 match element.special_data {
1025 SpecialElementData::CheckboxInput(ref mut checked_mut) => *checked_mut = checked,
1026 SpecialElementData::None => element.attrs.push(Attribute {
1031 name: qual_name!("checked", html),
1032 value: checked.to_string(),
1033 }),
1034 _ => {}
1035 }
1036}
1037
1038pub struct ViewportMut<'doc> {
1041 doc: &'doc mut BaseDocument,
1042 initial_viewport: Viewport,
1043}
1044impl ViewportMut<'_> {
1045 pub fn new(doc: &mut BaseDocument) -> ViewportMut<'_> {
1046 let initial_viewport = doc.viewport.clone();
1047 ViewportMut {
1048 doc,
1049 initial_viewport,
1050 }
1051 }
1052}
1053impl Deref for ViewportMut<'_> {
1054 type Target = Viewport;
1055
1056 fn deref(&self) -> &Self::Target {
1057 &self.doc.viewport
1058 }
1059}
1060impl DerefMut for ViewportMut<'_> {
1061 fn deref_mut(&mut self) -> &mut Self::Target {
1062 &mut self.doc.viewport
1063 }
1064}
1065impl Drop for ViewportMut<'_> {
1066 fn drop(&mut self) {
1067 if self.doc.viewport == self.initial_viewport {
1068 return;
1069 }
1070
1071 self.doc
1072 .set_stylist_device(make_device(&self.doc.viewport, self.doc.font_ctx.clone()));
1073 self.doc.scroll_viewport_by(0.0, 0.0); let scale_has_changed =
1076 self.doc.viewport().scale_f64() != self.initial_viewport.scale_f64();
1077 if scale_has_changed {
1078 self.doc.invalidate_inline_contexts();
1079 self.doc.shell_provider.request_redraw();
1080 }
1081 }
1082}
1083
1084#[cfg(test)]
1085mod test {
1086 use style_dom::ElementState;
1087
1088 use crate::{Attribute, BaseDocument, DocumentConfig, ElementData, NodeData, qual_name};
1089
1090 #[test]
1091 fn mutator_remove_disabled() {
1092 let mut document = BaseDocument::new(DocumentConfig::default());
1093 let id = document.create_node(NodeData::Element(ElementData::new(
1094 qual_name!("button"),
1095 vec![Attribute {
1096 name: qual_name!("disabled"),
1097 value: "".into(),
1098 }],
1099 )));
1100
1101 let node = document.get_node(id).unwrap();
1102 assert!(
1103 node.element_state.contains(ElementState::DISABLED),
1104 "form node is disabled"
1105 );
1106 assert!(
1107 !node.element_state.contains(ElementState::ENABLED),
1108 "form node is not enabled yet"
1109 );
1110
1111 let mut mutator = document.mutate();
1112 mutator.clear_attribute(id, qual_name!("disabled"));
1113 drop(mutator);
1114
1115 let node = document.get_node(id).unwrap();
1116 assert!(
1117 !node.element_state.contains(ElementState::DISABLED),
1118 "form node is no longer disabled"
1119 );
1120 assert!(
1121 node.element_state.contains(ElementState::ENABLED),
1122 "form node is enabled"
1123 );
1124 }
1125
1126 #[test]
1127 fn mutator_set_disabled() {
1128 let mut document = BaseDocument::new(DocumentConfig::default());
1129 let id = document.create_node(NodeData::Element(ElementData::new(
1130 qual_name!("button"),
1131 vec![],
1132 )));
1133
1134 let node = document.get_node(id).unwrap();
1135 assert!(
1136 !node.element_state.contains(ElementState::DISABLED),
1137 "form node is not disabled"
1138 );
1139 assert!(
1140 node.element_state.contains(ElementState::ENABLED),
1141 "form node is enabled"
1142 );
1143
1144 let mut mutator = document.mutate();
1145 mutator.set_attribute(id, qual_name!("disabled"), "");
1146 drop(mutator);
1147
1148 let node = document.get_node(id).unwrap();
1149
1150 assert!(
1151 node.element_state.contains(ElementState::DISABLED),
1152 "form node is disabled"
1153 );
1154 assert!(
1155 !node.element_state.contains(ElementState::ENABLED),
1156 "form node is no longer enabled enabled"
1157 );
1158 }
1159
1160 #[test]
1161 fn mutator_set_disabled_invalid_node() {
1162 let mut document = BaseDocument::new(DocumentConfig::default());
1163 let id = document.create_node(NodeData::Element(ElementData::new(qual_name!("a"), vec![])));
1164
1165 let node = document.get_node(id).unwrap();
1166 assert!(
1167 !node.element_state.contains(ElementState::DISABLED),
1168 "form node is not disabled"
1169 );
1170 assert!(
1171 !node.element_state.contains(ElementState::ENABLED),
1172 "form node is enabled"
1173 );
1174
1175 let mut mutator = document.mutate();
1176 mutator.set_attribute(id, qual_name!("disabled"), "");
1177 drop(mutator);
1178
1179 let node = document.get_node(id).unwrap();
1180 assert!(
1181 !node.element_state.contains(ElementState::DISABLED),
1182 "form node is not disabled"
1183 );
1184 assert!(
1185 !node.element_state.contains(ElementState::ENABLED),
1186 "form node is enabled"
1187 );
1188 }
1189}