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