blitz_dom/
mutator.rs

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::{CssHandler, ImageHandler};
8use crate::node::{CanvasData, NodeFlags, SpecialElementData};
9use crate::util::ImageType;
10use crate::{
11    Attribute, BaseDocument, 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    /// The node is not a text node
28    NotTextNode,
29}
30
31/// Operations that happen almost immediately, but are deferred within a
32/// function for borrow-checker reasons.
33enum SpecialOp {
34    LoadImage(usize),
35    LoadStylesheet(usize),
36    UnloadStylesheet(usize),
37    LoadCustomPaintSource(usize),
38    ProcessButtonInput(usize),
39}
40
41pub struct DocumentMutator<'doc> {
42    /// Document is public as an escape hatch, but users of this API should ideally avoid using it
43    /// and prefer exposing additional functionality in DocumentMutator.
44    pub doc: &'doc mut BaseDocument,
45
46    eager_op_queue: Vec<SpecialOp>,
47
48    // Tracked nodes for deferred processing when mutations have completed
49    title_node: Option<usize>,
50    style_nodes: HashSet<usize>,
51    form_nodes: HashSet<usize>,
52
53    /// Whether an element/attribute that affect animation status has been seen
54    recompute_is_animating: bool,
55
56    /// The (latest) node which has been mounted in and had autofocus=true, if any
57    #[cfg(feature = "autofocus")]
58    node_to_autofocus: Option<usize>,
59}
60
61impl Drop for DocumentMutator<'_> {
62    fn drop(&mut self) {
63        self.flush(); // Defined at bottom of file
64    }
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    // Query methods
82
83    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    // Node creation methods
121
122    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(id).unwrap();
136
137        // Initialise style data
138        *node.stylo_element_data.borrow_mut() = Some(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    // Node mutation methods
151
152    pub fn set_node_text(&mut self, node_id: usize, value: &str) {
153        let node = self.doc.get_node_mut(node_id).unwrap();
154
155        let text = match node.data {
156            NodeData::Text(ref mut text) => text,
157            // TODO: otherwise this is basically element.textContent which is a bit different - need to parse as html
158            _ => 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            let parent = node.parent;
167            self.maybe_record_node(parent);
168        }
169    }
170
171    pub fn append_text_to_node(&mut self, node_id: usize, text: &str) -> Result<(), AppendTextErr> {
172        match self.doc.nodes[node_id].text_data_mut() {
173            Some(data) => {
174                data.content += text;
175                Ok(())
176            }
177            None => Err(AppendTextErr::NotTextNode),
178        }
179    }
180
181    pub fn add_attrs_if_missing(&mut self, node_id: usize, attrs: Vec<Attribute>) {
182        let node = &mut self.doc.nodes[node_id];
183        node.insert_damage(ALL_DAMAGE);
184        let element_data = node.element_data_mut().expect("Not an element");
185
186        let existing_names = element_data
187            .attrs
188            .iter()
189            .map(|e| e.name.clone())
190            .collect::<HashSet<_>>();
191
192        for attr in attrs
193            .into_iter()
194            .filter(|attr| !existing_names.contains(&attr.name))
195        {
196            self.set_attribute(node_id, attr.name, &attr.value);
197        }
198    }
199
200    pub fn set_attribute(&mut self, node_id: usize, name: QualName, value: &str) {
201        self.doc.snapshot_node(node_id);
202
203        let node = &mut self.doc.nodes[node_id];
204        if let Some(data) = &mut *node.stylo_element_data.borrow_mut() {
205            data.hint |= RestyleHint::restyle_subtree();
206            data.damage.insert(ALL_DAMAGE);
207        }
208
209        // TODO: make this fine grained / conditional based on ElementSelectorFlags
210        let parent = node.parent;
211        if let Some(parent_id) = parent {
212            let parent = &mut self.doc.nodes[parent_id];
213            if let Some(data) = &mut *parent.stylo_element_data.borrow_mut() {
214                data.hint |= RestyleHint::restyle_subtree();
215            }
216        }
217
218        let node = &mut self.doc.nodes[node_id];
219
220        let NodeData::Element(ref mut element) = node.data else {
221            return;
222        };
223
224        element.attrs.set(name.clone(), value);
225
226        let tag = &element.name.local;
227        let attr = &name.local;
228
229        if *attr == local_name!("id") {
230            element.id = Some(Atom::from(value))
231        }
232
233        if *attr == local_name!("value") {
234            if let Some(input_data) = element.text_input_data_mut() {
235                // Update text input value
236                input_data.set_text(
237                    &mut self.doc.font_ctx.lock().unwrap(),
238                    &mut self.doc.layout_ctx,
239                    value,
240                );
241            }
242            return;
243        }
244
245        if *attr == local_name!("style") {
246            element.flush_style_attribute(&self.doc.guard, &self.doc.url.url_extra_data());
247            return;
248        }
249
250        // If node if not in the document, then don't apply any special behaviours
251        // and simply set the attribute value
252        if !node.flags.is_in_document() {
253            return;
254        }
255
256        if (tag, attr) == tag_and_attr!("input", "checked") {
257            set_input_checked_state(element, value.to_string());
258        } else if (tag, attr) == tag_and_attr!("img", "src") {
259            self.load_image(node_id);
260        } else if (tag, attr) == tag_and_attr!("canvas", "src") {
261            self.load_custom_paint_src(node_id);
262        } else if (tag, attr) == tag_and_attr!("link", "href") {
263            self.load_linked_stylesheet(node_id);
264        }
265    }
266
267    pub fn clear_attribute(&mut self, node_id: usize, name: QualName) {
268        self.doc.snapshot_node(node_id);
269
270        let node = &mut self.doc.nodes[node_id];
271
272        let mut stylo_element_data = node.stylo_element_data.borrow_mut();
273        if let Some(data) = &mut *stylo_element_data {
274            data.hint |= RestyleHint::restyle_subtree();
275            data.damage.insert(ALL_DAMAGE);
276        }
277        drop(stylo_element_data);
278
279        let Some(element) = node.element_data_mut() else {
280            return;
281        };
282
283        let removed_attr = element.attrs.remove(&name);
284        let had_attr = removed_attr.is_some();
285        if !had_attr {
286            return;
287        }
288
289        if name.local == local_name!("id") {
290            element.id = None;
291        }
292
293        // Update text input value
294        if name.local == local_name!("value") {
295            if let Some(input_data) = element.text_input_data_mut() {
296                input_data.set_text(
297                    &mut self.doc.font_ctx.lock().unwrap(),
298                    &mut self.doc.layout_ctx,
299                    "",
300                );
301            }
302        }
303
304        let tag = &element.name.local;
305        let attr = &name.local;
306        if *attr == local_name!("style") {
307            element.flush_style_attribute(&self.doc.guard, &self.doc.url.url_extra_data());
308        } else if (tag, attr) == tag_and_attr!("canvas", "src") {
309            self.recompute_is_animating = true;
310        } else if (tag, attr) == tag_and_attr!("link", "href") {
311            self.unload_stylesheet(node_id);
312        }
313    }
314
315    pub fn set_style_property(&mut self, node_id: usize, name: &str, value: &str) {
316        self.doc.set_style_property(node_id, name, value)
317    }
318
319    pub fn remove_style_property(&mut self, node_id: usize, name: &str) {
320        self.doc.remove_style_property(node_id, name)
321    }
322
323    /// Remove the node from it's parent but don't drop it
324    pub fn remove_node(&mut self, node_id: usize) {
325        let node = &mut self.doc.nodes[node_id];
326
327        // Update child_idx values
328        if let Some(parent_id) = node.parent.take() {
329            let parent = &mut self.doc.nodes[parent_id];
330            parent.insert_damage(ALL_DAMAGE);
331            parent.children.retain(|id| *id != node_id);
332            self.maybe_record_node(parent_id);
333        }
334
335        self.process_removed_subtree(node_id);
336    }
337
338    fn remove_node_ignoring_parent(&mut self, node_id: usize) -> Option<Node> {
339        let mut node = self.doc.nodes.try_remove(node_id);
340        if let Some(node) = &mut node {
341            for &child in &node.children {
342                self.remove_node_ignoring_parent(child);
343            }
344        }
345        node
346    }
347
348    pub fn remove_and_drop_node(&mut self, node_id: usize) -> Option<Node> {
349        self.process_removed_subtree(node_id);
350
351        let node = self.remove_node_ignoring_parent(node_id);
352
353        // Update child_idx values
354        if let Some(parent_id) = node.as_ref().and_then(|node| node.parent) {
355            let parent = &mut self.doc.nodes[parent_id];
356            parent.insert_damage(ALL_DAMAGE);
357            let parent_is_in_doc = parent.flags.is_in_document();
358
359            // TODO: make this fine grained / conditional based on ElementSelectorFlags
360            if parent_is_in_doc {
361                if let Some(data) = &mut *parent.stylo_element_data.borrow_mut() {
362                    data.hint |= RestyleHint::restyle_subtree();
363                }
364            }
365
366            parent.children.retain(|id| *id != node_id);
367            self.maybe_record_node(parent_id);
368        }
369
370        node
371    }
372
373    pub fn remove_and_drop_all_children(&mut self, node_id: usize) {
374        let parent = &mut self.doc.nodes[node_id];
375        let parent_is_in_doc = parent.flags.is_in_document();
376
377        // TODO: make this fine grained / conditional based on ElementSelectorFlags
378        if parent_is_in_doc {
379            if let Some(data) = &mut *parent.stylo_element_data.borrow_mut() {
380                data.hint |= RestyleHint::restyle_subtree();
381            }
382        }
383
384        let children = mem::take(&mut parent.children);
385        for child_id in children {
386            self.process_removed_subtree(child_id);
387            let _ = self.remove_node_ignoring_parent(child_id);
388        }
389        self.maybe_record_node(node_id);
390    }
391
392    // Tree mutation methods
393    pub fn remove_node_if_unparented(&mut self, node_id: usize) {
394        if let Some(node) = self.doc.get_node(node_id) {
395            if node.parent.is_none() {
396                self.remove_and_drop_node(node_id);
397            }
398        }
399    }
400
401    /// Remove all of the children from old_parent_id and append them to new_parent_id
402    pub fn append_children(&mut self, parent_id: usize, child_ids: &[usize]) {
403        self.add_children_to_parent(parent_id, child_ids, &|parent, child_ids| {
404            parent.children.extend_from_slice(child_ids);
405        });
406    }
407
408    pub fn insert_nodes_before(&mut self, anchor_node_id: usize, new_node_ids: &[usize]) {
409        let parent_id = self.doc.nodes[anchor_node_id].parent.unwrap();
410        self.add_children_to_parent(parent_id, new_node_ids, &|parent, child_ids| {
411            let node_child_idx = parent.index_of_child(anchor_node_id).unwrap();
412            parent
413                .children
414                .splice(node_child_idx..node_child_idx, child_ids.iter().copied());
415        });
416    }
417
418    fn add_children_to_parent(
419        &mut self,
420        parent_id: usize,
421        child_ids: &[usize],
422        insert_children_fn: &dyn Fn(&mut Node, &[usize]),
423    ) {
424        let new_parent = &mut self.doc.nodes[parent_id];
425        new_parent.insert_damage(ALL_DAMAGE);
426        let new_parent_is_in_doc = new_parent.flags.is_in_document();
427
428        // TODO: make this fine grained / conditional based on ElementSelectorFlags
429        if new_parent_is_in_doc {
430            if let Some(data) = &mut *new_parent.stylo_element_data.borrow_mut() {
431                data.hint |= RestyleHint::restyle_subtree();
432            }
433        }
434
435        insert_children_fn(new_parent, child_ids);
436
437        for child_id in child_ids.iter().copied() {
438            let child = &mut self.doc.nodes[child_id];
439            let old_parent_id = child.parent.replace(parent_id);
440
441            let child_was_in_doc = child.flags.is_in_document();
442            if new_parent_is_in_doc != child_was_in_doc {
443                self.process_added_subtree(child_id);
444            }
445
446            if let Some(old_parent_id) = old_parent_id {
447                let old_parent = &mut self.doc.nodes[old_parent_id];
448                old_parent.insert_damage(ALL_DAMAGE);
449
450                // TODO: make this fine grained / conditional based on ElementSelectorFlags
451                if child_was_in_doc {
452                    if let Some(data) = &mut *old_parent.stylo_element_data.borrow_mut() {
453                        data.hint |= RestyleHint::restyle_subtree();
454                    }
455                }
456
457                old_parent.children.retain(|id| *id != child_id);
458                self.maybe_record_node(old_parent_id);
459            }
460        }
461
462        self.maybe_record_node(parent_id);
463    }
464
465    // Tree mutation methods (that defer to other methods)
466    pub fn insert_nodes_after(&mut self, anchor_node_id: usize, new_node_ids: &[usize]) {
467        match self.next_sibling_id(anchor_node_id) {
468            Some(id) => self.insert_nodes_before(id, new_node_ids),
469            None => {
470                let parent_id = self.parent_id(anchor_node_id).unwrap();
471                self.append_children(parent_id, new_node_ids)
472            }
473        }
474    }
475
476    pub fn reparent_children(&mut self, old_parent_id: usize, new_parent_id: usize) {
477        let child_ids = std::mem::take(&mut self.doc.nodes[old_parent_id].children);
478        self.maybe_record_node(old_parent_id);
479        self.append_children(new_parent_id, &child_ids);
480    }
481
482    pub fn replace_node_with(&mut self, anchor_node_id: usize, new_node_ids: &[usize]) {
483        self.insert_nodes_before(anchor_node_id, new_node_ids);
484        self.remove_node(anchor_node_id);
485    }
486}
487
488impl<'doc> DocumentMutator<'doc> {
489    pub fn flush(&mut self) {
490        if self.recompute_is_animating {
491            self.doc.has_canvas = self.doc.compute_has_canvas();
492        }
493
494        if let Some(id) = self.title_node {
495            let title = self.doc.nodes[id].text_content();
496            self.doc.shell_provider.set_window_title(title);
497        }
498
499        // Add/Update inline stylesheets (<style> elements)
500        for id in self.style_nodes.drain() {
501            self.doc.process_style_element(id);
502        }
503
504        for id in self.form_nodes.drain() {
505            self.doc.reset_form_owner(id);
506        }
507
508        #[cfg(feature = "autofocus")]
509        if let Some(node_id) = self.node_to_autofocus.take() {
510            if self.doc.get_node(node_id).is_some() {
511                self.doc.set_focus_to(node_id);
512            }
513        }
514    }
515
516    pub fn set_inner_html(&mut self, node_id: usize, html: &str) {
517        self.remove_and_drop_all_children(node_id);
518        self.doc
519            .html_parser_provider
520            .clone()
521            .parse_inner_html(self, node_id, html);
522    }
523
524    fn flush_eager_ops(&mut self) {
525        let mut ops = mem::take(&mut self.eager_op_queue);
526        for op in ops.drain(0..) {
527            match op {
528                SpecialOp::LoadImage(node_id) => self.load_image(node_id),
529                SpecialOp::LoadStylesheet(node_id) => self.load_linked_stylesheet(node_id),
530                SpecialOp::UnloadStylesheet(node_id) => self.unload_stylesheet(node_id),
531                SpecialOp::LoadCustomPaintSource(node_id) => self.load_custom_paint_src(node_id),
532                SpecialOp::ProcessButtonInput(node_id) => self.process_button_input(node_id),
533            }
534        }
535
536        // Queue is empty, but put Vec back anyway so allocation can be reused.
537        self.eager_op_queue = ops;
538    }
539
540    fn process_added_subtree(&mut self, node_id: usize) {
541        self.doc.iter_subtree_mut(node_id, |node_id, doc| {
542            let node = &mut doc.nodes[node_id];
543            node.flags.set(NodeFlags::IS_IN_DOCUMENT, true);
544            node.insert_damage(ALL_DAMAGE);
545
546            // If the node has an "id" attribute, store it in the ID map.
547            if let Some(id_attr) = node.attr(local_name!("id")) {
548                doc.nodes_to_id.insert(id_attr.to_string(), node_id);
549            }
550
551            let NodeData::Element(ref mut element) = node.data else {
552                return;
553            };
554
555            // Custom post-processing by element tag name
556            let tag = element.name.local.as_ref();
557            match tag {
558                "title" => self.title_node = Some(node_id),
559                "link" => self.eager_op_queue.push(SpecialOp::LoadStylesheet(node_id)),
560                "img" => self.eager_op_queue.push(SpecialOp::LoadImage(node_id)),
561                "canvas" => self
562                    .eager_op_queue
563                    .push(SpecialOp::LoadCustomPaintSource(node_id)),
564                "style" => {
565                    self.style_nodes.insert(node_id);
566                }
567                "button" | "fieldset" | "input" | "select" | "textarea" | "object" | "output" => {
568                    self.eager_op_queue
569                        .push(SpecialOp::ProcessButtonInput(node_id));
570                    self.form_nodes.insert(node_id);
571                }
572                _ => {}
573            }
574
575            #[cfg(feature = "autofocus")]
576            if node.is_focussable() {
577                if let NodeData::Element(ref element) = node.data {
578                    if let Some(value) = element.attr(local_name!("autofocus")) {
579                        if value == "true" {
580                            self.node_to_autofocus = Some(node_id);
581                        }
582                    }
583                }
584            }
585        });
586
587        self.flush_eager_ops();
588    }
589
590    fn process_removed_subtree(&mut self, node_id: usize) {
591        self.doc.iter_subtree_mut(node_id, |node_id, doc| {
592            let node = &mut doc.nodes[node_id];
593            node.flags.set(NodeFlags::IS_IN_DOCUMENT, false);
594
595            // If the node has an "id" attribute remove it from the ID map.
596            if let Some(id_attr) = node.attr(local_name!("id")) {
597                doc.nodes_to_id.remove(id_attr);
598            }
599
600            let NodeData::Element(ref mut element) = node.data else {
601                return;
602            };
603
604            match &element.special_data {
605                SpecialElementData::Stylesheet(_) => self
606                    .eager_op_queue
607                    .push(SpecialOp::UnloadStylesheet(node_id)),
608                SpecialElementData::Image(_) => {}
609                SpecialElementData::Canvas(_) => {
610                    self.recompute_is_animating = true;
611                }
612                SpecialElementData::TableRoot(_) => {}
613                SpecialElementData::TextInput(_) => {}
614                SpecialElementData::CheckboxInput(_) => {}
615                #[cfg(feature = "file_input")]
616                SpecialElementData::FileInput(_) => {}
617                SpecialElementData::None => {}
618            }
619        });
620
621        self.flush_eager_ops();
622    }
623
624    fn maybe_record_node(&mut self, node_id: impl Into<Option<usize>>) {
625        let Some(node_id) = node_id.into() else {
626            return;
627        };
628
629        let Some(tag_name) = self.doc.nodes[node_id]
630            .data
631            .downcast_element()
632            .map(|elem| &elem.name.local)
633        else {
634            return;
635        };
636
637        match tag_name.as_ref() {
638            "title" => self.title_node = Some(node_id),
639            "style" => {
640                self.style_nodes.insert(node_id);
641            }
642            _ => {}
643        }
644    }
645
646    fn load_linked_stylesheet(&mut self, target_id: usize) {
647        let node = &self.doc.nodes[target_id];
648
649        let rel_attr = node.attr(local_name!("rel"));
650        let href_attr = node.attr(local_name!("href"));
651
652        let (Some(rels), Some(href)) = (rel_attr, href_attr) else {
653            return;
654        };
655        if !rels.split_ascii_whitespace().any(|rel| rel == "stylesheet") {
656            return;
657        }
658
659        let url = self.doc.resolve_url(href);
660        self.doc.net_provider.fetch(
661            self.doc.id(),
662            Request::get(url.clone()),
663            Box::new(CssHandler {
664                node: target_id,
665                source_url: url,
666                guard: self.doc.guard.clone(),
667                provider: self.doc.net_provider.clone(),
668            }),
669        );
670    }
671
672    fn unload_stylesheet(&mut self, node_id: usize) {
673        let node = &mut self.doc.nodes[node_id];
674        let Some(element) = node.element_data_mut() else {
675            unreachable!();
676        };
677        let SpecialElementData::Stylesheet(stylesheet) = element.special_data.take() else {
678            unreachable!();
679        };
680
681        let guard = self.doc.guard.read();
682        self.doc.stylist.remove_stylesheet(stylesheet, &guard);
683        self.doc
684            .stylist
685            .force_stylesheet_origins_dirty(OriginSet::all());
686
687        self.doc.nodes_to_stylesheet.remove(&node_id);
688    }
689
690    fn load_image(&mut self, target_id: usize) {
691        let node = &self.doc.nodes[target_id];
692        if let Some(raw_src) = node.attr(local_name!("src")) {
693            if !raw_src.is_empty() {
694                let src = self.doc.resolve_url(raw_src);
695                self.doc.net_provider.fetch(
696                    self.doc.id(),
697                    Request::get(src),
698                    Box::new(ImageHandler::new(target_id, ImageType::Image)),
699                );
700            }
701        }
702    }
703
704    fn load_custom_paint_src(&mut self, target_id: usize) {
705        let node = &mut self.doc.nodes[target_id];
706        if let Some(raw_src) = node.attr(local_name!("src")) {
707            if let Ok(custom_paint_source_id) = raw_src.parse::<u64>() {
708                self.recompute_is_animating = true;
709                let canvas_data = SpecialElementData::Canvas(CanvasData {
710                    custom_paint_source_id,
711                });
712                node.element_data_mut().unwrap().special_data = canvas_data;
713            }
714        }
715    }
716
717    fn process_button_input(&mut self, target_id: usize) {
718        let node = &self.doc.nodes[target_id];
719        let Some(data) = node.element_data() else {
720            return;
721        };
722
723        let tagname = data.name.local.as_ref();
724        let type_attr = data.attr(local_name!("type"));
725        let value = data.attr(local_name!("value"));
726
727        // Add content of "value" attribute as a text node child if:
728        //   - Tag name is
729        if let ("input", Some("button" | "submit" | "reset"), Some(value)) =
730            (tagname, type_attr, value)
731        {
732            let value = value.to_string();
733            let id = self.create_text_node(&value);
734            self.append_children(target_id, &[id]);
735            return;
736        }
737        #[cfg(feature = "file_input")]
738        if let ("input", Some("file")) = (tagname, type_attr) {
739            let button_id = self.create_element(
740                qual_name!("button", html),
741                vec![
742                    Attribute {
743                        name: qual_name!("type", html),
744                        value: "button".to_string(),
745                    },
746                    Attribute {
747                        name: qual_name!("tabindex", html),
748                        value: "-1".to_string(),
749                    },
750                ],
751            );
752            let label_id = self.create_element(qual_name!("label", html), vec![]);
753            let text_id = self.create_text_node("No File Selected");
754            let button_text_id = self.create_text_node("Browse");
755            self.append_children(target_id, &[button_id, label_id]);
756            self.append_children(label_id, &[text_id]);
757            self.append_children(button_id, &[button_text_id]);
758        }
759    }
760}
761
762/// Set 'checked' state on an input based on given attributevalue
763fn set_input_checked_state(element: &mut ElementData, value: String) {
764    let Ok(checked) = value.parse() else {
765        return;
766    };
767    match element.special_data {
768        SpecialElementData::CheckboxInput(ref mut checked_mut) => *checked_mut = checked,
769        // If we have just constructed the element, set the node attribute,
770        // and NodeSpecificData will be created from that later
771        // this simulates the checked attribute being set in html,
772        // and the element's checked property being set from that
773        SpecialElementData::None => element.attrs.push(Attribute {
774            name: qual_name!("checked", html),
775            value: checked.to_string(),
776        }),
777        _ => {}
778    }
779}
780
781/// Type that allows mutable access to the viewport
782/// And syncs it back to stylist on drop.
783pub struct ViewportMut<'doc> {
784    doc: &'doc mut BaseDocument,
785    initial_scale: f64,
786}
787impl ViewportMut<'_> {
788    pub fn new(doc: &mut BaseDocument) -> ViewportMut<'_> {
789        let initial_scale = doc.viewport.scale_f64();
790        ViewportMut { doc, initial_scale }
791    }
792}
793impl Deref for ViewportMut<'_> {
794    type Target = Viewport;
795
796    fn deref(&self) -> &Self::Target {
797        &self.doc.viewport
798    }
799}
800impl DerefMut for ViewportMut<'_> {
801    fn deref_mut(&mut self) -> &mut Self::Target {
802        &mut self.doc.viewport
803    }
804}
805impl Drop for ViewportMut<'_> {
806    fn drop(&mut self) {
807        self.doc
808            .set_stylist_device(make_device(&self.doc.viewport, self.doc.font_ctx.clone()));
809        self.doc.scroll_viewport_by(0.0, 0.0); // Clamp scroll offset
810
811        let scale_has_changed = self.doc.viewport().scale_f64() != self.initial_scale;
812        if scale_has_changed {
813            self.doc.invalidate_inline_contexts();
814        }
815    }
816}