blitz_dom/
document.rs

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