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 blitz_traits::net::Request;
14use blitz_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.nodes[node_id].parent.is_some()
85 }
86
87 pub fn previous_sibling_id(&self, node_id: usize) -> Option<usize> {
88 self.doc.nodes[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.nodes[node_id].forward(1).map(|node| node.id)
93 }
94
95 pub fn parent_id(&self, node_id: usize) -> Option<usize> {
96 self.doc.nodes[node_id].parent
97 }
98
99 pub fn last_child_id(&self, node_id: usize) -> Option<usize> {
100 self.doc.nodes[node_id].children.last().copied()
101 }
102
103 pub fn child_ids(&self, node_id: usize) -> Vec<usize> {
104 self.doc.nodes[node_id].children.clone()
105 }
106
107 pub fn element_name(&self, node_id: usize) -> Option<&QualName> {
108 self.doc.nodes[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 mut current = &self.doc.nodes[start_node_id];
113 for i in path {
114 let new_id = current.children[*i as usize];
115 current = &self.doc.nodes[new_id];
116 }
117 current.id
118 }
119
120 pub fn create_comment_node(&mut self) -> usize {
123 self.doc.create_node(NodeData::Comment)
124 }
125
126 pub fn create_text_node(&mut self, text: &str) -> usize {
127 self.doc.create_text_node(text)
128 }
129
130 pub fn create_element(&mut self, name: QualName, attrs: Vec<Attribute>) -> usize {
131 let mut data = ElementData::new(name, attrs);
132 data.flush_style_attribute(self.doc.guard(), &self.doc.url.url_extra_data());
133
134 let id = self.doc.create_node(NodeData::Element(data));
135 let node = self.doc.get_node_mut(id).unwrap();
136
137 *node.stylo_element_data.ensure_init_mut() = style::data::ElementData {
139 damage: ALL_DAMAGE,
140 ..Default::default()
141 };
142
143 id
144 }
145
146 pub fn deep_clone_node(&mut self, node_id: usize) -> usize {
147 self.doc.deep_clone_node(node_id)
148 }
149
150 pub fn set_node_text(&mut self, node_id: usize, value: &str) {
153 let node = &mut self.doc.nodes[node_id];
154
155 let text = match node.data {
156 NodeData::Text(ref mut text) => text,
157 _ => return,
159 };
160
161 let changed = text.content != value;
162 if changed {
163 text.content.clear();
164 text.content.push_str(value);
165 node.insert_damage(ALL_DAMAGE);
166 node.mark_ancestors_dirty();
169 let parent_id = node.parent;
170
171 if let Some(parent_id) = parent_id {
174 let parent = &mut self.doc.nodes[parent_id];
175 parent.insert_damage(ALL_DAMAGE);
176 }
177
178 self.maybe_record_node(parent_id);
179 }
180 }
181
182 pub fn append_text_to_node(&mut self, node_id: usize, text: &str) -> Result<(), AppendTextErr> {
183 let node = &mut self.doc.nodes[node_id];
184 node.insert_damage(ALL_DAMAGE);
185 node.mark_ancestors_dirty();
186 match node.text_data_mut() {
187 Some(data) => {
188 data.content += text;
189 Ok(())
190 }
191 None => Err(AppendTextErr::NotTextNode),
192 }
193 }
194
195 pub fn add_attrs_if_missing(&mut self, node_id: usize, attrs: Vec<Attribute>) {
196 let node = &mut self.doc.nodes[node_id];
197 node.insert_damage(ALL_DAMAGE);
198 let element_data = node.element_data_mut().expect("Not an element");
199
200 let existing_names = element_data
201 .attrs
202 .iter()
203 .map(|e| e.name.clone())
204 .collect::<HashSet<_>>();
205
206 for attr in attrs
207 .into_iter()
208 .filter(|attr| !existing_names.contains(&attr.name))
209 {
210 self.set_attribute(node_id, attr.name, &attr.value);
211 }
212 }
213
214 pub fn set_attribute(&mut self, node_id: usize, name: QualName, value: &str) {
215 self.doc.snapshot_node(node_id);
216
217 let node = &mut self.doc.nodes[node_id];
218 if let Some(mut data) = node.stylo_element_data.get_mut() {
219 data.hint |= RestyleHint::restyle_subtree();
220 data.damage.insert(ALL_DAMAGE);
221 }
222
223 let parent = node.parent;
225 if let Some(parent_id) = parent {
226 let parent = &mut self.doc.nodes[parent_id];
227 if let Some(mut data) = parent.stylo_element_data.get_mut() {
228 data.hint |= RestyleHint::restyle_subtree();
229 }
230 }
231
232 self.doc.nodes[node_id].mark_ancestors_dirty();
236
237 let node = &mut self.doc.nodes[node_id];
238
239 let NodeData::Element(ref mut element) = node.data else {
240 return;
241 };
242
243 element.attrs.set(name.clone(), value);
244
245 let tag = &element.name.local;
246 let attr = &name.local;
247
248 if *attr == local_name!("id") {
249 element.id = Some(Atom::from(value))
250 }
251
252 if *attr == local_name!("value") {
253 if let Some(input_data) = element.text_input_data_mut() {
254 input_data.set_text(
256 &mut self.doc.font_ctx.lock().unwrap(),
257 &mut self.doc.layout_ctx,
258 value,
259 );
260 }
261 return;
262 }
263
264 if *attr == local_name!("style") {
265 element.flush_style_attribute(&self.doc.guard, &self.doc.url.url_extra_data());
266 node.mark_style_attr_updated();
267 return;
268 }
269
270 if *attr == local_name!("disabled") && element.can_be_disabled() {
271 node.disable();
272 return;
273 }
274
275 if !node.flags.is_in_document() {
278 return;
279 }
280
281 if (tag, attr) == tag_and_attr!("input", "checked") {
282 set_input_checked_state(element, value.to_string());
283 } else if (tag, attr) == tag_and_attr!("img", "src") {
284 self.load_image(node_id);
285 } else if (tag, attr) == tag_and_attr!("canvas", "src") {
286 self.load_custom_paint_src(node_id);
287 } else if (tag, attr) == tag_and_attr!("link", "href") {
288 self.load_linked_stylesheet(node_id);
289 }
290 }
291
292 pub fn clear_attribute(&mut self, node_id: usize, name: QualName) {
293 self.doc.snapshot_node(node_id);
294
295 let node = &mut self.doc.nodes[node_id];
296
297 if let Some(mut data) = node.stylo_element_data.get_mut() {
298 data.hint |= RestyleHint::restyle_subtree();
299 data.damage.insert(ALL_DAMAGE);
300 }
301
302 node.mark_ancestors_dirty();
305
306 let Some(element) = node.element_data_mut() else {
307 return;
308 };
309
310 let removed_attr = element.attrs.remove(&name);
311 let had_attr = removed_attr.is_some();
312 if !had_attr {
313 return;
314 }
315
316 if name.local == local_name!("id") {
317 element.id = None;
318 }
319
320 if name.local == local_name!("value") {
322 if let Some(input_data) = element.text_input_data_mut() {
323 input_data.set_text(
324 &mut self.doc.font_ctx.lock().unwrap(),
325 &mut self.doc.layout_ctx,
326 "",
327 );
328 }
329 }
330
331 let tag = &element.name.local;
332 let attr = &name.local;
333
334 if *attr == local_name!("disabled") && element.can_be_disabled() {
335 node.enable();
336 return;
337 }
338
339 if *attr == local_name!("style") {
340 element.flush_style_attribute(&self.doc.guard, &self.doc.url.url_extra_data());
341 node.mark_style_attr_updated();
342 } else if (tag, attr) == tag_and_attr!("canvas", "src") {
343 self.recompute_is_animating = true;
344 } else if (tag, attr) == tag_and_attr!("link", "href") {
345 self.unload_stylesheet(node_id);
346 }
347 }
348
349 pub fn set_style_property(&mut self, node_id: usize, name: &str, value: &str) {
350 self.doc.set_style_property(node_id, name, value)
351 }
352
353 pub fn remove_style_property(&mut self, node_id: usize, name: &str) {
354 self.doc.remove_style_property(node_id, name)
355 }
356
357 pub fn set_sub_document(&mut self, node_id: usize, sub_document: Box<dyn Document>) {
358 self.doc.set_sub_document(node_id, sub_document)
359 }
360
361 pub fn remove_sub_document(&mut self, node_id: usize) {
362 self.doc.remove_sub_document(node_id)
363 }
364
365 pub fn remove_node(&mut self, node_id: usize) {
367 let node = &mut self.doc.nodes[node_id];
368
369 if let Some(parent_id) = node.parent.take() {
371 let parent = &mut self.doc.nodes[parent_id];
372 parent.insert_damage(ALL_DAMAGE);
373 parent.mark_ancestors_dirty();
375 parent.children.retain(|id| *id != node_id);
376 self.maybe_record_node(parent_id);
377 }
378
379 self.process_removed_subtree(node_id);
380 }
381
382 pub fn remove_and_drop_node(&mut self, node_id: usize) -> Option<Node> {
383 self.process_removed_subtree(node_id);
384
385 let node = self.doc.drop_node_ignoring_parent(node_id);
386
387 if let Some(parent_id) = node.as_ref().and_then(|node| node.parent) {
389 let parent = &mut self.doc.nodes[parent_id];
390 parent.insert_damage(ALL_DAMAGE);
391 let parent_is_in_doc = parent.flags.is_in_document();
392
393 if parent_is_in_doc {
395 if let Some(mut data) = parent.stylo_element_data.get_mut() {
396 data.hint |= RestyleHint::restyle_subtree();
397 }
398 parent.mark_ancestors_dirty();
400 }
401
402 parent.children.retain(|id| *id != node_id);
403 self.maybe_record_node(parent_id);
404 }
405
406 node
407 }
408
409 pub fn remove_and_drop_all_children(&mut self, node_id: usize) {
410 let parent = &mut self.doc.nodes[node_id];
411 let parent_is_in_doc = parent.flags.is_in_document();
412
413 if parent_is_in_doc {
415 if let Some(mut data) = parent.stylo_element_data.get_mut() {
416 data.hint |= RestyleHint::restyle_subtree();
417 }
418 parent.mark_ancestors_dirty();
420 }
421
422 let children = mem::take(&mut parent.children);
423 for child_id in children {
424 self.process_removed_subtree(child_id);
425 let _ = self.doc.drop_node_ignoring_parent(child_id);
426 }
427 self.maybe_record_node(node_id);
428 }
429
430 pub fn remove_node_if_unparented(&mut self, node_id: usize) {
432 if let Some(node) = self.doc.get_node(node_id) {
433 if node.parent.is_none() {
434 self.remove_and_drop_node(node_id);
435 }
436 }
437 }
438
439 pub fn append_children(&mut self, parent_id: usize, child_ids: &[usize]) {
441 self.add_children_to_parent(parent_id, child_ids, &|parent, child_ids| {
442 parent.children.extend_from_slice(child_ids);
443 });
444 }
445
446 pub fn insert_nodes_before(&mut self, anchor_node_id: usize, new_node_ids: &[usize]) {
447 let parent_id = self.doc.nodes[anchor_node_id].parent.unwrap();
448 self.add_children_to_parent(parent_id, new_node_ids, &|parent, child_ids| {
449 let node_child_idx = parent.index_of_child(anchor_node_id).unwrap();
450 parent
451 .children
452 .splice(node_child_idx..node_child_idx, child_ids.iter().copied());
453 });
454 }
455
456 fn add_children_to_parent(
457 &mut self,
458 parent_id: usize,
459 child_ids: &[usize],
460 insert_children_fn: &dyn Fn(&mut Node, &[usize]),
461 ) {
462 let new_parent = &mut self.doc.nodes[parent_id];
463 new_parent.insert_damage(ALL_DAMAGE);
464 let new_parent_is_in_doc = new_parent.flags.is_in_document();
465
466 if new_parent_is_in_doc {
468 if let Some(mut data) = new_parent.stylo_element_data.get_mut() {
469 data.hint |= RestyleHint::restyle_subtree();
470 }
471 new_parent.mark_ancestors_dirty();
473 }
474
475 insert_children_fn(new_parent, child_ids);
476
477 for child_id in child_ids.iter().copied() {
478 let child = &mut self.doc.nodes[child_id];
479 let old_parent_id = child.parent.replace(parent_id);
480
481 let child_was_in_doc = child.flags.is_in_document();
482 if new_parent_is_in_doc != child_was_in_doc {
483 self.process_added_subtree(child_id);
484 }
485
486 if let Some(old_parent_id) = old_parent_id {
487 let old_parent = &mut self.doc.nodes[old_parent_id];
488 old_parent.insert_damage(ALL_DAMAGE);
489
490 if child_was_in_doc {
492 if let Some(mut data) = old_parent.stylo_element_data.get_mut() {
493 data.hint |= RestyleHint::restyle_subtree();
494 }
495 old_parent.mark_ancestors_dirty();
497 }
498
499 old_parent.children.retain(|id| *id != child_id);
500 self.maybe_record_node(old_parent_id);
501 }
502 }
503
504 self.maybe_record_node(parent_id);
505 }
506
507 pub fn insert_nodes_after(&mut self, anchor_node_id: usize, new_node_ids: &[usize]) {
509 match self.next_sibling_id(anchor_node_id) {
510 Some(id) => self.insert_nodes_before(id, new_node_ids),
511 None => {
512 let parent_id = self.parent_id(anchor_node_id).unwrap();
513 self.append_children(parent_id, new_node_ids)
514 }
515 }
516 }
517
518 pub fn reparent_children(&mut self, old_parent_id: usize, new_parent_id: usize) {
519 let child_ids = std::mem::take(&mut self.doc.nodes[old_parent_id].children);
520 self.maybe_record_node(old_parent_id);
521 self.append_children(new_parent_id, &child_ids);
522 }
523
524 pub fn replace_node_with(&mut self, anchor_node_id: usize, new_node_ids: &[usize]) {
525 self.insert_nodes_before(anchor_node_id, new_node_ids);
526 self.remove_node(anchor_node_id);
527 }
528}
529
530impl<'doc> DocumentMutator<'doc> {
531 pub fn flush(&mut self) {
532 if self.recompute_is_animating {
533 self.doc.has_canvas = self.doc.compute_has_canvas();
534 }
535
536 if let Some(id) = self.title_node {
537 let title = self.doc.nodes[id].text_content();
538 self.doc.shell_provider.set_window_title(title);
539 }
540
541 for id in self.style_nodes.drain() {
543 self.doc.process_style_element(id);
544 }
545
546 for id in self.form_nodes.drain() {
547 self.doc.reset_form_owner(id);
548 }
549
550 #[cfg(feature = "autofocus")]
551 if let Some(node_id) = self.node_to_autofocus.take() {
552 if self.doc.get_node(node_id).is_some() {
553 self.doc.set_focus_to(node_id);
554 }
555 }
556 }
557
558 pub fn set_inner_html(&mut self, node_id: usize, html: &str) {
559 self.remove_and_drop_all_children(node_id);
560 self.doc
561 .html_parser_provider
562 .clone()
563 .parse_inner_html(self, node_id, html);
564 }
565
566 fn flush_eager_ops(&mut self) {
567 let mut ops = mem::take(&mut self.eager_op_queue);
568 for op in ops.drain(0..) {
569 match op {
570 SpecialOp::LoadImage(node_id) => self.load_image(node_id),
571 SpecialOp::LoadStylesheet(node_id) => self.load_linked_stylesheet(node_id),
572 SpecialOp::UnloadStylesheet(node_id) => self.unload_stylesheet(node_id),
573 SpecialOp::LoadCustomPaintSource(node_id) => self.load_custom_paint_src(node_id),
574 SpecialOp::ProcessButtonInput(node_id) => self.process_button_input(node_id),
575 }
576 }
577
578 self.eager_op_queue = ops;
580 }
581
582 fn process_added_subtree(&mut self, node_id: usize) {
583 self.doc.iter_subtree_mut(node_id, |node_id, doc| {
584 let node = &mut doc.nodes[node_id];
585 node.flags.set(NodeFlags::IS_IN_DOCUMENT, true);
586 node.insert_damage(ALL_DAMAGE);
587
588 if let Some(id_attr) = node.attr(local_name!("id")) {
590 doc.nodes_to_id.insert(id_attr.to_string(), node_id);
591 }
592
593 let NodeData::Element(ref mut element) = node.data else {
594 return;
595 };
596
597 let tag = element.name.local.as_ref();
599 match tag {
600 "title" => self.title_node = Some(node_id),
601 "link" => self.eager_op_queue.push(SpecialOp::LoadStylesheet(node_id)),
602 "img" => self.eager_op_queue.push(SpecialOp::LoadImage(node_id)),
603 "canvas" => self
604 .eager_op_queue
605 .push(SpecialOp::LoadCustomPaintSource(node_id)),
606 "style" => {
607 self.style_nodes.insert(node_id);
608 }
609 "button" | "fieldset" | "input" | "select" | "textarea" | "object" | "output" => {
610 self.eager_op_queue
611 .push(SpecialOp::ProcessButtonInput(node_id));
612 self.form_nodes.insert(node_id);
613 }
614 _ => {}
615 }
616
617 #[cfg(feature = "autofocus")]
618 if node.is_focussable() {
619 if let NodeData::Element(ref element) = node.data {
620 if let Some(value) = element.attr(local_name!("autofocus")) {
621 if value == "true" {
622 self.node_to_autofocus = Some(node_id);
623 }
624 }
625 }
626 }
627 });
628
629 self.flush_eager_ops();
630 }
631
632 fn process_removed_subtree(&mut self, node_id: usize) {
633 self.doc.iter_subtree_mut(node_id, |node_id, doc| {
634 let node = &mut doc.nodes[node_id];
635 node.flags.set(NodeFlags::IS_IN_DOCUMENT, false);
636
637 if doc.hover_node_id == Some(node_id) {
640 doc.hover_node_id = None;
641 doc.hover_node_is_text = false;
642 }
643
644 if doc.active_node_id == Some(node_id) {
647 doc.active_node_id = None;
648 }
649
650 if node.has_snapshot {
653 let opaque_id = style::dom::TNode::opaque(&&*node);
654 doc.snapshots.remove(&opaque_id);
655 node.has_snapshot = false;
656 }
657
658 if let Some(id_attr) = node.attr(local_name!("id")) {
660 doc.nodes_to_id.remove(id_attr);
661 }
662
663 let NodeData::Element(ref mut element) = node.data else {
664 return;
665 };
666
667 match &element.special_data {
668 SpecialElementData::SubDocument(_) => {}
669 SpecialElementData::Stylesheet(_) => self
670 .eager_op_queue
671 .push(SpecialOp::UnloadStylesheet(node_id)),
672 SpecialElementData::Image(_) => {}
673 SpecialElementData::Canvas(_) => {
674 self.recompute_is_animating = true;
675 }
676 SpecialElementData::TableRoot(_) => {}
677 SpecialElementData::TextInput(_) => {}
678 SpecialElementData::CheckboxInput(_) => {}
679 #[cfg(feature = "file_input")]
680 SpecialElementData::FileInput(_) => {}
681 SpecialElementData::None => {}
682 }
683 });
684
685 self.flush_eager_ops();
686 }
687
688 fn maybe_record_node(&mut self, node_id: impl Into<Option<usize>>) {
689 let Some(node_id) = node_id.into() else {
690 return;
691 };
692
693 let Some(tag_name) = self.doc.nodes[node_id]
694 .data
695 .downcast_element()
696 .map(|elem| &elem.name.local)
697 else {
698 return;
699 };
700
701 match tag_name.as_ref() {
702 "title" => self.title_node = Some(node_id),
703 "style" => {
704 self.style_nodes.insert(node_id);
705 }
706 _ => {}
707 }
708 }
709
710 fn load_linked_stylesheet(&mut self, target_id: usize) {
711 let node = &self.doc.nodes[target_id];
712
713 let mut is_in_head = false;
714 let mut parent_id = node.parent;
715 while let Some(id) = parent_id
716 && !is_in_head
717 {
718 let parent = &self.doc.nodes[id];
719 is_in_head |= parent.data.is_element_with_tag_name(&local_name!("head"));
720 parent_id = parent.parent;
721 }
722
723 let rel_attr = node.attr(local_name!("rel"));
724 let href_attr = node.attr(local_name!("href"));
725
726 let (Some(rels), Some(href)) = (rel_attr, href_attr) else {
727 return;
728 };
729 if !rels.split_ascii_whitespace().any(|rel| rel == "stylesheet") {
730 return;
731 }
732
733 let url = self.doc.resolve_url(href);
734 let handler = ResourceHandler::new(
735 self.doc.tx.clone(),
736 self.doc.id(),
737 Some(node.id),
738 self.doc.shell_provider.clone(),
739 StylesheetHandler {
740 source_url: url.clone(),
741 guard: self.doc.guard.clone(),
742 net_provider: self.doc.net_provider.clone(),
743 },
744 );
745
746 if is_in_head {
747 self.doc
748 .pending_critical_resources
749 .insert(handler.request_id());
750 }
751
752 self.doc
753 .net_provider
754 .fetch(self.doc.id(), Request::get(url), Box::new(handler));
755 }
756
757 fn unload_stylesheet(&mut self, node_id: usize) {
758 let node = &mut self.doc.nodes[node_id];
759 let Some(element) = node.element_data_mut() else {
760 unreachable!();
761 };
762 let SpecialElementData::Stylesheet(stylesheet) = element.special_data.take() else {
763 unreachable!();
764 };
765
766 let guard = self.doc.guard.read();
767 self.doc.stylist.remove_stylesheet(stylesheet, &guard);
768 self.doc
769 .stylist
770 .force_stylesheet_origins_dirty(OriginSet::all());
771
772 self.doc.nodes_to_stylesheet.remove(&node_id);
773 }
774
775 fn load_image(&mut self, target_id: usize) {
776 let node = &self.doc.nodes[target_id];
777 if let Some(raw_src) = node.attr(local_name!("src")) {
778 if !raw_src.is_empty() {
779 let src = self.doc.resolve_url(raw_src);
780 let src_string = src.as_str();
781
782 if let Some(cached_image) = self.doc.image_cache.get(src_string) {
784 #[cfg(feature = "tracing")]
785 tracing::info!("Loading image {src_string} from cache");
786 let node = &mut self.doc.nodes[target_id];
787 node.element_data_mut().unwrap().special_data =
788 SpecialElementData::Image(Box::new(cached_image.clone()));
789 node.cache.clear();
790 node.insert_damage(ALL_DAMAGE);
791 return;
792 }
793
794 if let Some(waiting_list) = self.doc.pending_images.get_mut(src_string) {
796 #[cfg(feature = "tracing")]
797 tracing::info!("Image {src_string} already pending, queueing node {target_id}");
798 waiting_list.push((target_id, ImageType::Image));
799 return;
800 }
801
802 #[cfg(feature = "tracing")]
804 tracing::info!("Fetching image {src_string}");
805 self.doc
806 .pending_images
807 .insert(src_string.to_string(), vec![(target_id, ImageType::Image)]);
808
809 self.doc.net_provider.fetch(
810 self.doc.id(),
811 Request::get(src),
812 ResourceHandler::boxed(
813 self.doc.tx.clone(),
814 self.doc.id(),
815 None, self.doc.shell_provider.clone(),
817 ImageHandler::new(ImageType::Image),
818 ),
819 );
820 }
821 }
822 }
823
824 fn load_custom_paint_src(&mut self, target_id: usize) {
825 let node = &mut self.doc.nodes[target_id];
826 if let Some(raw_src) = node.attr(local_name!("src")) {
827 if let Ok(custom_paint_source_id) = raw_src.parse::<u64>() {
828 self.recompute_is_animating = true;
829 let canvas_data = SpecialElementData::Canvas(CanvasData {
830 custom_paint_source_id,
831 });
832 node.element_data_mut().unwrap().special_data = canvas_data;
833 }
834 }
835 }
836
837 fn process_button_input(&mut self, target_id: usize) {
838 let node = &self.doc.nodes[target_id];
839 let Some(data) = node.element_data() else {
840 return;
841 };
842
843 let tagname = data.name.local.as_ref();
844 let type_attr = data.attr(local_name!("type"));
845 let value = data.attr(local_name!("value"));
846
847 if let ("input", Some("button" | "submit" | "reset"), Some(value)) =
850 (tagname, type_attr, value)
851 {
852 let value = value.to_string();
853 let id = self.create_text_node(&value);
854 self.append_children(target_id, &[id]);
855 return;
856 }
857 #[cfg(feature = "file_input")]
858 if let ("input", Some("file")) = (tagname, type_attr) {
859 let button_id = self.create_element(
860 qual_name!("button", html),
861 vec![
862 Attribute {
863 name: qual_name!("type", html),
864 value: "button".to_string(),
865 },
866 Attribute {
867 name: qual_name!("tabindex", html),
868 value: "-1".to_string(),
869 },
870 ],
871 );
872 let label_id = self.create_element(qual_name!("label", html), vec![]);
873 let text_id = self.create_text_node("No File Selected");
874 let button_text_id = self.create_text_node("Browse");
875 self.append_children(target_id, &[button_id, label_id]);
876 self.append_children(label_id, &[text_id]);
877 self.append_children(button_id, &[button_text_id]);
878 }
879 }
880}
881
882fn set_input_checked_state(element: &mut ElementData, value: String) {
884 let Ok(checked) = value.parse() else {
885 return;
886 };
887 match element.special_data {
888 SpecialElementData::CheckboxInput(ref mut checked_mut) => *checked_mut = checked,
889 SpecialElementData::None => element.attrs.push(Attribute {
894 name: qual_name!("checked", html),
895 value: checked.to_string(),
896 }),
897 _ => {}
898 }
899}
900
901pub struct ViewportMut<'doc> {
904 doc: &'doc mut BaseDocument,
905 initial_viewport: Viewport,
906}
907impl ViewportMut<'_> {
908 pub fn new(doc: &mut BaseDocument) -> ViewportMut<'_> {
909 let initial_viewport = doc.viewport.clone();
910 ViewportMut {
911 doc,
912 initial_viewport,
913 }
914 }
915}
916impl Deref for ViewportMut<'_> {
917 type Target = Viewport;
918
919 fn deref(&self) -> &Self::Target {
920 &self.doc.viewport
921 }
922}
923impl DerefMut for ViewportMut<'_> {
924 fn deref_mut(&mut self) -> &mut Self::Target {
925 &mut self.doc.viewport
926 }
927}
928impl Drop for ViewportMut<'_> {
929 fn drop(&mut self) {
930 if self.doc.viewport == self.initial_viewport {
931 return;
932 }
933
934 self.doc.set_stylist_device(make_device(
935 &self.doc.viewport,
936 self.doc.media_type.clone(),
937 self.doc.font_ctx.clone(),
938 ));
939 self.doc.scroll_viewport_by(0.0, 0.0); let scale_has_changed =
942 self.doc.viewport().scale_f64() != self.initial_viewport.scale_f64();
943 if scale_has_changed {
944 self.doc.invalidate_inline_contexts();
945 self.doc.shell_provider.request_redraw();
946 }
947 }
948}
949
950#[cfg(test)]
951mod test {
952 use style::media_queries::MediaType;
953 use style_dom::ElementState;
954
955 use crate::{Attribute, BaseDocument, DocumentConfig, ElementData, NodeData, qual_name};
956
957 #[test]
958 fn media_type_defaults_to_screen() {
959 let mut document = BaseDocument::new(DocumentConfig::default());
960 assert_eq!(*document.media_type(), MediaType::screen());
961 assert_eq!(document.stylist_device().media_type(), MediaType::screen());
962 }
963
964 #[test]
965 fn media_type_honors_config() {
966 let mut document = BaseDocument::new(DocumentConfig {
967 media_type: Some(MediaType::print()),
968 ..Default::default()
969 });
970 assert_eq!(*document.media_type(), MediaType::print());
971 assert_eq!(document.stylist_device().media_type(), MediaType::print());
972 }
973
974 #[test]
975 fn set_media_type_updates_stylist_device() {
976 let mut document = BaseDocument::new(DocumentConfig::default());
977 assert_eq!(document.stylist_device().media_type(), MediaType::screen());
978
979 document.set_media_type(MediaType::print());
980 assert_eq!(*document.media_type(), MediaType::print());
981 assert_eq!(document.stylist_device().media_type(), MediaType::print());
982 }
983
984 #[test]
985 fn mutator_remove_disabled() {
986 let mut document = BaseDocument::new(DocumentConfig::default());
987 let id = document.create_node(NodeData::Element(ElementData::new(
988 qual_name!("button"),
989 vec![Attribute {
990 name: qual_name!("disabled"),
991 value: "".into(),
992 }],
993 )));
994
995 let node = document.get_node(id).unwrap();
996 assert!(
997 node.element_state.contains(ElementState::DISABLED),
998 "form node is disabled"
999 );
1000 assert!(
1001 !node.element_state.contains(ElementState::ENABLED),
1002 "form node is not enabled yet"
1003 );
1004
1005 let mut mutator = document.mutate();
1006 mutator.clear_attribute(id, qual_name!("disabled"));
1007 drop(mutator);
1008
1009 let node = document.get_node(id).unwrap();
1010 assert!(
1011 !node.element_state.contains(ElementState::DISABLED),
1012 "form node is no longer disabled"
1013 );
1014 assert!(
1015 node.element_state.contains(ElementState::ENABLED),
1016 "form node is enabled"
1017 );
1018 }
1019
1020 #[test]
1021 fn mutator_set_disabled() {
1022 let mut document = BaseDocument::new(DocumentConfig::default());
1023 let id = document.create_node(NodeData::Element(ElementData::new(
1024 qual_name!("button"),
1025 vec![],
1026 )));
1027
1028 let node = document.get_node(id).unwrap();
1029 assert!(
1030 !node.element_state.contains(ElementState::DISABLED),
1031 "form node is not disabled"
1032 );
1033 assert!(
1034 node.element_state.contains(ElementState::ENABLED),
1035 "form node is enabled"
1036 );
1037
1038 let mut mutator = document.mutate();
1039 mutator.set_attribute(id, qual_name!("disabled"), "");
1040 drop(mutator);
1041
1042 let node = document.get_node(id).unwrap();
1043
1044 assert!(
1045 node.element_state.contains(ElementState::DISABLED),
1046 "form node is disabled"
1047 );
1048 assert!(
1049 !node.element_state.contains(ElementState::ENABLED),
1050 "form node is no longer enabled enabled"
1051 );
1052 }
1053
1054 #[test]
1055 fn mutator_set_disabled_invalid_node() {
1056 let mut document = BaseDocument::new(DocumentConfig::default());
1057 let id = document.create_node(NodeData::Element(ElementData::new(qual_name!("a"), vec![])));
1058
1059 let node = document.get_node(id).unwrap();
1060 assert!(
1061 !node.element_state.contains(ElementState::DISABLED),
1062 "form node is not disabled"
1063 );
1064 assert!(
1065 !node.element_state.contains(ElementState::ENABLED),
1066 "form node is enabled"
1067 );
1068
1069 let mut mutator = document.mutate();
1070 mutator.set_attribute(id, qual_name!("disabled"), "");
1071 drop(mutator);
1072
1073 let node = document.get_node(id).unwrap();
1074 assert!(
1075 !node.element_state.contains(ElementState::DISABLED),
1076 "form node is not disabled"
1077 );
1078 assert!(
1079 !node.element_state.contains(ElementState::ENABLED),
1080 "form node is enabled"
1081 );
1082 }
1083}