blitz_dom/
document.rs

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