blitz_dom/
document.rs

1use crate::events::handle_event;
2use crate::layout::construct::collect_layout_children;
3use crate::mutator::ViewportMut;
4use crate::net::{Resource, StylesheetLoader};
5use crate::node::{ImageData, NodeFlags, RasterImageData, SpecialElementData, Status, TextBrush};
6use crate::stylo_to_cursor_icon::stylo_to_cursor_icon;
7use crate::traversal::TreeTraverser;
8use crate::util::{ImageType, resolve_url};
9use crate::{DocumentMutator, ElementData, Node, NodeData, TextNodeData};
10use app_units::Au;
11use blitz_traits::events::UiEvent;
12use blitz_traits::navigation::{DummyNavigationProvider, NavigationProvider};
13use blitz_traits::net::{DummyNetProvider, SharedProvider};
14use blitz_traits::shell::{DummyShellProvider, ShellProvider};
15use blitz_traits::{ColorScheme, Devtools, Viewport};
16use blitz_traits::{DomEvent, HitResult};
17use cursor_icon::CursorIcon;
18use markup5ever::local_name;
19use parley::FontContext;
20use peniko::{Blob, kurbo};
21use selectors::{Element, matching::QuirksMode};
22use slab::Slab;
23use std::any::Any;
24use std::collections::{BTreeMap, Bound, HashMap, HashSet};
25use std::ops::{Deref, DerefMut};
26use std::sync::Arc;
27use std::sync::atomic::{AtomicUsize, Ordering};
28use style::Atom;
29use style::attr::{AttrIdentifier, AttrValue};
30use style::data::{ElementData as StyloElementData, ElementStyles};
31use style::media_queries::MediaType;
32use style::properties::ComputedValues;
33use style::properties::style_structs::Font;
34use style::queries::values::PrefersColorScheme;
35use style::selector_parser::ServoElementSnapshot;
36use style::servo::media_queries::FontMetricsProvider;
37use style::servo_arc::Arc as ServoArc;
38use style::values::GenericAtomIdent;
39use style::values::computed::Overflow;
40use style::{
41    dom::{TDocument, TNode},
42    media_queries::{Device, MediaList},
43    selector_parser::SnapshotMap,
44    shared_lock::{SharedRwLock, StylesheetGuards},
45    stylesheets::{AllowImportRules, DocumentStyleSheet, Origin, Stylesheet, UrlExtraData},
46    stylist::Stylist,
47};
48use taffy::AvailableSpace;
49use url::Url;
50
51pub trait Document: Deref<Target = BaseDocument> + DerefMut + 'static {
52    fn poll(&mut self, cx: std::task::Context) -> bool {
53        // Default implementation does nothing
54        let _ = cx;
55        false
56    }
57
58    fn handle_event(&mut self, event: UiEvent) {
59        // Default implementation does nothing
60        let _ = event;
61    }
62
63    fn as_any_mut(&mut self) -> &mut dyn Any;
64
65    fn id(&self) -> usize;
66}
67
68// TODO: implement a proper font metrics provider
69#[derive(Debug, Clone)]
70struct DummyFontMetricsProvider;
71impl FontMetricsProvider for DummyFontMetricsProvider {
72    fn query_font_metrics(
73        &self,
74        _vertical: bool,
75        _font: &Font,
76        _base_size: style::values::computed::CSSPixelLength,
77        _flags: style::values::computed::font::QueryFontMetricsFlags,
78    ) -> style::font_metrics::FontMetrics {
79        Default::default()
80    }
81
82    fn base_size_for_generic(
83        &self,
84        generic: style::values::computed::font::GenericFontFamily,
85    ) -> style::values::computed::Length {
86        let size = match generic {
87            style::values::computed::font::GenericFontFamily::Monospace => 13.0,
88            _ => 16.0,
89        };
90        style::values::computed::Length::from(Au::from_f32_px(size))
91    }
92}
93
94pub struct BaseDocument {
95    id: usize,
96
97    /// A bump-backed tree
98    ///
99    /// Both taffy and stylo traits are implemented for this.
100    /// We pin the tree to a guarantee to the nodes it creates that the tree is stable in memory.
101    ///
102    /// There is no way to create the tree - publicly or privately - that would invalidate that invariant.
103    pub nodes: Box<Slab<Node>>,
104
105    // TODO: encapsulate and make private again
106    pub guard: SharedRwLock,
107
108    /// The styling engine of firefox
109    pub(crate) stylist: Stylist,
110
111    // caching for the stylist
112    pub(crate) snapshots: SnapshotMap,
113
114    // TODO: encapsulate and make private again
115    pub nodes_to_id: HashMap<String, usize>,
116
117    /// Base url for resolving linked resources (stylesheets, images, fonts, etc)
118    pub base_url: Option<url::Url>,
119
120    // /// The quadtree we use for hit-testing
121    // pub(crate) quadtree: Quadtree<u64, usize>,
122
123    // Viewport details such as the dimensions, HiDPI scale, and zoom factor,
124    pub(crate) viewport: Viewport,
125    // Devtool settings. Currently used to render debug overlays
126    pub(crate) devtool_settings: Devtools,
127
128    // Scroll within our viewport
129    pub(crate) viewport_scroll: kurbo::Point,
130
131    /// Stylesheets added by the useragent
132    /// where the key is the hashed CSS
133    pub(crate) ua_stylesheets: HashMap<String, DocumentStyleSheet>,
134
135    pub(crate) nodes_to_stylesheet: BTreeMap<usize, DocumentStyleSheet>,
136
137    /// A Parley font context
138    pub font_ctx: parley::FontContext,
139
140    /// A Parley layout context
141    pub layout_ctx: parley::LayoutContext<TextBrush>,
142
143    /// The node which is currently hovered (if any)
144    pub(crate) hover_node_id: Option<usize>,
145    /// The node which is currently focussed (if any)
146    pub(crate) focus_node_id: Option<usize>,
147    /// The node which is currently active (if any)
148    pub(crate) active_node_id: Option<usize>,
149    /// The node which recieved a mousedown event (if any)
150    pub(crate) mousedown_node_id: Option<usize>,
151    /// Whether there are active animations (so we should re-render every frame)
152    pub(crate) is_animating: bool,
153
154    pub changed: HashSet<usize>,
155
156    /// A map from control node ID's to their associated forms node ID's
157    pub controls_to_form: HashMap<usize, usize>,
158
159    /// Network provider. Can be used to fetch assets.
160    pub net_provider: SharedProvider<Resource>,
161
162    /// Navigation provider. Can be used to navigate to a new page (bubbles up the event
163    /// on e.g. clicking a Link)
164    pub navigation_provider: Arc<dyn NavigationProvider>,
165
166    /// Shell provider. Can be used to request a redraw or set the cursor icon
167    pub shell_provider: Arc<dyn ShellProvider>,
168}
169
170pub(crate) fn make_device(viewport: &Viewport) -> Device {
171    let width = viewport.window_size.0 as f32 / viewport.scale();
172    let height = viewport.window_size.1 as f32 / viewport.scale();
173    let viewport_size = euclid::Size2D::new(width, height);
174    let device_pixel_ratio = euclid::Scale::new(viewport.scale());
175
176    Device::new(
177        MediaType::screen(),
178        selectors::matching::QuirksMode::NoQuirks,
179        viewport_size,
180        device_pixel_ratio,
181        Box::new(DummyFontMetricsProvider),
182        ComputedValues::initial_values_with_font_override(Font::initial_values()),
183        match viewport.color_scheme {
184            ColorScheme::Light => PrefersColorScheme::Light,
185            ColorScheme::Dark => PrefersColorScheme::Dark,
186        },
187    )
188}
189
190impl BaseDocument {
191    pub fn handle_event<F: FnMut(DomEvent)>(&mut self, event: &mut DomEvent, dispatch_event: F) {
192        handle_event(self, event, dispatch_event)
193    }
194    pub fn as_any_mut(&mut self) -> &mut dyn Any {
195        self
196    }
197}
198
199impl BaseDocument {
200    pub fn new(viewport: Viewport) -> Self {
201        Self::with_font_ctx(viewport, parley::FontContext::default())
202    }
203
204    pub fn with_font_ctx(viewport: Viewport, mut font_ctx: FontContext) -> Self {
205        static ID_GENERATOR: AtomicUsize = AtomicUsize::new(1);
206
207        let id = ID_GENERATOR.fetch_add(1, Ordering::SeqCst);
208        let device = make_device(&viewport);
209        let stylist = Stylist::new(device, QuirksMode::NoQuirks);
210        let snapshots = SnapshotMap::new();
211        let nodes = Box::new(Slab::new());
212        let guard = SharedRwLock::new();
213        let nodes_to_id = HashMap::new();
214
215        // Make sure we turn on stylo features
216        style_config::set_bool("layout.flexbox.enabled", true);
217        style_config::set_bool("layout.grid.enabled", true);
218        style_config::set_bool("layout.legacy_layout", true);
219        style_config::set_bool("layout.unimplemented", true);
220        style_config::set_bool("layout.columns.enabled", true);
221
222        font_ctx
223            .collection
224            .register_fonts(Blob::new(Arc::new(crate::BULLET_FONT) as _), None);
225
226        let mut doc = Self {
227            id,
228            guard,
229            nodes,
230            stylist,
231            snapshots,
232            nodes_to_id,
233            viewport,
234            devtool_settings: Devtools::default(),
235            viewport_scroll: kurbo::Point::ZERO,
236            base_url: None,
237            // quadtree: Quadtree::new(20),
238            ua_stylesheets: HashMap::new(),
239            nodes_to_stylesheet: BTreeMap::new(),
240            font_ctx,
241            layout_ctx: parley::LayoutContext::new(),
242
243            hover_node_id: None,
244            focus_node_id: None,
245            active_node_id: None,
246            mousedown_node_id: None,
247            is_animating: false,
248            changed: HashSet::new(),
249            controls_to_form: HashMap::new(),
250            net_provider: Arc::new(DummyNetProvider),
251            navigation_provider: Arc::new(DummyNavigationProvider),
252            shell_provider: Arc::new(DummyShellProvider),
253        };
254
255        // Initialise document with root Document node
256        doc.create_node(NodeData::Document);
257        doc.root_node_mut().flags.insert(NodeFlags::IS_IN_DOCUMENT);
258
259        // Stylo data on the root node container is needed to render the node
260        let stylo_element_data = StyloElementData {
261            styles: ElementStyles {
262                primary: Some(
263                    ComputedValues::initial_values_with_font_override(Font::initial_values())
264                        .to_arc(),
265                ),
266                ..Default::default()
267            },
268            ..Default::default()
269        };
270        *doc.root_node().stylo_element_data.borrow_mut() = Some(stylo_element_data);
271
272        doc
273    }
274
275    /// Set the Document's networking provider
276    pub fn set_net_provider(&mut self, net_provider: SharedProvider<Resource>) {
277        self.net_provider = net_provider;
278    }
279
280    /// Set the Document's navigation provider
281    pub fn set_navigation_provider(&mut self, navigation_provider: Arc<dyn NavigationProvider>) {
282        self.navigation_provider = navigation_provider;
283    }
284
285    /// Set the Document's shell provider
286    pub fn set_shell_provider(&mut self, shell_provider: Arc<dyn ShellProvider>) {
287        self.shell_provider = shell_provider;
288    }
289
290    /// Set base url for resolving linked resources (stylesheets, images, fonts, etc)
291    pub fn set_base_url(&mut self, url: &str) {
292        self.base_url = Some(Url::parse(url).unwrap());
293    }
294
295    pub fn guard(&self) -> &SharedRwLock {
296        &self.guard
297    }
298
299    pub fn tree(&self) -> &Slab<Node> {
300        &self.nodes
301    }
302
303    pub fn id(&self) -> usize {
304        self.id
305    }
306
307    pub fn get_node(&self, node_id: usize) -> Option<&Node> {
308        self.nodes.get(node_id)
309    }
310
311    pub fn get_node_mut(&mut self, node_id: usize) -> Option<&mut Node> {
312        self.nodes.get_mut(node_id)
313    }
314
315    pub fn get_focussed_node_id(&self) -> Option<usize> {
316        self.focus_node_id
317            .or(self.try_root_element().map(|el| el.id))
318    }
319
320    pub fn mutate<'doc>(&'doc mut self) -> DocumentMutator<'doc> {
321        DocumentMutator::new(self)
322    }
323
324    /// Find the label's bound input elements:
325    /// the element id referenced by the "for" attribute of a given label element
326    /// or the first input element which is nested in the label
327    /// Note that although there should only be one bound element,
328    /// we return all possibilities instead of just the first
329    /// in order to allow the caller to decide which one is correct
330    pub fn label_bound_input_element(&self, label_node_id: usize) -> Option<&Node> {
331        let label_element = self.nodes[label_node_id].element_data()?;
332        if let Some(target_element_dom_id) = label_element.attr(local_name!("for")) {
333            TreeTraverser::new(self)
334                .filter_map(|id| {
335                    let node = self.get_node(id)?;
336                    let element_data = node.element_data()?;
337                    if element_data.name.local != local_name!("input") {
338                        return None;
339                    }
340                    let id = element_data.id.as_ref()?;
341                    if *id == *target_element_dom_id {
342                        Some(node)
343                    } else {
344                        None
345                    }
346                })
347                .next()
348        } else {
349            TreeTraverser::new_with_root(self, label_node_id)
350                .filter_map(|child_id| {
351                    let node = self.get_node(child_id)?;
352                    let element_data = node.element_data()?;
353                    if element_data.name.local == local_name!("input") {
354                        Some(node)
355                    } else {
356                        None
357                    }
358                })
359                .next()
360        }
361    }
362
363    pub fn toggle_checkbox(el: &mut ElementData) -> bool {
364        let Some(is_checked) = el.checkbox_input_checked_mut() else {
365            return false;
366        };
367        *is_checked = !*is_checked;
368
369        *is_checked
370    }
371
372    pub fn toggle_radio(&mut self, radio_set_name: String, target_radio_id: usize) {
373        for i in 0..self.nodes.len() {
374            let node = &mut self.nodes[i];
375            if let Some(node_data) = node.data.downcast_element_mut() {
376                if node_data.attr(local_name!("name")) == Some(&radio_set_name) {
377                    let was_clicked = i == target_radio_id;
378                    let Some(is_checked) = node_data.checkbox_input_checked_mut() else {
379                        continue;
380                    };
381                    *is_checked = was_clicked;
382                }
383            }
384        }
385    }
386
387    pub fn root_node(&self) -> &Node {
388        &self.nodes[0]
389    }
390
391    pub fn root_node_mut(&mut self) -> &mut Node {
392        &mut self.nodes[0]
393    }
394
395    pub fn try_root_element(&self) -> Option<&Node> {
396        TDocument::as_node(&self.root_node()).first_element_child()
397    }
398
399    pub fn root_element(&self) -> &Node {
400        TDocument::as_node(&self.root_node())
401            .first_element_child()
402            .unwrap()
403            .as_element()
404            .unwrap()
405    }
406
407    pub fn create_node(&mut self, node_data: NodeData) -> usize {
408        let slab_ptr = self.nodes.as_mut() as *mut Slab<Node>;
409        let guard = self.guard.clone();
410
411        let entry = self.nodes.vacant_entry();
412        let id = entry.key();
413        entry.insert(Node::new(slab_ptr, id, guard, node_data));
414
415        // Mark the new node as changed.
416        self.changed.insert(id);
417        id
418    }
419
420    pub fn create_text_node(&mut self, text: &str) -> usize {
421        let content = text.to_string();
422        let data = NodeData::Text(TextNodeData::new(content));
423        self.create_node(data)
424    }
425
426    pub fn deep_clone_node(&mut self, node_id: usize) -> usize {
427        // Load existing node
428        let node = &self.nodes[node_id];
429        let data = node.data.clone();
430        let children = node.children.clone();
431
432        // Create new node
433        let new_node_id = self.create_node(data);
434
435        // Recursively clone children
436        let new_children: Vec<usize> = children
437            .into_iter()
438            .map(|child_id| self.deep_clone_node(child_id))
439            .collect();
440        for &child_id in &new_children {
441            self.nodes[child_id].parent = Some(new_node_id);
442        }
443        self.nodes[new_node_id].children = new_children;
444
445        new_node_id
446    }
447
448    pub(crate) fn remove_and_drop_pe(&mut self, node_id: usize) -> Option<Node> {
449        fn remove_pe_ignoring_parent(doc: &mut BaseDocument, node_id: usize) -> Option<Node> {
450            let mut node = doc.nodes.try_remove(node_id);
451            if let Some(node) = &mut node {
452                for &child in &node.children {
453                    remove_pe_ignoring_parent(doc, child);
454                }
455            }
456            node
457        }
458
459        let node = remove_pe_ignoring_parent(self, node_id);
460
461        // Update child_idx values
462        if let Some(parent_id) = node.as_ref().and_then(|node| node.parent) {
463            let parent = &mut self.nodes[parent_id];
464            parent.children.retain(|id| *id != node_id);
465        }
466
467        node
468    }
469
470    pub fn resolve_url(&self, raw: &str) -> url::Url {
471        resolve_url(&self.base_url, raw).unwrap_or_else(|| {
472            panic!(
473                "to be able to resolve {raw} with the base_url: {base_url:?}",
474                base_url = self.base_url
475            )
476        })
477    }
478
479    pub fn print_tree(&self) {
480        crate::util::walk_tree(0, self.root_node());
481    }
482
483    pub fn print_subtree(&self, node_id: usize) {
484        crate::util::walk_tree(0, &self.nodes[node_id]);
485    }
486
487    pub fn process_style_element(&mut self, target_id: usize) {
488        let css = self.nodes[target_id].text_content();
489        let css = html_escape::decode_html_entities(&css);
490        let sheet = self.make_stylesheet(&css, Origin::Author);
491        self.add_stylesheet_for_node(sheet, target_id);
492    }
493
494    pub fn remove_user_agent_stylesheet(&mut self, contents: &str) {
495        if let Some(sheet) = self.ua_stylesheets.remove(contents) {
496            self.stylist.remove_stylesheet(sheet, &self.guard.read());
497        }
498    }
499
500    pub fn add_user_agent_stylesheet(&mut self, css: &str) {
501        let sheet = self.make_stylesheet(css, Origin::UserAgent);
502        self.ua_stylesheets.insert(css.to_string(), sheet.clone());
503        self.stylist.append_stylesheet(sheet, &self.guard.read());
504    }
505
506    pub fn make_stylesheet(&self, css: impl AsRef<str>, origin: Origin) -> DocumentStyleSheet {
507        let data = Stylesheet::from_str(
508            css.as_ref(),
509            UrlExtraData::from(self.base_url.clone().unwrap_or_else(|| {
510                "data:text/css;charset=utf-8;base64,"
511                    .parse::<Url>()
512                    .unwrap()
513            })),
514            origin,
515            ServoArc::new(self.guard.wrap(MediaList::empty())),
516            self.guard.clone(),
517            Some(&StylesheetLoader(self.id, self.net_provider.clone())),
518            None,
519            QuirksMode::NoQuirks,
520            AllowImportRules::Yes,
521        );
522
523        DocumentStyleSheet(ServoArc::new(data))
524    }
525
526    pub fn upsert_stylesheet_for_node(&mut self, node_id: usize) {
527        let raw_styles = self.nodes[node_id].text_content();
528        let sheet = self.make_stylesheet(raw_styles, Origin::Author);
529        self.add_stylesheet_for_node(sheet, node_id);
530    }
531
532    pub fn add_stylesheet_for_node(&mut self, stylesheet: DocumentStyleSheet, node_id: usize) {
533        let old = self.nodes_to_stylesheet.insert(node_id, stylesheet.clone());
534
535        if let Some(old) = old {
536            self.stylist.remove_stylesheet(old, &self.guard.read())
537        }
538
539        // Store data on element
540        let element = &mut self.nodes[node_id].element_data_mut().unwrap();
541        element.special_data = SpecialElementData::Stylesheet(stylesheet.clone());
542
543        // TODO: Nodes could potentially get reused so ordering by node_id might be wrong.
544        let insertion_point = self
545            .nodes_to_stylesheet
546            .range((Bound::Excluded(node_id), Bound::Unbounded))
547            .next()
548            .map(|(_, sheet)| sheet);
549
550        if let Some(insertion_point) = insertion_point {
551            self.stylist.insert_stylesheet_before(
552                stylesheet,
553                insertion_point.clone(),
554                &self.guard.read(),
555            )
556        } else {
557            self.stylist
558                .append_stylesheet(stylesheet, &self.guard.read())
559        }
560    }
561
562    pub fn load_resource(&mut self, resource: Resource) {
563        match resource {
564            Resource::Css(node_id, css) => {
565                self.add_stylesheet_for_node(css, node_id);
566            }
567            Resource::Image(node_id, kind, width, height, image_data) => {
568                let node = self.get_node_mut(node_id).unwrap();
569
570                match kind {
571                    ImageType::Image => {
572                        node.element_data_mut().unwrap().special_data =
573                            SpecialElementData::Image(Box::new(ImageData::Raster(
574                                RasterImageData::new(width, height, image_data),
575                            )));
576
577                        // Clear layout cache
578                        node.cache.clear();
579                    }
580                    ImageType::Background(idx) => {
581                        if let Some(Some(bg_image)) = node
582                            .element_data_mut()
583                            .and_then(|el| el.background_images.get_mut(idx))
584                        {
585                            bg_image.status = Status::Ok;
586                            bg_image.image =
587                                ImageData::Raster(RasterImageData::new(width, height, image_data))
588                        }
589                    }
590                }
591            }
592            #[cfg(feature = "svg")]
593            Resource::Svg(node_id, kind, tree) => {
594                let node = self.get_node_mut(node_id).unwrap();
595
596                match kind {
597                    ImageType::Image => {
598                        node.element_data_mut().unwrap().special_data =
599                            SpecialElementData::Image(Box::new(ImageData::Svg(tree)));
600
601                        // Clear layout cache
602                        node.cache.clear();
603                    }
604                    ImageType::Background(idx) => {
605                        if let Some(Some(bg_image)) = node
606                            .element_data_mut()
607                            .and_then(|el| el.background_images.get_mut(idx))
608                        {
609                            bg_image.status = Status::Ok;
610                            bg_image.image = ImageData::Svg(tree);
611                        }
612                    }
613                }
614            }
615            Resource::Font(bytes) => {
616                // TODO: Implement FontInfoOveride
617                // TODO: Investigate eliminating double-box
618                self.font_ctx
619                    .collection
620                    .register_fonts(Blob::new(Arc::new(bytes)) as _, None);
621            }
622            Resource::None => {
623                // Do nothing
624            }
625            _ => {}
626        }
627    }
628
629    pub fn snapshot_node(&mut self, node_id: usize) {
630        let node = &mut self.nodes[node_id];
631        let opaque_node_id = TNode::opaque(&&*node);
632        node.has_snapshot = true;
633        node.snapshot_handled
634            .store(false, std::sync::atomic::Ordering::SeqCst);
635
636        // TODO: handle invalidations other than hover
637        if let Some(_existing_snapshot) = self.snapshots.get_mut(&opaque_node_id) {
638            // Do nothing
639            // TODO: update snapshot
640        } else {
641            let attrs: Option<Vec<_>> = node.attrs().map(|attrs| {
642                attrs
643                    .iter()
644                    .map(|attr| {
645                        let ident = AttrIdentifier {
646                            local_name: GenericAtomIdent(attr.name.local.clone()),
647                            name: GenericAtomIdent(attr.name.local.clone()),
648                            namespace: GenericAtomIdent(attr.name.ns.clone()),
649                            prefix: None,
650                        };
651
652                        let value = if attr.name.local == local_name!("id") {
653                            AttrValue::Atom(Atom::from(&*attr.value))
654                        } else if attr.name.local == local_name!("class") {
655                            let classes = attr
656                                .value
657                                .split_ascii_whitespace()
658                                .map(Atom::from)
659                                .collect();
660                            AttrValue::TokenList(attr.value.clone(), classes)
661                        } else {
662                            AttrValue::String(attr.value.clone())
663                        };
664
665                        (ident, value)
666                    })
667                    .collect()
668            });
669
670            let changed_attrs = attrs
671                .as_ref()
672                .map(|attrs| attrs.iter().map(|attr| attr.0.name.clone()).collect())
673                .unwrap_or_default();
674
675            self.snapshots.insert(
676                opaque_node_id,
677                ServoElementSnapshot {
678                    state: Some(node.element_state),
679                    attrs,
680                    changed_attrs,
681                    class_changed: true,
682                    id_changed: true,
683                    other_attributes_changed: true,
684                },
685            );
686        }
687    }
688
689    pub fn snapshot_node_and(&mut self, node_id: usize, cb: impl FnOnce(&mut Node)) {
690        self.snapshot_node(node_id);
691        cb(&mut self.nodes[node_id]);
692    }
693
694    /// Restyle the tree and then relayout it
695    pub fn resolve(&mut self) {
696        if TDocument::as_node(&&self.nodes[0])
697            .first_element_child()
698            .is_none()
699        {
700            println!("No DOM - not resolving");
701            return;
702        }
703
704        // we need to resolve stylist first since it will need to drive our layout bits
705        self.resolve_stylist();
706
707        // Fix up tree for layout (insert anonymous blocks as necessary, etc)
708        self.resolve_layout_children();
709
710        // Merge stylo into taffy
711        self.flush_styles_to_layout(self.root_element().id);
712
713        // Next we resolve layout with the data resolved by stlist
714        self.resolve_layout();
715    }
716
717    // Takes (x, y) co-ordinates (relative to the )
718    pub fn hit(&self, x: f32, y: f32) -> Option<HitResult> {
719        if TDocument::as_node(&&self.nodes[0])
720            .first_element_child()
721            .is_none()
722        {
723            println!("No DOM - not resolving");
724            return None;
725        }
726
727        self.root_element().hit(x, y)
728    }
729
730    pub fn focus_next_node(&mut self) -> Option<usize> {
731        let focussed_node_id = self.get_focussed_node_id()?;
732        let id = self.next_node(&self.nodes[focussed_node_id], |node| node.is_focussable())?;
733        self.set_focus_to(id);
734        Some(id)
735    }
736
737    /// Clear the focussed node
738    pub fn clear_focus(&mut self) {
739        if let Some(id) = self.focus_node_id {
740            self.snapshot_node_and(id, |node| node.blur());
741            self.focus_node_id = None;
742        }
743    }
744
745    pub fn set_mousedown_node_id(&mut self, node_id: Option<usize>) {
746        self.mousedown_node_id = node_id;
747    }
748    pub fn set_focus_to(&mut self, focus_node_id: usize) -> bool {
749        if Some(focus_node_id) == self.focus_node_id {
750            return false;
751        }
752
753        println!("Focussed node {focus_node_id}");
754
755        // Remove focus from the old node
756        if let Some(id) = self.focus_node_id {
757            self.snapshot_node_and(id, |node| node.blur());
758        }
759
760        // Focus the new node
761        self.snapshot_node_and(focus_node_id, |node| node.focus());
762
763        self.focus_node_id = Some(focus_node_id);
764
765        true
766    }
767
768    pub fn active_node(&mut self) -> bool {
769        let Some(hover_node_id) = self.get_hover_node_id() else {
770            return false;
771        };
772
773        if let Some(active_node_id) = self.active_node_id {
774            if active_node_id == hover_node_id {
775                return true;
776            }
777            self.unactive_node();
778        }
779
780        let active_node_id = Some(hover_node_id);
781
782        let node_path = self.maybe_node_layout_ancestors(active_node_id);
783        for &id in node_path.iter() {
784            self.snapshot_node_and(id, |node| node.active());
785        }
786
787        self.active_node_id = active_node_id;
788
789        true
790    }
791
792    pub fn unactive_node(&mut self) -> bool {
793        let Some(active_node_id) = self.active_node_id.take() else {
794            return false;
795        };
796
797        let node_path = self.maybe_node_layout_ancestors(Some(active_node_id));
798        for &id in node_path.iter() {
799            self.snapshot_node_and(id, |node| node.unactive());
800        }
801
802        true
803    }
804
805    pub fn set_hover_to(&mut self, x: f32, y: f32) -> bool {
806        let hit = self.hit(x, y);
807        let hover_node_id = hit.map(|hit| hit.node_id);
808
809        // Return early if the new node is the same as the already-hovered node
810        if hover_node_id == self.hover_node_id {
811            return false;
812        }
813
814        let old_node_path = self.maybe_node_layout_ancestors(self.hover_node_id);
815        let new_node_path = self.maybe_node_layout_ancestors(hover_node_id);
816        let same_count = old_node_path
817            .iter()
818            .zip(&new_node_path)
819            .take_while(|(o, n)| o == n)
820            .count();
821        for &id in old_node_path.iter().skip(same_count) {
822            self.snapshot_node_and(id, |node| node.unhover());
823        }
824        for &id in new_node_path.iter().skip(same_count) {
825            self.snapshot_node_and(id, |node| node.hover());
826        }
827
828        self.hover_node_id = hover_node_id;
829
830        // Update the cursor
831        let cursor = self.get_cursor().unwrap_or_default();
832        self.shell_provider.set_cursor(cursor);
833
834        // Request redraw
835        self.shell_provider.request_redraw();
836
837        true
838    }
839
840    pub fn get_hover_node_id(&self) -> Option<usize> {
841        self.hover_node_id
842    }
843
844    pub fn set_viewport(&mut self, viewport: Viewport) {
845        self.viewport = viewport;
846        self.set_stylist_device(make_device(&self.viewport));
847        self.scroll_viewport_by(0.0, 0.0); // Clamp scroll offset
848    }
849
850    pub fn viewport(&self) -> &Viewport {
851        &self.viewport
852    }
853
854    pub fn viewport_mut(&mut self) -> ViewportMut<'_> {
855        ViewportMut::new(self)
856    }
857
858    pub fn zoom_by(&mut self, increment: f32) {
859        *self.viewport.zoom_mut() += increment;
860        self.set_viewport(self.viewport.clone());
861    }
862
863    pub fn zoom_to(&mut self, zoom: f32) {
864        *self.viewport.zoom_mut() = zoom;
865        self.set_viewport(self.viewport.clone());
866    }
867
868    pub fn get_viewport(&self) -> Viewport {
869        self.viewport.clone()
870    }
871
872    pub fn devtools(&self) -> &Devtools {
873        &self.devtool_settings
874    }
875
876    pub fn devtools_mut(&mut self) -> &mut Devtools {
877        &mut self.devtool_settings
878    }
879
880    pub fn is_animating(&self) -> bool {
881        self.is_animating
882    }
883
884    /// Update the device and reset the stylist to process the new size
885    pub fn set_stylist_device(&mut self, device: Device) {
886        let origins = {
887            let guard = &self.guard;
888            let guards = StylesheetGuards {
889                author: &guard.read(),
890                ua_or_user: &guard.read(),
891            };
892            self.stylist.set_device(device, &guards)
893        };
894        self.stylist.force_stylesheet_origins_dirty(origins);
895    }
896
897    pub fn stylist_device(&mut self) -> &Device {
898        self.stylist.device()
899    }
900
901    /// Ensure that the layout_children field is populated for all nodes
902    pub fn resolve_layout_children(&mut self) {
903        resolve_layout_children_recursive(self, self.root_node().id);
904
905        fn resolve_layout_children_recursive(doc: &mut BaseDocument, node_id: usize) {
906            // if doc.nodes[node_id].layout_children.borrow().is_none() {
907            let mut layout_children = Vec::new();
908            let mut anonymous_block: Option<usize> = None;
909            collect_layout_children(doc, node_id, &mut layout_children, &mut anonymous_block);
910
911            // Recurse into newly collected layout children
912            for child_id in layout_children.iter().copied() {
913                resolve_layout_children_recursive(doc, child_id);
914                doc.nodes[child_id].layout_parent.set(Some(node_id));
915            }
916
917            *doc.nodes[node_id].layout_children.borrow_mut() = Some(layout_children.clone());
918            *doc.nodes[node_id].paint_children.borrow_mut() = Some(layout_children);
919            // }
920        }
921    }
922
923    /// Walk the nodes now that they're properly styled and transfer their styles to the taffy style system
924    ///
925    /// TODO: update taffy to use an associated type instead of slab key
926    /// TODO: update taffy to support traited styles so we don't even need to rely on taffy for storage
927    pub fn resolve_layout(&mut self) {
928        let size = self.stylist.device().au_viewport_size();
929
930        let available_space = taffy::Size {
931            width: AvailableSpace::Definite(size.width.to_f32_px()),
932            height: AvailableSpace::Definite(size.height.to_f32_px()),
933        };
934
935        let root_element_id = taffy::NodeId::from(self.root_element().id);
936
937        // println!("\n\nRESOLVE LAYOUT\n===========\n");
938
939        taffy::compute_root_layout(self, root_element_id, available_space);
940        taffy::round_layout(self, root_element_id);
941
942        // println!("\n\n");
943        // taffy::print_tree(self, root_node_id)
944    }
945
946    pub fn get_cursor(&self) -> Option<CursorIcon> {
947        // todo: cache this on the node itself
948        let node = &self.nodes[self.get_hover_node_id()?];
949
950        let style = node.primary_styles()?;
951        let keyword = stylo_to_cursor_icon(style.clone_cursor().keyword);
952
953        // Return cursor from style if it is non-auto
954        if keyword != CursorIcon::Default {
955            return Some(keyword);
956        }
957
958        // Return text cursor for text nodes and text inputs
959        if node.is_text_node()
960            || node
961                .element_data()
962                .is_some_and(|e| e.text_input_data().is_some())
963        {
964            return Some(CursorIcon::Text);
965        }
966
967        // Use "pointer" cursor if any ancestor is a link
968        let mut maybe_node = Some(node);
969        while let Some(node) = maybe_node {
970            if node.is_link() {
971                return Some(CursorIcon::Pointer);
972            }
973
974            maybe_node = node.layout_parent.get().map(|node_id| node.with(node_id));
975        }
976
977        // Else fallback to default cursor
978        Some(CursorIcon::Default)
979    }
980
981    /// Scroll a node by given x and y
982    /// Will bubble scrolling up to parent node once it can no longer scroll further
983    /// If we're already at the root node, bubbles scrolling up to the viewport
984    pub fn scroll_node_by(&mut self, node_id: usize, x: f64, y: f64) {
985        let Some(node) = self.nodes.get_mut(node_id) else {
986            return;
987        };
988
989        let is_html_or_body = node.data.downcast_element().is_some_and(|e| {
990            let tag = &e.name.local;
991            tag == "html" || tag == "body"
992        });
993
994        let (can_x_scroll, can_y_scroll) = node
995            .primary_styles()
996            .map(|styles| {
997                (
998                    matches!(styles.clone_overflow_x(), Overflow::Scroll | Overflow::Auto),
999                    matches!(styles.clone_overflow_y(), Overflow::Scroll | Overflow::Auto)
1000                        || (styles.clone_overflow_y() == Overflow::Visible && is_html_or_body),
1001                )
1002            })
1003            .unwrap_or((false, false));
1004
1005        let new_x = node.scroll_offset.x - x;
1006        let new_y = node.scroll_offset.y - y;
1007
1008        let mut bubble_x = 0.0;
1009        let mut bubble_y = 0.0;
1010
1011        let scroll_width = node.final_layout.scroll_width() as f64;
1012        let scroll_height = node.final_layout.scroll_height() as f64;
1013
1014        // If we're past our scroll bounds, transfer remainder of scrolling to parent/viewport
1015        if !can_x_scroll {
1016            bubble_x = x
1017        } else if new_x < 0.0 {
1018            bubble_x = -new_x;
1019            node.scroll_offset.x = 0.0;
1020        } else if new_x > scroll_width {
1021            bubble_x = scroll_width - new_x;
1022            node.scroll_offset.x = scroll_width;
1023        } else {
1024            node.scroll_offset.x = new_x;
1025        }
1026
1027        if !can_y_scroll {
1028            bubble_y = y
1029        } else if new_y < 0.0 {
1030            bubble_y = -new_y;
1031            node.scroll_offset.y = 0.0;
1032        } else if new_y > scroll_height {
1033            bubble_y = scroll_height - new_y;
1034            node.scroll_offset.y = scroll_height;
1035        } else {
1036            node.scroll_offset.y = new_y;
1037        }
1038
1039        if bubble_x != 0.0 || bubble_y != 0.0 {
1040            if let Some(parent) = node.parent {
1041                self.scroll_node_by(parent, bubble_x, bubble_y);
1042            } else {
1043                self.scroll_viewport_by(bubble_x, bubble_y);
1044            }
1045        }
1046    }
1047
1048    /// Scroll the viewport by the given values
1049    pub fn scroll_viewport_by(&mut self, x: f64, y: f64) {
1050        let content_size = self.root_element().final_layout.size;
1051        let new_scroll = (self.viewport_scroll.x - x, self.viewport_scroll.y - y);
1052        let window_width = self.viewport.window_size.0 as f64 / self.viewport.scale() as f64;
1053        let window_height = self.viewport.window_size.1 as f64 / self.viewport.scale() as f64;
1054        self.viewport_scroll.x = f64::max(
1055            0.0,
1056            f64::min(new_scroll.0, content_size.width as f64 - window_width),
1057        );
1058        self.viewport_scroll.y = f64::max(
1059            0.0,
1060            f64::min(new_scroll.1, content_size.height as f64 - window_height),
1061        )
1062    }
1063
1064    pub fn viewport_scroll(&self) -> kurbo::Point {
1065        self.viewport_scroll
1066    }
1067
1068    pub fn set_viewport_scroll(&mut self, scroll: kurbo::Point) {
1069        self.viewport_scroll = scroll;
1070    }
1071
1072    pub fn find_title_node(&self) -> Option<&Node> {
1073        TreeTraverser::new(self)
1074            .find(|node_id| {
1075                self.nodes[*node_id]
1076                    .data
1077                    .is_element_with_tag_name(&local_name!("title"))
1078            })
1079            .map(|node_id| &self.nodes[node_id])
1080    }
1081
1082    pub(crate) fn compute_is_animating(&self) -> bool {
1083        TreeTraverser::new(self).any(|node_id| {
1084            let node = &self.nodes[node_id];
1085            let Some(element) = node.element_data() else {
1086                return false;
1087            };
1088            if element.name.local == local_name!("canvas") && element.has_attr(local_name!("src")) {
1089                return true;
1090            }
1091
1092            false
1093        })
1094    }
1095}
1096
1097impl AsRef<BaseDocument> for BaseDocument {
1098    fn as_ref(&self) -> &BaseDocument {
1099        self
1100    }
1101}
1102
1103impl AsMut<BaseDocument> for BaseDocument {
1104    fn as_mut(&mut self) -> &mut BaseDocument {
1105        self
1106    }
1107}