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 self.doc.snapshot_node(node_id);
218
219 let node = &mut self.doc.nodes[node_id];
220 if let Some(mut data) = node.stylo_element_data.get_mut() {
221 data.hint |= RestyleHint::restyle_subtree();
222 data.damage.insert(ALL_DAMAGE);
223 }
224
225 let parent = node.parent;
227 if let Some(parent_id) = parent {
228 let parent = &mut self.doc.nodes[parent_id];
229 if let Some(mut data) = parent.stylo_element_data.get_mut() {
230 data.hint |= RestyleHint::restyle_subtree();
231 }
232 }
233
234 self.doc.nodes[node_id].mark_ancestors_dirty();
238
239 let node = &mut self.doc.nodes[node_id];
240
241 let NodeData::Element(ref mut element) = node.data else {
242 return;
243 };
244
245 #[cfg(feature = "custom-widget")]
247 if let SpecialElementData::CustomWidget(widget_data) = &mut element.special_data {
248 let old_value = element.attrs.get(&name).as_ref().map(|attr| &*attr.value);
249 widget_data
250 .widget
251 .attribute_changed(&name.local, old_value, Some(value));
252 }
253
254 element.attrs.set(name.clone(), value);
255
256 let tag = &element.name.local;
257 let attr = &name.local;
258
259 if *attr == local_name!("id") {
260 element.id = Some(Atom::from(value))
261 }
262
263 if *attr == local_name!("value") {
264 if let Some(input_data) = element.text_input_data_mut() {
265 input_data.set_text(
267 &mut self.doc.font_ctx.lock().unwrap(),
268 &mut self.doc.layout_ctx,
269 value,
270 );
271 }
272 return;
273 }
274
275 if *attr == local_name!("style") {
276 element.flush_style_attribute(&self.doc.guard, &self.doc.url.url_extra_data());
277 node.mark_style_attr_updated();
278 return;
279 }
280
281 if *attr == local_name!("disabled") && element.can_be_disabled() {
282 node.disable();
283 return;
284 }
285
286 if !node.flags.is_in_document() {
289 return;
290 }
291
292 if (tag, attr) == tag_and_attr!("input", "checked") {
293 set_input_checked_state(element, value.to_string());
294 } else if (tag, attr) == tag_and_attr!("img", "src") {
295 self.load_image(node_id);
296 } else if (tag, attr) == tag_and_attr!("canvas", "src") {
297 self.load_custom_paint_src(node_id);
298 } else if (tag, attr) == tag_and_attr!("link", "href") {
299 self.load_linked_stylesheet(node_id);
300 }
301 }
302
303 pub fn clear_attribute(&mut self, node_id: usize, name: QualName) {
304 self.doc.snapshot_node(node_id);
305
306 let node = &mut self.doc.nodes[node_id];
307
308 if let Some(mut data) = node.stylo_element_data.get_mut() {
309 data.hint |= RestyleHint::restyle_subtree();
310 data.damage.insert(ALL_DAMAGE);
311 }
312
313 node.mark_ancestors_dirty();
316
317 let Some(element) = node.element_data_mut() else {
318 return;
319 };
320
321 let removed_attr = element.attrs.remove(&name);
322 let had_attr = removed_attr.is_some();
323 if !had_attr {
324 return;
325 }
326
327 #[cfg(feature = "custom-widget")]
329 if let SpecialElementData::CustomWidget(widget_data) = &mut element.special_data {
330 let old_value = removed_attr.as_ref().map(|attr| &*attr.value);
331 widget_data
332 .widget
333 .attribute_changed(&name.local, old_value, None);
334 }
335
336 if name.local == local_name!("id") {
337 element.id = None;
338 }
339
340 if name.local == local_name!("value") {
342 if let Some(input_data) = element.text_input_data_mut() {
343 input_data.set_text(
344 &mut self.doc.font_ctx.lock().unwrap(),
345 &mut self.doc.layout_ctx,
346 "",
347 );
348 }
349 }
350
351 let tag = &element.name.local;
352 let attr = &name.local;
353
354 if *attr == local_name!("disabled") && element.can_be_disabled() {
355 node.enable();
356 return;
357 }
358
359 if *attr == local_name!("style") {
360 element.flush_style_attribute(&self.doc.guard, &self.doc.url.url_extra_data());
361 node.mark_style_attr_updated();
362 } else if (tag, attr) == tag_and_attr!("canvas", "src") {
363 self.recompute_is_animating = true;
364 } else if (tag, attr) == tag_and_attr!("link", "href") {
365 self.unload_stylesheet(node_id);
366 }
367 }
368
369 pub fn set_style_property(&mut self, node_id: usize, name: &str, value: &str) {
370 self.doc.set_style_property(node_id, name, value)
371 }
372
373 pub fn remove_style_property(&mut self, node_id: usize, name: &str) {
374 self.doc.remove_style_property(node_id, name)
375 }
376
377 pub fn set_sub_document(&mut self, node_id: usize, sub_document: Box<dyn Document>) {
378 self.doc.set_sub_document(node_id, sub_document)
379 }
380
381 pub fn remove_sub_document(&mut self, node_id: usize) {
382 self.doc.remove_sub_document(node_id)
383 }
384
385 #[cfg(feature = "custom-widget")]
386 pub fn set_custom_widget(&mut self, node_id: usize, widget: Box<dyn crate::Widget>) {
387 self.doc.set_custom_widget(node_id, widget)
388 }
389
390 #[cfg(feature = "custom-widget")]
391 pub fn remove_custom_widget(&mut self, node_id: usize) {
392 self.doc.remove_custom_widget(node_id)
393 }
394
395 pub fn remove_node(&mut self, node_id: usize) {
397 let node = &mut self.doc.nodes[node_id];
398
399 if let Some(parent_id) = node.parent.take() {
401 let parent = &mut self.doc.nodes[parent_id];
402 parent.insert_damage(ALL_DAMAGE);
403 parent.mark_ancestors_dirty();
405 parent.children.retain(|id| *id != node_id);
406 self.maybe_record_node(parent_id);
407 }
408
409 self.process_removed_subtree(node_id);
410 }
411
412 pub fn remove_and_drop_node(&mut self, node_id: usize) -> Option<Node> {
413 self.process_removed_subtree(node_id);
414
415 let node = self.doc.drop_node_ignoring_parent(node_id);
416
417 if let Some(parent_id) = node.as_ref().and_then(|node| node.parent) {
419 let parent = &mut self.doc.nodes[parent_id];
420 parent.insert_damage(ALL_DAMAGE);
421 let parent_is_in_doc = parent.flags.is_in_document();
422
423 if parent_is_in_doc {
425 if let Some(mut data) = parent.stylo_element_data.get_mut() {
426 data.hint |= RestyleHint::restyle_subtree();
427 }
428 parent.mark_ancestors_dirty();
430 }
431
432 parent.children.retain(|id| *id != node_id);
433 self.maybe_record_node(parent_id);
434 }
435
436 node
437 }
438
439 pub fn remove_and_drop_all_children(&mut self, node_id: usize) {
440 let parent = &mut self.doc.nodes[node_id];
441 let parent_is_in_doc = parent.flags.is_in_document();
442
443 if parent_is_in_doc {
445 if let Some(mut data) = parent.stylo_element_data.get_mut() {
446 data.hint |= RestyleHint::restyle_subtree();
447 }
448 parent.mark_ancestors_dirty();
450 }
451
452 let children = mem::take(&mut parent.children);
453 for child_id in children {
454 self.process_removed_subtree(child_id);
455 let _ = self.doc.drop_node_ignoring_parent(child_id);
456 }
457 self.maybe_record_node(node_id);
458 }
459
460 pub fn remove_node_if_unparented(&mut self, node_id: usize) {
462 if let Some(node) = self.doc.get_node(node_id) {
463 if node.parent.is_none() {
464 self.remove_and_drop_node(node_id);
465 }
466 }
467 }
468
469 pub fn append_children(&mut self, parent_id: usize, child_ids: &[usize]) {
471 self.add_children_to_parent(parent_id, child_ids, &|parent, child_ids| {
472 parent.children.extend_from_slice(child_ids);
473 });
474 }
475
476 pub fn insert_nodes_before(&mut self, anchor_node_id: usize, new_node_ids: &[usize]) {
477 let parent_id = self.doc.nodes[anchor_node_id].parent.unwrap();
478 self.add_children_to_parent(parent_id, new_node_ids, &|parent, child_ids| {
479 let node_child_idx = parent.index_of_child(anchor_node_id).unwrap();
480 parent
481 .children
482 .splice(node_child_idx..node_child_idx, child_ids.iter().copied());
483 });
484 }
485
486 fn add_children_to_parent(
487 &mut self,
488 parent_id: usize,
489 child_ids: &[usize],
490 insert_children_fn: &dyn Fn(&mut Node, &[usize]),
491 ) {
492 let new_parent = &mut self.doc.nodes[parent_id];
493 new_parent.insert_damage(ALL_DAMAGE);
494 let new_parent_is_in_doc = new_parent.flags.is_in_document();
495
496 if new_parent_is_in_doc {
498 if let Some(mut data) = new_parent.stylo_element_data.get_mut() {
499 data.hint |= RestyleHint::restyle_subtree();
500 }
501 new_parent.mark_ancestors_dirty();
503 }
504
505 insert_children_fn(new_parent, child_ids);
506
507 for child_id in child_ids.iter().copied() {
508 let child = &mut self.doc.nodes[child_id];
509 let old_parent_id = child.parent.replace(parent_id);
510
511 let child_was_in_doc = child.flags.is_in_document();
512 if new_parent_is_in_doc != child_was_in_doc {
513 self.process_added_subtree(child_id);
514 }
515
516 if let Some(old_parent_id) = old_parent_id {
517 let old_parent = &mut self.doc.nodes[old_parent_id];
518 old_parent.insert_damage(ALL_DAMAGE);
519
520 if child_was_in_doc {
522 if let Some(mut data) = old_parent.stylo_element_data.get_mut() {
523 data.hint |= RestyleHint::restyle_subtree();
524 }
525 old_parent.mark_ancestors_dirty();
527 }
528
529 old_parent.children.retain(|id| *id != child_id);
530 self.maybe_record_node(old_parent_id);
531 }
532 }
533
534 self.maybe_record_node(parent_id);
535 }
536
537 pub fn insert_nodes_after(&mut self, anchor_node_id: usize, new_node_ids: &[usize]) {
539 match self.next_sibling_id(anchor_node_id) {
540 Some(id) => self.insert_nodes_before(id, new_node_ids),
541 None => {
542 let parent_id = self.parent_id(anchor_node_id).unwrap();
543 self.append_children(parent_id, new_node_ids)
544 }
545 }
546 }
547
548 pub fn reparent_children(&mut self, old_parent_id: usize, new_parent_id: usize) {
549 let child_ids = std::mem::take(&mut self.doc.nodes[old_parent_id].children);
550 self.maybe_record_node(old_parent_id);
551 self.append_children(new_parent_id, &child_ids);
552 }
553
554 pub fn replace_node_with(&mut self, anchor_node_id: usize, new_node_ids: &[usize]) {
555 self.insert_nodes_before(anchor_node_id, new_node_ids);
556 self.remove_node(anchor_node_id);
557 }
558}
559
560impl<'doc> DocumentMutator<'doc> {
561 pub fn flush(&mut self) {
562 if self.recompute_is_animating {
563 self.doc.has_canvas = self.doc.compute_has_canvas();
564 }
565
566 if let Some(id) = self.title_node {
567 let title = self.doc.nodes[id].text_content();
568 self.doc.shell_provider.set_window_title(title);
569 }
570
571 for id in self.style_nodes.drain() {
573 self.doc.process_style_element(id);
574 }
575
576 for id in self.form_nodes.drain() {
577 self.doc.reset_form_owner(id);
578 }
579
580 #[cfg(feature = "autofocus")]
581 if let Some(node_id) = self.node_to_autofocus.take() {
582 if self.doc.get_node(node_id).is_some() {
583 self.doc.set_focus_to(node_id);
584 }
585 }
586 }
587
588 pub fn set_inner_html(&mut self, node_id: usize, html: &str) {
589 self.remove_and_drop_all_children(node_id);
590 self.doc
591 .html_parser_provider
592 .clone()
593 .parse_inner_html(self, node_id, html);
594 }
595
596 fn flush_eager_ops(&mut self) {
597 let mut ops = mem::take(&mut self.eager_op_queue);
598 for op in ops.drain(0..) {
599 match op {
600 SpecialOp::LoadImage(node_id) => self.load_image(node_id),
601 SpecialOp::LoadStylesheet(node_id) => self.load_linked_stylesheet(node_id),
602 SpecialOp::UnloadStylesheet(node_id) => self.unload_stylesheet(node_id),
603 SpecialOp::LoadCustomPaintSource(node_id) => self.load_custom_paint_src(node_id),
604 SpecialOp::ProcessButtonInput(node_id) => self.process_button_input(node_id),
605 SpecialOp::UnloadSubDocument(node_id) => self.remove_sub_document(node_id),
606 #[cfg(feature = "custom-widget")]
607 SpecialOp::UnloadCustomWidget(node_id) => self.remove_custom_widget(node_id),
608 }
609 }
610
611 self.eager_op_queue = ops;
613 }
614
615 fn process_added_subtree(&mut self, node_id: usize) {
616 self.doc.iter_subtree_mut(node_id, |node_id, doc| {
617 let node = &mut doc.nodes[node_id];
618 node.flags.set(NodeFlags::IS_IN_DOCUMENT, true);
619 node.insert_damage(ALL_DAMAGE);
620
621 if let Some(id_attr) = node.attr(local_name!("id")) {
623 doc.nodes_to_id.insert(id_attr.to_string(), node_id);
624 }
625
626 let NodeData::Element(ref mut element) = node.data else {
627 return;
628 };
629
630 let tag = element.name.local.as_ref();
632 match tag {
633 "title" => self.title_node = Some(node_id),
634 "link" => self.eager_op_queue.push(SpecialOp::LoadStylesheet(node_id)),
635 "img" => self.eager_op_queue.push(SpecialOp::LoadImage(node_id)),
636 "canvas" => self
637 .eager_op_queue
638 .push(SpecialOp::LoadCustomPaintSource(node_id)),
639 "style" => {
640 self.style_nodes.insert(node_id);
641 }
642 "button" | "fieldset" | "input" | "select" | "textarea" | "object" | "output" => {
643 self.eager_op_queue
644 .push(SpecialOp::ProcessButtonInput(node_id));
645 self.form_nodes.insert(node_id);
646 }
647 _ => {}
648 }
649
650 #[cfg(feature = "autofocus")]
651 if node.is_focussable() {
652 if let NodeData::Element(ref element) = node.data {
653 if let Some(value) = element.attr(local_name!("autofocus")) {
654 if value == "true" {
655 self.node_to_autofocus = Some(node_id);
656 }
657 }
658 }
659 }
660 });
661
662 self.flush_eager_ops();
663 }
664
665 fn process_removed_subtree(&mut self, node_id: usize) {
666 self.doc.iter_subtree_mut(node_id, |node_id, doc| {
667 let node = &mut doc.nodes[node_id];
668 node.flags.set(NodeFlags::IS_IN_DOCUMENT, false);
669
670 if doc.hover_node_id == Some(node_id) {
673 doc.hover_node_id = None;
674 doc.hover_node_is_text = false;
675 }
676
677 if doc.active_node_id == Some(node_id) {
680 doc.active_node_id = None;
681 }
682
683 if node.has_snapshot {
686 let opaque_id = style::dom::TNode::opaque(&&*node);
687 doc.snapshots.remove(&opaque_id);
688 node.has_snapshot = false;
689 }
690
691 if let Some(id_attr) = node.attr(local_name!("id")) {
693 doc.nodes_to_id.remove(id_attr);
694 }
695
696 let NodeData::Element(ref mut element) = node.data else {
697 return;
698 };
699
700 match &element.special_data {
701 SpecialElementData::SubDocument(_) => {
702 self.eager_op_queue
703 .push(SpecialOp::UnloadSubDocument(node_id));
704 }
705 #[cfg(feature = "custom-widget")]
706 SpecialElementData::CustomWidget(_) => {
707 self.eager_op_queue
708 .push(SpecialOp::UnloadCustomWidget(node_id));
709 }
710 SpecialElementData::Stylesheet(_) => self
711 .eager_op_queue
712 .push(SpecialOp::UnloadStylesheet(node_id)),
713 SpecialElementData::Image(_) => {}
714 SpecialElementData::Canvas(_) => {
715 self.recompute_is_animating = true;
716 }
717 SpecialElementData::TableRoot(_) => {}
718 SpecialElementData::TextInput(_) => {}
719 SpecialElementData::CheckboxInput(_) => {}
720 #[cfg(feature = "file_input")]
721 SpecialElementData::FileInput(_) => {}
722 SpecialElementData::None => {}
723 }
724 });
725
726 self.flush_eager_ops();
727 }
728
729 fn maybe_record_node(&mut self, node_id: impl Into<Option<usize>>) {
730 let Some(node_id) = node_id.into() else {
731 return;
732 };
733
734 let Some(tag_name) = self.doc.nodes[node_id]
735 .data
736 .downcast_element()
737 .map(|elem| &elem.name.local)
738 else {
739 return;
740 };
741
742 match tag_name.as_ref() {
743 "title" => self.title_node = Some(node_id),
744 "style" => {
745 self.style_nodes.insert(node_id);
746 }
747 _ => {}
748 }
749 }
750
751 fn load_linked_stylesheet(&mut self, target_id: usize) {
752 let node = &self.doc.nodes[target_id];
753
754 let mut is_in_head = false;
755 let mut parent_id = node.parent;
756 while let Some(id) = parent_id
757 && !is_in_head
758 {
759 let parent = &self.doc.nodes[id];
760 is_in_head |= parent.data.is_element_with_tag_name(&local_name!("head"));
761 parent_id = parent.parent;
762 }
763
764 let rel_attr = node.attr(local_name!("rel"));
765 let href_attr = node.attr(local_name!("href"));
766
767 let (Some(rels), Some(href)) = (rel_attr, href_attr) else {
768 return;
769 };
770 if !rels.split_ascii_whitespace().any(|rel| rel == "stylesheet") {
771 return;
772 }
773
774 let url = self.doc.resolve_url(href);
775 let handler = ResourceHandler::new(
776 self.doc.tx.clone(),
777 self.doc.id(),
778 Some(node.id),
779 self.doc.shell_provider.clone(),
780 StylesheetHandler {
781 source_url: url.clone(),
782 guard: self.doc.guard.clone(),
783 net_provider: self.doc.net_provider.clone(),
784 abort_signal: self.doc.abort_signal.clone(),
785 },
786 );
787
788 if is_in_head {
789 self.doc
790 .pending_critical_resources
791 .insert(handler.request_id());
792 }
793
794 self.doc.net_provider.fetch(
795 self.doc.id(),
796 self.doc.build_request(url),
797 Box::new(handler),
798 );
799 }
800
801 fn unload_stylesheet(&mut self, node_id: usize) {
802 let node = &mut self.doc.nodes[node_id];
803 let Some(element) = node.element_data_mut() else {
804 unreachable!();
805 };
806 let SpecialElementData::Stylesheet(stylesheet) = element.special_data.take() else {
807 unreachable!();
808 };
809
810 let guard = self.doc.guard.read();
811 self.doc.stylist.remove_stylesheet(stylesheet, &guard);
812 self.doc
813 .stylist
814 .force_stylesheet_origins_dirty(OriginSet::all());
815
816 self.doc.nodes_to_stylesheet.remove(&node_id);
817 }
818
819 fn load_image(&mut self, target_id: usize) {
820 let node = &self.doc.nodes[target_id];
821 if let Some(raw_src) = node.attr(local_name!("src")) {
822 if !raw_src.is_empty() {
823 let src = self.doc.resolve_url(raw_src);
824 let src_string = src.as_str();
825
826 if let Some(cached_image) = self.doc.image_cache.get(src_string) {
828 #[cfg(feature = "tracing")]
829 tracing::info!("Loading image {src_string} from cache");
830 let node = &mut self.doc.nodes[target_id];
831 node.element_data_mut().unwrap().special_data =
832 SpecialElementData::Image(Box::new(cached_image.clone()));
833 node.cache.clear();
834 node.insert_damage(ALL_DAMAGE);
835 return;
836 }
837
838 if let Some(waiting_list) = self.doc.pending_images.get_mut(src_string) {
840 #[cfg(feature = "tracing")]
841 tracing::info!("Image {src_string} already pending, queueing node {target_id}");
842 waiting_list.push((target_id, ImageType::Image));
843 return;
844 }
845
846 #[cfg(feature = "tracing")]
848 tracing::info!("Fetching image {src_string}");
849 self.doc
850 .pending_images
851 .insert(src_string.to_string(), vec![(target_id, ImageType::Image)]);
852
853 self.doc.net_provider.fetch(
854 self.doc.id(),
855 self.doc.build_request(src),
856 ResourceHandler::boxed(
857 self.doc.tx.clone(),
858 self.doc.id(),
859 None, self.doc.shell_provider.clone(),
861 ImageHandler::new(ImageType::Image),
862 ),
863 );
864 }
865 }
866 }
867
868 fn load_custom_paint_src(&mut self, target_id: usize) {
869 let node = &mut self.doc.nodes[target_id];
870 if let Some(raw_src) = node.attr(local_name!("src")) {
871 if let Ok(custom_paint_source_id) = raw_src.parse::<u64>() {
872 self.recompute_is_animating = true;
873 let canvas_data = SpecialElementData::Canvas(CanvasData {
874 custom_paint_source_id,
875 });
876 node.element_data_mut().unwrap().special_data = canvas_data;
877 }
878 }
879 }
880
881 fn process_button_input(&mut self, target_id: usize) {
882 let node = &self.doc.nodes[target_id];
883 let Some(data) = node.element_data() else {
884 return;
885 };
886
887 let tagname = data.name.local.as_ref();
888 let type_attr = data.attr(local_name!("type"));
889 let value = data.attr(local_name!("value"));
890
891 if let ("input", Some("button" | "submit" | "reset"), Some(value)) =
894 (tagname, type_attr, value)
895 {
896 let value = value.to_string();
897 let id = self.create_text_node(&value);
898 self.append_children(target_id, &[id]);
899 return;
900 }
901 #[cfg(feature = "file_input")]
902 if let ("input", Some("file")) = (tagname, type_attr) {
903 let button_id = self.create_element(
904 qual_name!("button", html),
905 vec![
906 Attribute {
907 name: qual_name!("type", html),
908 value: "button".to_string(),
909 },
910 Attribute {
911 name: qual_name!("tabindex", html),
912 value: "-1".to_string(),
913 },
914 ],
915 );
916 let label_id = self.create_element(qual_name!("label", html), vec![]);
917 let text_id = self.create_text_node("No File Selected");
918 let button_text_id = self.create_text_node("Browse");
919 self.append_children(target_id, &[button_id, label_id]);
920 self.append_children(label_id, &[text_id]);
921 self.append_children(button_id, &[button_text_id]);
922 }
923 }
924}
925
926fn set_input_checked_state(element: &mut ElementData, value: String) {
928 let Ok(checked) = value.parse() else {
929 return;
930 };
931 match element.special_data {
932 SpecialElementData::CheckboxInput(ref mut checked_mut) => *checked_mut = checked,
933 SpecialElementData::None => element.attrs.push(Attribute {
938 name: qual_name!("checked", html),
939 value: checked.to_string(),
940 }),
941 _ => {}
942 }
943}
944
945pub struct ViewportMut<'doc> {
948 doc: &'doc mut BaseDocument,
949 initial_viewport: Viewport,
950}
951impl ViewportMut<'_> {
952 pub fn new(doc: &mut BaseDocument) -> ViewportMut<'_> {
953 let initial_viewport = doc.viewport.clone();
954 ViewportMut {
955 doc,
956 initial_viewport,
957 }
958 }
959}
960impl Deref for ViewportMut<'_> {
961 type Target = Viewport;
962
963 fn deref(&self) -> &Self::Target {
964 &self.doc.viewport
965 }
966}
967impl DerefMut for ViewportMut<'_> {
968 fn deref_mut(&mut self) -> &mut Self::Target {
969 &mut self.doc.viewport
970 }
971}
972impl Drop for ViewportMut<'_> {
973 fn drop(&mut self) {
974 if self.doc.viewport == self.initial_viewport {
975 return;
976 }
977
978 self.doc.set_stylist_device(make_device(
979 &self.doc.viewport,
980 self.doc.media_type.clone(),
981 self.doc.font_ctx.clone(),
982 ));
983 self.doc.scroll_viewport_by(0.0, 0.0); let scale_has_changed =
986 self.doc.viewport().scale_f64() != self.initial_viewport.scale_f64();
987 if scale_has_changed {
988 self.doc.invalidate_inline_contexts();
989 self.doc.shell_provider.request_redraw();
990 }
991 }
992}
993
994#[cfg(test)]
995mod test {
996 use style::media_queries::MediaType;
997 use style_dom::ElementState;
998
999 use crate::{Attribute, BaseDocument, DocumentConfig, ElementData, NodeData, qual_name};
1000
1001 #[test]
1002 fn media_type_defaults_to_screen() {
1003 let mut document = BaseDocument::new(DocumentConfig::default());
1004 assert_eq!(*document.media_type(), MediaType::screen());
1005 assert_eq!(document.stylist_device().media_type(), MediaType::screen());
1006 }
1007
1008 #[test]
1009 fn media_type_honors_config() {
1010 let mut document = BaseDocument::new(DocumentConfig {
1011 media_type: Some(MediaType::print()),
1012 ..Default::default()
1013 });
1014 assert_eq!(*document.media_type(), MediaType::print());
1015 assert_eq!(document.stylist_device().media_type(), MediaType::print());
1016 }
1017
1018 #[test]
1019 fn set_media_type_updates_stylist_device() {
1020 let mut document = BaseDocument::new(DocumentConfig::default());
1021 assert_eq!(document.stylist_device().media_type(), MediaType::screen());
1022
1023 document.set_media_type(MediaType::print());
1024 assert_eq!(*document.media_type(), MediaType::print());
1025 assert_eq!(document.stylist_device().media_type(), MediaType::print());
1026 }
1027
1028 #[test]
1029 fn mutator_remove_disabled() {
1030 let mut document = BaseDocument::new(DocumentConfig::default());
1031 let id = document.create_node(NodeData::Element(ElementData::new(
1032 qual_name!("button"),
1033 vec![Attribute {
1034 name: qual_name!("disabled"),
1035 value: "".into(),
1036 }],
1037 )));
1038
1039 let node = document.get_node(id).unwrap();
1040 assert!(
1041 node.element_state.contains(ElementState::DISABLED),
1042 "form node is disabled"
1043 );
1044 assert!(
1045 !node.element_state.contains(ElementState::ENABLED),
1046 "form node is not enabled yet"
1047 );
1048
1049 let mut mutator = document.mutate();
1050 mutator.clear_attribute(id, qual_name!("disabled"));
1051 drop(mutator);
1052
1053 let node = document.get_node(id).unwrap();
1054 assert!(
1055 !node.element_state.contains(ElementState::DISABLED),
1056 "form node is no longer disabled"
1057 );
1058 assert!(
1059 node.element_state.contains(ElementState::ENABLED),
1060 "form node is enabled"
1061 );
1062 }
1063
1064 #[test]
1065 fn mutator_set_disabled() {
1066 let mut document = BaseDocument::new(DocumentConfig::default());
1067 let id = document.create_node(NodeData::Element(ElementData::new(
1068 qual_name!("button"),
1069 vec![],
1070 )));
1071
1072 let node = document.get_node(id).unwrap();
1073 assert!(
1074 !node.element_state.contains(ElementState::DISABLED),
1075 "form node is not disabled"
1076 );
1077 assert!(
1078 node.element_state.contains(ElementState::ENABLED),
1079 "form node is enabled"
1080 );
1081
1082 let mut mutator = document.mutate();
1083 mutator.set_attribute(id, qual_name!("disabled"), "");
1084 drop(mutator);
1085
1086 let node = document.get_node(id).unwrap();
1087
1088 assert!(
1089 node.element_state.contains(ElementState::DISABLED),
1090 "form node is disabled"
1091 );
1092 assert!(
1093 !node.element_state.contains(ElementState::ENABLED),
1094 "form node is no longer enabled enabled"
1095 );
1096 }
1097
1098 #[test]
1099 fn mutator_set_disabled_invalid_node() {
1100 let mut document = BaseDocument::new(DocumentConfig::default());
1101 let id = document.create_node(NodeData::Element(ElementData::new(qual_name!("a"), vec![])));
1102
1103 let node = document.get_node(id).unwrap();
1104 assert!(
1105 !node.element_state.contains(ElementState::DISABLED),
1106 "form node is not disabled"
1107 );
1108 assert!(
1109 !node.element_state.contains(ElementState::ENABLED),
1110 "form node is enabled"
1111 );
1112
1113 let mut mutator = document.mutate();
1114 mutator.set_attribute(id, qual_name!("disabled"), "");
1115 drop(mutator);
1116
1117 let node = document.get_node(id).unwrap();
1118 assert!(
1119 !node.element_state.contains(ElementState::DISABLED),
1120 "form node is not disabled"
1121 );
1122 assert!(
1123 !node.element_state.contains(ElementState::ENABLED),
1124 "form node is enabled"
1125 );
1126 }
1127}