Skip to main content

blitz_dom/
document.rs

1use crate::events::{DragMode, ScrollAnimationState, handle_dom_event};
2use crate::font_metrics::BlitzFontMetricsProvider;
3use crate::layout::construct::ConstructionTask;
4use crate::layout::damage::ALL_DAMAGE;
5use crate::mutator::ViewportMut;
6use crate::net::{
7    Resource, ResourceHandler, ResourceLoadResponse, StylesheetHandler, StylesheetLoader,
8};
9use crate::node::{ImageData, NodeFlags, RasterImageData, SpecialElementData, Status, TextBrush};
10use crate::selection::TextSelection;
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, Node, NodeData, NoopEventHandler, TextNodeData,
18};
19use blitz_traits::devtools::DevtoolSettings;
20use blitz_traits::events::{BlitzScrollEvent, DomEvent, DomEventData, HitResult, UiEvent};
21use blitz_traits::navigation::{DummyNavigationProvider, NavigationProvider};
22use blitz_traits::net::{DummyNetProvider, NetProvider, Request};
23use blitz_traits::shell::{ColorScheme, DummyShellProvider, ShellProvider, Viewport};
24use cursor_icon::CursorIcon;
25use linebender_resource_handle::Blob;
26use markup5ever::local_name;
27use parley::{FontContext, PlainEditorDriver};
28use selectors::{Element, matching::QuirksMode};
29use slab::Slab;
30use std::any::Any;
31use std::cell::RefCell;
32use std::collections::{BTreeMap, Bound, HashMap, HashSet};
33use std::ops::{Deref, DerefMut};
34use std::rc::Rc;
35use std::str::FromStr;
36use std::sync::atomic::{AtomicUsize, Ordering};
37use std::sync::mpsc::{Receiver, Sender, channel};
38use std::sync::{Arc, Mutex, MutexGuard, RwLockReadGuard, RwLockWriteGuard};
39use std::task::Context as TaskContext;
40use std::time::Instant;
41use style::Atom;
42use style::animation::DocumentAnimationSet;
43use style::attr::{AttrIdentifier, AttrValue};
44use style::data::{ElementData as StyloElementData, ElementStyles};
45use style::media_queries::MediaType;
46use style::properties::ComputedValues;
47use style::properties::style_structs::Font;
48use style::queries::values::PrefersColorScheme;
49use style::selector_parser::ServoElementSnapshot;
50use style::servo_arc::Arc as ServoArc;
51use style::values::GenericAtomIdent;
52use style::values::computed::Overflow;
53use style::{
54    device::Device,
55    dom::{TDocument, TNode},
56    media_queries::MediaList,
57    selector_parser::SnapshotMap,
58    shared_lock::{SharedRwLock, StylesheetGuards},
59    stylesheets::{AllowImportRules, DocumentStyleSheet, Origin, Stylesheet},
60    stylist::Stylist,
61};
62use url::Url;
63
64#[cfg(feature = "parallel-construct")]
65use thread_local::ThreadLocal;
66
67pub enum DocGuard<'a> {
68    Ref(&'a BaseDocument),
69    RefCell(std::cell::Ref<'a, BaseDocument>),
70    RwLock(RwLockReadGuard<'a, BaseDocument>),
71    Mutex(MutexGuard<'a, BaseDocument>),
72}
73
74impl Deref for DocGuard<'_> {
75    type Target = BaseDocument;
76    #[inline(always)]
77    fn deref(&self) -> &Self::Target {
78        match self {
79            Self::Ref(base_document) => base_document,
80            Self::RefCell(refcell_guard) => refcell_guard,
81            Self::RwLock(rw_lock_read_guard) => rw_lock_read_guard,
82            Self::Mutex(mutex_guard) => mutex_guard,
83        }
84    }
85}
86
87pub enum DocGuardMut<'a> {
88    Ref(&'a mut BaseDocument),
89    RefCell(std::cell::RefMut<'a, BaseDocument>),
90    RwLock(RwLockWriteGuard<'a, BaseDocument>),
91    Mutex(MutexGuard<'a, BaseDocument>),
92}
93
94impl Deref for DocGuardMut<'_> {
95    type Target = BaseDocument;
96    #[inline(always)]
97    fn deref(&self) -> &Self::Target {
98        match self {
99            Self::Ref(base_document) => base_document,
100            Self::RefCell(refcell_guard) => refcell_guard,
101            Self::RwLock(rw_lock_read_guard) => rw_lock_read_guard,
102            Self::Mutex(mutex_guard) => mutex_guard,
103        }
104    }
105}
106
107impl DerefMut for DocGuardMut<'_> {
108    #[inline(always)]
109    fn deref_mut(&mut self) -> &mut Self::Target {
110        match self {
111            Self::Ref(base_document) => base_document,
112            Self::RefCell(refcell_guard) => &mut *refcell_guard,
113            Self::RwLock(rw_lock_read_guard) => &mut *rw_lock_read_guard,
114            Self::Mutex(mutex_guard) => &mut *mutex_guard,
115        }
116    }
117}
118
119/// Abstraction over wrappers around [`BaseDocument`] to allow for them all to
120/// be driven by [`blitz-shell`](https://docs.rs/blitz-shell)
121pub trait Document: Any + 'static {
122    fn inner(&self) -> DocGuard<'_>;
123    fn inner_mut(&mut self) -> DocGuardMut<'_>;
124
125    /// Update the [`Document`] in response to a [`UiEvent`] (click, keypress, etc)
126    fn handle_ui_event(&mut self, event: UiEvent) {
127        let mut doc = self.inner_mut();
128        let mut driver = EventDriver::new(&mut *doc, NoopEventHandler);
129        driver.handle_ui_event(event);
130    }
131
132    /// Poll any pending async operations, and flush changes to the underlying [`BaseDocument`]
133    fn poll(&mut self, task_context: Option<TaskContext>) -> bool {
134        // Default implementation does nothing
135        let _ = task_context;
136        false
137    }
138
139    /// Get the [`Document`]'s id
140    fn id(&self) -> usize {
141        self.inner().id
142    }
143}
144
145pub struct PlainDocument(pub BaseDocument);
146impl Document for PlainDocument {
147    fn inner(&self) -> DocGuard<'_> {
148        DocGuard::Ref(&self.0)
149    }
150    fn inner_mut(&mut self) -> DocGuardMut<'_> {
151        DocGuardMut::Ref(&mut self.0)
152    }
153}
154
155impl Document for BaseDocument {
156    fn inner(&self) -> DocGuard<'_> {
157        DocGuard::Ref(self)
158    }
159    fn inner_mut(&mut self) -> DocGuardMut<'_> {
160        DocGuardMut::Ref(self)
161    }
162}
163
164impl Document for Rc<RefCell<BaseDocument>> {
165    fn inner(&self) -> DocGuard<'_> {
166        DocGuard::RefCell(self.borrow())
167    }
168
169    fn inner_mut(&mut self) -> DocGuardMut<'_> {
170        DocGuardMut::RefCell(self.borrow_mut())
171    }
172}
173
174pub enum DocumentEvent {
175    ResourceLoad(ResourceLoadResponse),
176}
177
178pub struct BaseDocument {
179    /// ID of the document
180    id: usize,
181
182    // Config
183    /// Base url for resolving linked resources (stylesheets, images, fonts, etc)
184    pub(crate) url: DocumentUrl,
185    // Devtool settings. Currently used to render debug overlays
186    pub(crate) devtool_settings: DevtoolSettings,
187    // Viewport details such as the dimensions, HiDPI scale, and zoom factor,
188    pub(crate) viewport: Viewport,
189    // Scroll within our viewport
190    pub(crate) viewport_scroll: crate::Point<f64>,
191    /// CSS media type used to evaluate `@media` rules.
192    pub(crate) media_type: MediaType,
193
194    // Events
195    pub(crate) tx: Sender<DocumentEvent>,
196    // rx will always be Some, except temporarily while processing events
197    pub(crate) rx: Option<Receiver<DocumentEvent>>,
198
199    /// A slab-backed tree of nodes
200    ///
201    /// We pin the tree to a guarantee to the nodes it creates that the tree is stable in memory.
202    /// There is no way to create the tree - publicly or privately - that would invalidate that invariant.
203    pub(crate) nodes: Box<Slab<Node>>,
204
205    // Stylo
206    /// The Stylo engine
207    pub(crate) stylist: Stylist,
208    pub(crate) animations: DocumentAnimationSet,
209    /// Stylo shared lock
210    pub(crate) guard: SharedRwLock,
211    /// Stylo invalidation map. We insert into this map prior to mutating nodes.
212    pub(crate) snapshots: SnapshotMap,
213
214    // Parley contexts
215    /// A Parley font context
216    pub(crate) font_ctx: Arc<Mutex<parley::FontContext>>,
217    #[cfg(feature = "parallel-construct")]
218    /// Thread-and-document-local copies to the font context
219    pub(crate) thread_font_contexts: ThreadLocal<RefCell<Box<FontContext>>>,
220    /// A Parley layout context
221    pub(crate) layout_ctx: parley::LayoutContext<TextBrush>,
222
223    /// The node which is currently hovered (if any)
224    pub(crate) hover_node_id: Option<usize>,
225    /// Whether the node which is currently hovered is a text node/span
226    pub(crate) hover_node_is_text: bool,
227    /// The node which is currently focussed (if any)
228    pub(crate) focus_node_id: Option<usize>,
229    /// The node which is currently active (if any)
230    pub(crate) active_node_id: Option<usize>,
231    /// The node which recieved a mousedown event (if any)
232    pub(crate) mousedown_node_id: Option<usize>,
233    /// The last time a mousedown was made (for double-click detection)
234    pub(crate) last_mousedown_time: Option<Instant>,
235    /// The position where mousedown occurred (for selection drags and double-click detection)
236    pub(crate) mousedown_position: taffy::Point<f32>,
237    /// How many clicks have been made in quick succession
238    pub(crate) click_count: u16,
239    /// Whether we're currently in a text selection drag (moved 2px+ from mousedown)
240    pub(crate) drag_mode: DragMode,
241    /// Whether and what kind of scroll animation is currently in progress
242    pub(crate) scroll_animation: ScrollAnimationState,
243
244    /// Text selection state (for non-input text)
245    pub(crate) text_selection: TextSelection,
246
247    // TODO: collapse animating state into a bitflags
248    /// Whether there are active CSS animations/transitions (so we should re-render every frame)
249    pub(crate) has_active_animations: bool,
250    /// Whether there is a `<canvas>` element in the DOM (so we should re-render every frame)
251    pub(crate) has_canvas: bool,
252    /// Whether there are subdocuments that are animating (so we should re-render every frame)
253    pub(crate) subdoc_is_animating: bool,
254
255    /// Map of node ID's for fast lookups
256    pub(crate) nodes_to_id: HashMap<String, usize>,
257    /// Map of `<style>` and `<link>` node IDs to their associated stylesheet
258    pub(crate) nodes_to_stylesheet: BTreeMap<usize, DocumentStyleSheet>,
259    /// Stylesheets added by the useragent
260    /// where the key is the hashed CSS
261    pub(crate) ua_stylesheets: HashMap<String, DocumentStyleSheet>,
262    /// Map from form control node ID's to their associated forms node ID's
263    pub(crate) controls_to_form: HashMap<usize, usize>,
264    /// Nodes that contain sub documents
265    pub(crate) sub_document_nodes: HashSet<usize>,
266    /// Set of changed nodes for updating the accessibility tree
267    pub(crate) changed_nodes: HashSet<usize>,
268    /// Set of changed nodes for updating the accessibility tree
269    pub(crate) deferred_construction_nodes: Vec<ConstructionTask>,
270
271    /// Cache of loaded images, keyed by URL. Allows reusing images across multiple
272    /// elements without re-fetching from the network.
273    pub(crate) image_cache: HashMap<String, ImageData>,
274
275    /// Tracks in-flight image requests. When an image is being fetched, additional
276    /// requests for the same URL are queued here instead of starting new fetches.
277    /// Value is a list of (node_id, image_type) pairs waiting for the image.
278    pub(crate) pending_images: HashMap<String, Vec<(usize, ImageType)>>,
279
280    // Tracks in-flight "critical" resources (e.g. stylesheets linked from the `<head>`)
281    pub(crate) pending_critical_resources: HashSet<usize>,
282
283    // Service providers
284    /// Network provider. Can be used to fetch assets.
285    pub net_provider: Arc<dyn NetProvider>,
286    /// Navigation provider. Can be used to navigate to a new page (bubbles up the event
287    /// on e.g. clicking a Link)
288    pub navigation_provider: Arc<dyn NavigationProvider>,
289    /// Shell provider. Can be used to request a redraw or set the cursor icon
290    pub shell_provider: Arc<dyn ShellProvider>,
291    /// HTML parser provider. Used to parse HTML for setInnerHTML
292    pub html_parser_provider: Arc<dyn HtmlParserProvider>,
293}
294
295pub(crate) fn make_device(
296    viewport: &Viewport,
297    media_type: MediaType,
298    font_ctx: Arc<Mutex<FontContext>>,
299) -> Device {
300    let width = viewport.window_size.0 as f32 / viewport.scale();
301    let height = viewport.window_size.1 as f32 / viewport.scale();
302    let viewport_size = euclid::Size2D::new(width, height);
303    let device_pixel_ratio = euclid::Scale::new(viewport.scale());
304
305    Device::new(
306        media_type,
307        selectors::matching::QuirksMode::NoQuirks,
308        viewport_size,
309        device_pixel_ratio,
310        Box::new(BlitzFontMetricsProvider { font_ctx }),
311        ComputedValues::initial_values_with_font_override(Font::initial_values()),
312        match viewport.color_scheme {
313            ColorScheme::Light => PrefersColorScheme::Light,
314            ColorScheme::Dark => PrefersColorScheme::Dark,
315        },
316    )
317}
318
319impl BaseDocument {
320    /// Create a new (empty) [`BaseDocument`] with the specified configuration
321    pub fn new(config: DocumentConfig) -> Self {
322        static ID_GENERATOR: AtomicUsize = AtomicUsize::new(1);
323
324        let id = ID_GENERATOR.fetch_add(1, Ordering::SeqCst);
325
326        let font_ctx = config
327            .font_ctx
328            .map(|mut font_ctx| {
329                font_ctx.source_cache.make_shared();
330                // font_ctx.collection.make_shared();
331                font_ctx
332            })
333            .unwrap_or_else(|| {
334                use parley::fontique::{Collection, CollectionOptions, SourceCache};
335                let mut font_ctx = FontContext {
336                    source_cache: SourceCache::new_shared(),
337                    collection: Collection::new(CollectionOptions {
338                        shared: false,
339                        system_fonts: true,
340                    }),
341                };
342                font_ctx
343                    .collection
344                    .register_fonts(Blob::new(Arc::new(crate::BULLET_FONT) as _), None);
345                font_ctx
346            });
347        let font_ctx = Arc::new(Mutex::new(font_ctx));
348
349        // Make sure we turn on stylo features *before* creating the Stylist
350        style_config::set_pref!("layout.grid.enabled", true);
351        style_config::set_pref!("layout.unimplemented", true);
352        style_config::set_pref!("layout.columns.enabled", true);
353        style_config::set_pref!("layout.threads", -1);
354
355        let viewport = config.viewport.unwrap_or_default();
356        let media_type = config.media_type.unwrap_or_else(MediaType::screen);
357        let device = make_device(&viewport, media_type.clone(), font_ctx.clone());
358        let stylist = Stylist::new(device, QuirksMode::NoQuirks);
359        let snapshots = SnapshotMap::new();
360        let nodes = Box::new(Slab::new());
361        let guard = SharedRwLock::new();
362        let nodes_to_id = HashMap::new();
363
364        let base_url = config
365            .base_url
366            .and_then(|url| DocumentUrl::from_str(&url).ok())
367            .unwrap_or_default();
368
369        let net_provider = config
370            .net_provider
371            .unwrap_or_else(|| Arc::new(DummyNetProvider));
372        let navigation_provider = config
373            .navigation_provider
374            .unwrap_or_else(|| Arc::new(DummyNavigationProvider));
375        let shell_provider = config
376            .shell_provider
377            .unwrap_or_else(|| Arc::new(DummyShellProvider));
378        let html_parser_provider = config
379            .html_parser_provider
380            .unwrap_or_else(|| Arc::new(DummyHtmlParserProvider));
381
382        let (tx, rx) = channel();
383
384        let mut doc = Self {
385            id,
386            tx,
387            rx: Some(rx),
388
389            guard,
390            nodes,
391            stylist,
392            animations: DocumentAnimationSet::default(),
393            snapshots,
394            nodes_to_id,
395            viewport,
396            media_type,
397            devtool_settings: DevtoolSettings::default(),
398            viewport_scroll: crate::Point::ZERO,
399            url: base_url,
400            ua_stylesheets: HashMap::new(),
401            nodes_to_stylesheet: BTreeMap::new(),
402            font_ctx,
403            #[cfg(feature = "parallel-construct")]
404            thread_font_contexts: ThreadLocal::new(),
405            layout_ctx: parley::LayoutContext::new(),
406
407            hover_node_id: None,
408            hover_node_is_text: false,
409            focus_node_id: None,
410            active_node_id: None,
411            mousedown_node_id: None,
412            has_active_animations: false,
413            subdoc_is_animating: false,
414            has_canvas: false,
415            sub_document_nodes: HashSet::new(),
416            changed_nodes: HashSet::new(),
417            deferred_construction_nodes: Vec::new(),
418            image_cache: HashMap::new(),
419            pending_images: HashMap::new(),
420            pending_critical_resources: HashSet::new(),
421            controls_to_form: HashMap::new(),
422            net_provider,
423            navigation_provider,
424            shell_provider,
425            html_parser_provider,
426            last_mousedown_time: None,
427            mousedown_position: taffy::Point::ZERO,
428            click_count: 0,
429            drag_mode: DragMode::None,
430            scroll_animation: ScrollAnimationState::None,
431            text_selection: TextSelection::default(),
432        };
433
434        // Initialise document with root Document node
435        doc.create_node(NodeData::Document);
436        doc.root_node_mut().flags.insert(NodeFlags::IS_IN_DOCUMENT);
437
438        match config.ua_stylesheets {
439            Some(stylesheets) => {
440                for ss in &stylesheets {
441                    doc.add_user_agent_stylesheet(ss);
442                }
443            }
444            None => doc.add_user_agent_stylesheet(DEFAULT_CSS),
445        }
446
447        // Stylo data on the root node container is needed to render the node
448        let stylo_element_data = StyloElementData {
449            styles: ElementStyles {
450                primary: Some(
451                    ComputedValues::initial_values_with_font_override(Font::initial_values())
452                        .to_arc(),
453                ),
454                ..Default::default()
455            },
456            ..Default::default()
457        };
458        let stylo_data = &mut doc.root_node_mut().stylo_element_data;
459        *stylo_data.ensure_init_mut() = stylo_element_data;
460
461        doc
462    }
463
464    /// Set the Document's networking provider
465    pub fn set_net_provider(&mut self, net_provider: Arc<dyn NetProvider>) {
466        self.net_provider = net_provider;
467    }
468
469    /// Set the Document's navigation provider
470    pub fn set_navigation_provider(&mut self, navigation_provider: Arc<dyn NavigationProvider>) {
471        self.navigation_provider = navigation_provider;
472    }
473
474    /// Set the Document's shell provider
475    pub fn set_shell_provider(&mut self, shell_provider: Arc<dyn ShellProvider>) {
476        self.shell_provider = shell_provider;
477    }
478
479    /// Set the Document's html parser provider
480    pub fn set_html_parser_provider(&mut self, html_parser_provider: Arc<dyn HtmlParserProvider>) {
481        self.html_parser_provider = html_parser_provider;
482    }
483
484    /// Set base url for resolving linked resources (stylesheets, images, fonts, etc)
485    pub fn set_base_url(&mut self, url: &str) {
486        self.url = DocumentUrl::from(Url::parse(url).unwrap());
487    }
488
489    pub fn guard(&self) -> &SharedRwLock {
490        &self.guard
491    }
492
493    pub fn tree(&self) -> &Slab<Node> {
494        &self.nodes
495    }
496
497    pub fn id(&self) -> usize {
498        self.id
499    }
500
501    pub fn get_node(&self, node_id: usize) -> Option<&Node> {
502        self.nodes.get(node_id)
503    }
504
505    pub fn get_node_mut(&mut self, node_id: usize) -> Option<&mut Node> {
506        self.nodes.get_mut(node_id)
507    }
508
509    pub fn get_focussed_node_id(&self) -> Option<usize> {
510        self.focus_node_id
511            .or(self.try_root_element().map(|el| el.id))
512    }
513
514    pub fn mutate<'doc>(&'doc mut self) -> DocumentMutator<'doc> {
515        DocumentMutator::new(self)
516    }
517
518    pub fn handle_dom_event<F: FnMut(DomEvent)>(
519        &mut self,
520        event: &mut DomEvent,
521        dispatch_event: F,
522    ) {
523        handle_dom_event(self, event, dispatch_event)
524    }
525
526    pub fn as_any_mut(&mut self) -> &mut dyn Any {
527        self
528    }
529
530    /// Find the label's bound input elements:
531    /// the element id referenced by the "for" attribute of a given label element
532    /// or the first input element which is nested in the label
533    /// Note that although there should only be one bound element,
534    /// we return all possibilities instead of just the first
535    /// in order to allow the caller to decide which one is correct
536    pub fn label_bound_input_element(&self, label_node_id: usize) -> Option<&Node> {
537        let label_element = self.nodes[label_node_id].element_data()?;
538        if let Some(target_element_dom_id) = label_element.attr(local_name!("for")) {
539            TreeTraverser::new(self)
540                .filter_map(|id| {
541                    let node = self.get_node(id)?;
542                    let element_data = node.element_data()?;
543                    if element_data.name.local != local_name!("input") {
544                        return None;
545                    }
546                    let id = element_data.id.as_ref()?;
547                    if *id == *target_element_dom_id {
548                        Some(node)
549                    } else {
550                        None
551                    }
552                })
553                .next()
554        } else {
555            TreeTraverser::new_with_root(self, label_node_id)
556                .filter_map(|child_id| {
557                    let node = self.get_node(child_id)?;
558                    let element_data = node.element_data()?;
559                    if element_data.name.local == local_name!("input") {
560                        Some(node)
561                    } else {
562                        None
563                    }
564                })
565                .next()
566        }
567    }
568
569    pub fn toggle_checkbox(el: &mut ElementData) -> bool {
570        let Some(is_checked) = el.checkbox_input_checked_mut() else {
571            return false;
572        };
573        *is_checked = !*is_checked;
574
575        *is_checked
576    }
577
578    pub fn toggle_radio(&mut self, radio_set_name: String, target_radio_id: usize) {
579        for i in 0..self.nodes.len() {
580            let node = &mut self.nodes[i];
581            if let Some(node_data) = node.data.downcast_element_mut() {
582                if node_data.attr(local_name!("name")) == Some(&radio_set_name) {
583                    let was_clicked = i == target_radio_id;
584                    let Some(is_checked) = node_data.checkbox_input_checked_mut() else {
585                        continue;
586                    };
587                    *is_checked = was_clicked;
588                }
589            }
590        }
591    }
592
593    pub fn set_style_property(&mut self, node_id: usize, name: &str, value: &str) {
594        let node = &mut self.nodes[node_id];
595        let did_change = node.element_data_mut().unwrap().set_style_property(
596            name,
597            value,
598            &self.guard,
599            self.url.url_extra_data(),
600        );
601        if did_change {
602            node.mark_style_attr_updated();
603        }
604    }
605
606    pub fn remove_style_property(&mut self, node_id: usize, name: &str) {
607        let node = &mut self.nodes[node_id];
608        let did_change = node.element_data_mut().unwrap().remove_style_property(
609            name,
610            &self.guard,
611            self.url.url_extra_data(),
612        );
613        if did_change {
614            node.mark_style_attr_updated();
615        }
616    }
617
618    pub fn set_sub_document(&mut self, node_id: usize, sub_document: Box<dyn Document>) {
619        self.nodes[node_id]
620            .element_data_mut()
621            .unwrap()
622            .set_sub_document(sub_document);
623        self.sub_document_nodes.insert(node_id);
624    }
625
626    pub fn remove_sub_document(&mut self, node_id: usize) {
627        self.nodes[node_id]
628            .element_data_mut()
629            .unwrap()
630            .remove_sub_document();
631        self.sub_document_nodes.remove(&node_id);
632    }
633
634    pub fn root_node(&self) -> &Node {
635        &self.nodes[0]
636    }
637
638    pub fn root_node_mut(&mut self) -> &mut Node {
639        &mut self.nodes[0]
640    }
641
642    pub fn try_root_element(&self) -> Option<&Node> {
643        TDocument::as_node(&self.root_node()).first_element_child()
644    }
645
646    pub fn root_element(&self) -> &Node {
647        TDocument::as_node(&self.root_node())
648            .first_element_child()
649            .unwrap()
650            .as_element()
651            .unwrap()
652    }
653
654    pub fn create_node(&mut self, node_data: NodeData) -> usize {
655        let slab_ptr = self.nodes.as_mut() as *mut Slab<Node>;
656        let guard = self.guard.clone();
657
658        let entry = self.nodes.vacant_entry();
659        let id = entry.key();
660        entry.insert(Node::new(slab_ptr, id, guard, node_data));
661
662        // Mark the new node as changed.
663        self.changed_nodes.insert(id);
664        id
665    }
666
667    pub(crate) fn drop_node_ignoring_parent(&mut self, node_id: usize) -> Option<Node> {
668        let mut node = self.nodes.try_remove(node_id);
669        if let Some(node) = &mut node {
670            if let Some(before) = node.before {
671                self.drop_node_ignoring_parent(before);
672            }
673            if let Some(after) = node.after {
674                self.drop_node_ignoring_parent(after);
675            }
676
677            for &child in &node.children {
678                self.drop_node_ignoring_parent(child);
679            }
680        }
681        node
682    }
683
684    /// Whether the document has been mutated
685    pub fn has_changes(&self) -> bool {
686        self.changed_nodes.is_empty()
687    }
688
689    pub fn create_text_node(&mut self, text: &str) -> usize {
690        let content = text.to_string();
691        let data = NodeData::Text(TextNodeData::new(content));
692        self.create_node(data)
693    }
694
695    pub fn deep_clone_node(&mut self, node_id: usize) -> usize {
696        // Load existing node
697        let node = &self.nodes[node_id];
698        let data = node.data.clone();
699        let children = node.children.clone();
700
701        // Create new node
702        let new_node_id = self.create_node(data);
703
704        // Recursively clone children
705        let new_children: Vec<usize> = children
706            .into_iter()
707            .map(|child_id| self.deep_clone_node(child_id))
708            .collect();
709        for &child_id in &new_children {
710            self.nodes[child_id].parent = Some(new_node_id);
711        }
712        self.nodes[new_node_id].children = new_children;
713
714        new_node_id
715    }
716
717    pub(crate) fn remove_and_drop_pe(&mut self, node_id: usize) -> Option<Node> {
718        fn remove_pe_ignoring_parent(doc: &mut BaseDocument, node_id: usize) -> Option<Node> {
719            let mut node = doc.nodes.try_remove(node_id);
720            if let Some(node) = &mut node {
721                for &child in &node.children {
722                    remove_pe_ignoring_parent(doc, child);
723                }
724            }
725            node
726        }
727
728        let node = remove_pe_ignoring_parent(self, node_id);
729
730        // Update child_idx values
731        if let Some(parent_id) = node.as_ref().and_then(|node| node.parent) {
732            let parent = &mut self.nodes[parent_id];
733            parent.children.retain(|id| *id != node_id);
734        }
735
736        node
737    }
738
739    pub(crate) fn resolve_url(&self, raw: &str) -> url::Url {
740        self.url.resolve_relative(raw).unwrap_or_else(|| {
741            panic!(
742                "to be able to resolve {raw} with the base_url: {:?}",
743                *self.url
744            )
745        })
746    }
747
748    pub fn print_tree(&self) {
749        crate::util::walk_tree(0, self.root_node());
750    }
751
752    pub fn print_subtree(&self, node_id: usize) {
753        crate::util::walk_tree(0, &self.nodes[node_id]);
754    }
755
756    pub fn reload_resource_by_href(&mut self, href_to_reload: &str) {
757        for &node_id in self.nodes_to_stylesheet.keys() {
758            let node = &self.nodes[node_id];
759            let Some(element) = node.element_data() else {
760                continue;
761            };
762
763            if element.name.local == local_name!("link") {
764                if let Some(href) = element.attr(local_name!("href")) {
765                    // println!("Node {node_id} {href} {href_to_reload} {} {}", resolved_href.as_str(), resolved_href.as_str() == url_to_reload);
766                    if href == href_to_reload {
767                        let resolved_href = self.resolve_url(href);
768                        self.net_provider.fetch(
769                            self.id(),
770                            Request::get(resolved_href.clone()),
771                            ResourceHandler::boxed(
772                                self.tx.clone(),
773                                self.id,
774                                Some(node_id),
775                                self.shell_provider.clone(),
776                                StylesheetHandler {
777                                    source_url: resolved_href,
778                                    guard: self.guard.clone(),
779                                    net_provider: self.net_provider.clone(),
780                                },
781                            ),
782                        );
783                    }
784                }
785            }
786        }
787    }
788
789    pub fn process_style_element(&mut self, target_id: usize) {
790        let css = self.nodes[target_id].text_content();
791        let css = html_escape::decode_html_entities(&css);
792        let sheet = self.make_stylesheet(&css, Origin::Author);
793        self.add_stylesheet_for_node(sheet, target_id);
794    }
795
796    pub fn remove_user_agent_stylesheet(&mut self, contents: &str) {
797        if let Some(sheet) = self.ua_stylesheets.remove(contents) {
798            self.stylist.remove_stylesheet(sheet, &self.guard.read());
799        }
800    }
801
802    pub fn add_user_agent_stylesheet(&mut self, css: &str) {
803        let sheet = self.make_stylesheet(css, Origin::UserAgent);
804        self.ua_stylesheets.insert(css.to_string(), sheet.clone());
805        self.stylist.append_stylesheet(sheet, &self.guard.read());
806    }
807
808    pub fn make_stylesheet(&self, css: impl AsRef<str>, origin: Origin) -> DocumentStyleSheet {
809        let data = Stylesheet::from_str(
810            css.as_ref(),
811            self.url.url_extra_data(),
812            origin,
813            ServoArc::new(self.guard.wrap(MediaList::empty())),
814            self.guard.clone(),
815            Some(&StylesheetLoader {
816                tx: self.tx.clone(),
817                doc_id: self.id,
818                net_provider: self.net_provider.clone(),
819                shell_provider: self.shell_provider.clone(),
820            }),
821            None,
822            QuirksMode::NoQuirks,
823            AllowImportRules::Yes,
824        );
825
826        DocumentStyleSheet(ServoArc::new(data))
827    }
828
829    pub fn upsert_stylesheet_for_node(&mut self, node_id: usize) {
830        let raw_styles = self.nodes[node_id].text_content();
831        let sheet = self.make_stylesheet(raw_styles, Origin::Author);
832        self.add_stylesheet_for_node(sheet, node_id);
833    }
834
835    pub fn add_stylesheet_for_node(&mut self, stylesheet: DocumentStyleSheet, node_id: usize) {
836        let old = self.nodes_to_stylesheet.insert(node_id, stylesheet.clone());
837
838        if let Some(old) = old {
839            self.stylist.remove_stylesheet(old, &self.guard.read())
840        }
841
842        // Fetch @font-face fonts
843        crate::net::fetch_font_face(
844            self.tx.clone(),
845            self.id,
846            Some(node_id),
847            &stylesheet.0,
848            &self.net_provider,
849            &self.shell_provider,
850            &self.guard.read(),
851        );
852
853        // Store data on element
854        let element = &mut self.nodes[node_id].element_data_mut().unwrap();
855        element.special_data = SpecialElementData::Stylesheet(stylesheet.clone());
856
857        // TODO: Nodes could potentially get reused so ordering by node_id might be wrong.
858        let insertion_point = self
859            .nodes_to_stylesheet
860            .range((Bound::Excluded(node_id), Bound::Unbounded))
861            .next()
862            .map(|(_, sheet)| sheet);
863
864        if let Some(insertion_point) = insertion_point {
865            self.stylist.insert_stylesheet_before(
866                stylesheet,
867                insertion_point.clone(),
868                &self.guard.read(),
869            )
870        } else {
871            self.stylist
872                .append_stylesheet(stylesheet, &self.guard.read())
873        }
874    }
875
876    pub fn handle_messages(&mut self) {
877        // Remove event Reciever from the Document so that we can process events
878        // without holding a borrow to the Document
879        let rx = self.rx.take().unwrap();
880
881        while let Ok(msg) = rx.try_recv() {
882            self.handle_message(msg);
883        }
884
885        // Put Reciever back
886        self.rx = Some(rx);
887    }
888
889    pub fn handle_message(&mut self, msg: DocumentEvent) {
890        match msg {
891            DocumentEvent::ResourceLoad(resource) => self.load_resource(resource),
892        }
893    }
894
895    /// Whether the Document has pending requests for "critical" resources (that should block rendering)
896    pub fn has_pending_critical_resources(&self) -> bool {
897        !self.pending_critical_resources.is_empty()
898    }
899
900    pub fn load_resource(&mut self, res: ResourceLoadResponse) {
901        self.pending_critical_resources.remove(&res.request_id);
902
903        let Ok(resource) = res.result else {
904            // TODO: handle error
905            return;
906        };
907
908        match resource {
909            Resource::Css(css) => {
910                let node_id = res.node_id.unwrap();
911                self.add_stylesheet_for_node(css, node_id);
912            }
913            Resource::Image(_kind, width, height, image_data) => {
914                // Create the ImageData and cache it
915                let image = ImageData::Raster(RasterImageData::new(width, height, image_data));
916
917                let Some(url) = res.resolved_url.as_ref() else {
918                    return;
919                };
920
921                // Get all nodes waiting for this image
922                let waiting_nodes = self.pending_images.remove(url).unwrap_or_default();
923
924                #[cfg(feature = "tracing")]
925                tracing::info!(
926                    "Image {url} loaded, applying to {} nodes",
927                    waiting_nodes.len()
928                );
929
930                // Cache the image
931                self.image_cache.insert(url.clone(), image.clone());
932
933                // Apply to all waiting nodes
934                for (node_id, image_type) in waiting_nodes {
935                    let Some(node) = self.get_node_mut(node_id) else {
936                        continue;
937                    };
938
939                    match image_type {
940                        ImageType::Image => {
941                            node.element_data_mut().unwrap().special_data =
942                                SpecialElementData::Image(Box::new(image.clone()));
943
944                            // Clear layout cache
945                            node.cache.clear();
946                            node.insert_damage(ALL_DAMAGE);
947                        }
948                        ImageType::Background(idx) => {
949                            if let Some(Some(bg_image)) = node
950                                .element_data_mut()
951                                .and_then(|el| el.background_images.get_mut(idx))
952                            {
953                                bg_image.status = Status::Ok;
954                                bg_image.image = image.clone();
955                            }
956                        }
957                    }
958                }
959            }
960            #[cfg(feature = "svg")]
961            Resource::Svg(_kind, tree) => {
962                // Create the ImageData and cache it
963                let image = ImageData::Svg(tree);
964
965                let Some(url) = res.resolved_url.as_ref() else {
966                    return;
967                };
968
969                // Get all nodes waiting for this image
970                let waiting_nodes = self.pending_images.remove(url).unwrap_or_default();
971
972                #[cfg(feature = "tracing")]
973                tracing::info!(
974                    "SVG {url} loaded, applying to {} nodes",
975                    waiting_nodes.len()
976                );
977
978                // Cache the image
979                self.image_cache.insert(url.clone(), image.clone());
980
981                // Apply to all waiting nodes
982                for (node_id, image_type) in waiting_nodes {
983                    let Some(node) = self.get_node_mut(node_id) else {
984                        continue;
985                    };
986
987                    match image_type {
988                        ImageType::Image => {
989                            node.element_data_mut().unwrap().special_data =
990                                SpecialElementData::Image(Box::new(image.clone()));
991
992                            // Clear layout cache
993                            node.cache.clear();
994                            node.insert_damage(ALL_DAMAGE);
995                        }
996                        ImageType::Background(idx) => {
997                            if let Some(Some(bg_image)) = node
998                                .element_data_mut()
999                                .and_then(|el| el.background_images.get_mut(idx))
1000                            {
1001                                bg_image.status = Status::Ok;
1002                                bg_image.image = image.clone();
1003                            }
1004                        }
1005                    }
1006                }
1007            }
1008            Resource::Font(bytes) => {
1009                let font = Blob::new(Arc::new(bytes));
1010
1011                // TODO: Implement FontInfoOveride
1012                // TODO: Investigate eliminating double-box
1013                let mut global_font_ctx = self.font_ctx.lock().unwrap();
1014                global_font_ctx
1015                    .collection
1016                    .register_fonts(font.clone(), None);
1017
1018                #[cfg(feature = "parallel-construct")]
1019                {
1020                    rayon::broadcast(|_ctx| {
1021                        let mut font_ctx = self
1022                            .thread_font_contexts
1023                            .get_or(|| RefCell::new(Box::new(global_font_ctx.clone())))
1024                            .borrow_mut();
1025                        font_ctx.collection.register_fonts(font.clone(), None);
1026                    });
1027                }
1028                drop(global_font_ctx);
1029
1030                // TODO: see if we can only invalidate if resolved fonts may have changed
1031                self.invalidate_inline_contexts();
1032            }
1033            Resource::None => {
1034                // Do nothing
1035            }
1036        }
1037    }
1038
1039    pub fn snapshot_node(&mut self, node_id: usize) {
1040        let node = &mut self.nodes[node_id];
1041        let opaque_node_id = TNode::opaque(&&*node);
1042        node.has_snapshot = true;
1043        node.snapshot_handled
1044            .store(false, std::sync::atomic::Ordering::SeqCst);
1045
1046        // TODO: handle invalidations other than hover
1047        if let Some(_existing_snapshot) = self.snapshots.get_mut(&opaque_node_id) {
1048            // Do nothing
1049            // TODO: update snapshot
1050        } else {
1051            let attrs: Option<Vec<_>> = node.attrs().map(|attrs| {
1052                attrs
1053                    .iter()
1054                    .map(|attr| {
1055                        let ident = AttrIdentifier {
1056                            local_name: GenericAtomIdent(attr.name.local.clone()),
1057                            name: GenericAtomIdent(attr.name.local.clone()),
1058                            namespace: GenericAtomIdent(attr.name.ns.clone()),
1059                            prefix: None,
1060                        };
1061
1062                        let value = if attr.name.local == local_name!("id") {
1063                            AttrValue::Atom(Atom::from(&*attr.value))
1064                        } else if attr.name.local == local_name!("class") {
1065                            let classes = attr
1066                                .value
1067                                .split_ascii_whitespace()
1068                                .map(Atom::from)
1069                                .collect();
1070                            AttrValue::TokenList(attr.value.clone(), classes)
1071                        } else {
1072                            AttrValue::String(attr.value.clone())
1073                        };
1074
1075                        (ident, value)
1076                    })
1077                    .collect()
1078            });
1079
1080            let changed_attrs = attrs
1081                .as_ref()
1082                .map(|attrs| attrs.iter().map(|attr| attr.0.name.clone()).collect())
1083                .unwrap_or_default();
1084
1085            self.snapshots.insert(
1086                opaque_node_id,
1087                ServoElementSnapshot {
1088                    state: Some(node.element_state),
1089                    attrs,
1090                    changed_attrs,
1091                    class_changed: true,
1092                    id_changed: true,
1093                    other_attributes_changed: true,
1094                },
1095            );
1096        }
1097    }
1098
1099    pub fn snapshot_node_and(&mut self, node_id: usize, cb: impl FnOnce(&mut Node)) {
1100        self.snapshot_node(node_id);
1101        cb(&mut self.nodes[node_id]);
1102    }
1103
1104    // Takes (x, y) co-ordinates (relative to the )
1105    pub fn hit(&self, x: f32, y: f32) -> Option<HitResult> {
1106        if TDocument::as_node(&&self.nodes[0])
1107            .first_element_child()
1108            .is_none()
1109        {
1110            #[cfg(feature = "tracing")]
1111            tracing::warn!("No DOM - not resolving hit test");
1112            return None;
1113        }
1114
1115        self.root_element().hit(x, y)
1116    }
1117
1118    pub fn focus_next_node(&mut self) -> Option<usize> {
1119        let focussed_node_id = self.get_focussed_node_id()?;
1120        let id = self.next_node(&self.nodes[focussed_node_id], |node| node.is_focussable())?;
1121        self.set_focus_to(id);
1122        Some(id)
1123    }
1124
1125    /// Clear the focussed node
1126    pub fn clear_focus(&mut self) {
1127        if let Some(id) = self.focus_node_id {
1128            let shell_provider = self.shell_provider.clone();
1129            self.snapshot_node_and(id, |node| node.blur(shell_provider));
1130            self.focus_node_id = None;
1131        }
1132    }
1133
1134    pub fn set_mousedown_node_id(&mut self, node_id: Option<usize>) {
1135        self.mousedown_node_id = node_id;
1136    }
1137    pub fn set_focus_to(&mut self, focus_node_id: usize) -> bool {
1138        if Some(focus_node_id) == self.focus_node_id {
1139            return false;
1140        }
1141
1142        #[cfg(feature = "tracing")]
1143        tracing::info!("Focussed node {focus_node_id}");
1144
1145        let shell_provider = self.shell_provider.clone();
1146
1147        // Remove focus from the old node
1148        if let Some(id) = self.focus_node_id {
1149            self.snapshot_node_and(id, |node| node.blur(shell_provider.clone()));
1150        }
1151
1152        // Focus the new node
1153        self.snapshot_node_and(focus_node_id, |node| node.focus(shell_provider));
1154
1155        self.focus_node_id = Some(focus_node_id);
1156
1157        true
1158    }
1159
1160    pub fn active_node(&mut self) -> bool {
1161        let Some(hover_node_id) = self.get_hover_node_id() else {
1162            return false;
1163        };
1164
1165        if let Some(active_node_id) = self.active_node_id {
1166            if active_node_id == hover_node_id {
1167                return true;
1168            }
1169            self.unactive_node();
1170        }
1171
1172        let active_node_id = Some(hover_node_id);
1173
1174        let node_path = self.maybe_node_layout_ancestors(active_node_id);
1175        for &id in node_path.iter() {
1176            self.snapshot_node_and(id, |node| node.active());
1177        }
1178
1179        self.active_node_id = active_node_id;
1180
1181        true
1182    }
1183
1184    pub fn unactive_node(&mut self) -> bool {
1185        let Some(active_node_id) = self.active_node_id.take() else {
1186            return false;
1187        };
1188
1189        let node_path = self.maybe_node_layout_ancestors(Some(active_node_id));
1190        for &id in node_path.iter() {
1191            self.snapshot_node_and(id, |node| node.unactive());
1192        }
1193
1194        true
1195    }
1196
1197    pub fn set_hover_to(&mut self, x: f32, y: f32) -> bool {
1198        let hit = self.hit(x, y);
1199        let hover_node_id = hit.map(|hit| hit.node_id);
1200        let new_is_text = hit.map(|hit| hit.is_text).unwrap_or(false);
1201
1202        // Return early if the new node is the same as the already-hovered node
1203        if hover_node_id == self.hover_node_id {
1204            return false;
1205        }
1206
1207        let old_node_path = self.maybe_node_layout_ancestors(self.hover_node_id);
1208        let new_node_path = self.maybe_node_layout_ancestors(hover_node_id);
1209        let same_count = old_node_path
1210            .iter()
1211            .zip(&new_node_path)
1212            .take_while(|(o, n)| o == n)
1213            .count();
1214        for &id in old_node_path.iter().skip(same_count) {
1215            self.snapshot_node_and(id, |node| node.unhover());
1216        }
1217        for &id in new_node_path.iter().skip(same_count) {
1218            self.snapshot_node_and(id, |node| node.hover());
1219        }
1220
1221        self.hover_node_id = hover_node_id;
1222        self.hover_node_is_text = new_is_text;
1223
1224        // Update the cursor
1225        let cursor = self.get_cursor().unwrap_or_default();
1226        self.shell_provider.set_cursor(cursor);
1227
1228        // Request redraw
1229        self.shell_provider.request_redraw();
1230
1231        true
1232    }
1233
1234    pub fn clear_hover(&mut self) -> bool {
1235        let Some(hover_node_id) = self.hover_node_id else {
1236            return false;
1237        };
1238
1239        let old_node_path = self.maybe_node_layout_ancestors(Some(hover_node_id));
1240        for &id in old_node_path.iter() {
1241            self.snapshot_node_and(id, |node| node.unhover());
1242        }
1243
1244        self.hover_node_id = None;
1245        self.hover_node_is_text = false;
1246
1247        // Update the cursor
1248        let cursor = self.get_cursor().unwrap_or_default();
1249        self.shell_provider.set_cursor(cursor);
1250
1251        // Request redraw
1252        self.shell_provider.request_redraw();
1253
1254        true
1255    }
1256
1257    pub fn get_hover_node_id(&self) -> Option<usize> {
1258        self.hover_node_id
1259    }
1260
1261    pub fn set_viewport(&mut self, viewport: Viewport) {
1262        let scale_has_changed = viewport.scale_f64() != self.viewport.scale_f64();
1263        self.viewport = viewport;
1264        self.set_stylist_device(make_device(
1265            &self.viewport,
1266            self.media_type.clone(),
1267            self.font_ctx.clone(),
1268        ));
1269        self.scroll_viewport_by(0.0, 0.0); // Clamp scroll offset
1270
1271        if scale_has_changed {
1272            self.invalidate_inline_contexts();
1273            self.shell_provider.request_redraw();
1274        }
1275    }
1276
1277    /// Returns the current CSS media type used to evaluate `@media` rules.
1278    pub fn media_type(&self) -> &MediaType {
1279        &self.media_type
1280    }
1281
1282    /// Sets the CSS media type used to evaluate `@media` rules (e.g. `screen` or `print`)
1283    /// and rebuilds the stylist device so updated rules apply on the next restyle.
1284    pub fn set_media_type(&mut self, media_type: MediaType) {
1285        if self.media_type == media_type {
1286            return;
1287        }
1288        self.media_type = media_type;
1289        self.set_stylist_device(make_device(
1290            &self.viewport,
1291            self.media_type.clone(),
1292            self.font_ctx.clone(),
1293        ));
1294    }
1295
1296    pub fn viewport(&self) -> &Viewport {
1297        &self.viewport
1298    }
1299
1300    pub fn viewport_mut(&mut self) -> ViewportMut<'_> {
1301        ViewportMut::new(self)
1302    }
1303
1304    pub fn zoom_by(&mut self, increment: f32) {
1305        *self.viewport.zoom_mut() += increment;
1306        self.set_viewport(self.viewport.clone());
1307    }
1308
1309    pub fn zoom_to(&mut self, zoom: f32) {
1310        *self.viewport.zoom_mut() = zoom;
1311        self.set_viewport(self.viewport.clone());
1312    }
1313
1314    pub fn get_viewport(&self) -> Viewport {
1315        self.viewport.clone()
1316    }
1317
1318    pub fn devtools(&self) -> &DevtoolSettings {
1319        &self.devtool_settings
1320    }
1321
1322    pub fn devtools_mut(&mut self) -> &mut DevtoolSettings {
1323        &mut self.devtool_settings
1324    }
1325
1326    pub fn is_animating(&self) -> bool {
1327        self.has_canvas
1328            | self.has_active_animations
1329            | self.subdoc_is_animating
1330            | (self.scroll_animation != ScrollAnimationState::None)
1331    }
1332
1333    /// Update the device and reset the stylist to process the new size
1334    pub fn set_stylist_device(&mut self, device: Device) {
1335        let origins = {
1336            let guard = &self.guard;
1337            let guards = StylesheetGuards {
1338                author: &guard.read(),
1339                ua_or_user: &guard.read(),
1340            };
1341            self.stylist.set_device(device, &guards)
1342        };
1343        self.stylist.force_stylesheet_origins_dirty(origins);
1344    }
1345
1346    pub fn stylist_device(&mut self) -> &Device {
1347        self.stylist.device()
1348    }
1349
1350    pub fn get_cursor(&self) -> Option<CursorIcon> {
1351        let node = &self.nodes[self.get_hover_node_id()?];
1352
1353        if let Some(subdoc) = node.subdoc().map(|doc| doc.inner()) {
1354            return subdoc.get_cursor();
1355        }
1356
1357        let style = node.primary_styles()?;
1358        let keyword = stylo_to_cursor_icon(style.clone_cursor().keyword);
1359
1360        // Return cursor from style if it is non-auto
1361        if keyword != CursorIcon::Default {
1362            return Some(keyword);
1363        }
1364
1365        // Return text cursor for text inputs
1366        if node
1367            .element_data()
1368            .is_some_and(|e| e.text_input_data().is_some())
1369        {
1370            return Some(CursorIcon::Text);
1371        }
1372
1373        // Use "pointer" cursor if any ancestor is a link
1374        let mut maybe_node = Some(node);
1375        while let Some(node) = maybe_node {
1376            if node.is_link() {
1377                return Some(CursorIcon::Pointer);
1378            }
1379
1380            maybe_node = node.layout_parent.get().map(|node_id| node.with(node_id));
1381        }
1382
1383        // Return text cursor for text nodes
1384        if self.hover_node_is_text {
1385            return Some(CursorIcon::Text);
1386        }
1387
1388        // Else fallback to default cursor
1389        Some(CursorIcon::Default)
1390    }
1391
1392    pub fn scroll_node_by<F: FnMut(DomEvent)>(
1393        &mut self,
1394        node_id: usize,
1395        x: f64,
1396        y: f64,
1397        dispatch_event: F,
1398    ) {
1399        self.scroll_node_by_has_changed(node_id, x, y, dispatch_event);
1400    }
1401
1402    /// Scroll a node by given x and y
1403    /// Will bubble scrolling up to parent node once it can no longer scroll further
1404    /// If we're already at the root node, bubbles scrolling up to the viewport
1405    pub fn scroll_node_by_has_changed<F: FnMut(DomEvent)>(
1406        &mut self,
1407        node_id: usize,
1408        x: f64,
1409        y: f64,
1410        mut dispatch_event: F,
1411    ) -> bool {
1412        let Some(node) = self.nodes.get_mut(node_id) else {
1413            return false;
1414        };
1415
1416        let is_html_or_body = node.data.downcast_element().is_some_and(|e| {
1417            let tag = &e.name.local;
1418            tag == "html" || tag == "body"
1419        });
1420
1421        let (can_x_scroll, can_y_scroll) = node
1422            .primary_styles()
1423            .map(|styles| {
1424                (
1425                    matches!(styles.clone_overflow_x(), Overflow::Scroll | Overflow::Auto),
1426                    matches!(styles.clone_overflow_y(), Overflow::Scroll | Overflow::Auto)
1427                        || (styles.clone_overflow_y() == Overflow::Visible && is_html_or_body),
1428                )
1429            })
1430            .unwrap_or((false, false));
1431
1432        let initial = node.scroll_offset;
1433        let new_x = node.scroll_offset.x - x;
1434        let new_y = node.scroll_offset.y - y;
1435
1436        let mut bubble_x = 0.0;
1437        let mut bubble_y = 0.0;
1438
1439        let scroll_width = node.final_layout.scroll_width() as f64;
1440        let scroll_height = node.final_layout.scroll_height() as f64;
1441
1442        // Handle sub document case
1443        if let Some(mut sub_doc) = node.subdoc_mut().map(|doc| doc.inner_mut()) {
1444            let has_changed = if let Some(hover_node_id) = sub_doc.get_hover_node_id() {
1445                sub_doc.scroll_node_by_has_changed(hover_node_id, x, y, dispatch_event)
1446            } else {
1447                sub_doc.scroll_viewport_by_has_changed(x, y)
1448            };
1449
1450            // TODO: propagate remaining scroll to parent
1451            return has_changed;
1452        }
1453
1454        // If we're past our scroll bounds, transfer remainder of scrolling to parent/viewport
1455        if !can_x_scroll {
1456            bubble_x = x
1457        } else if new_x < 0.0 {
1458            bubble_x = -new_x;
1459            node.scroll_offset.x = 0.0;
1460        } else if new_x > scroll_width {
1461            bubble_x = scroll_width - new_x;
1462            node.scroll_offset.x = scroll_width;
1463        } else {
1464            node.scroll_offset.x = new_x;
1465        }
1466
1467        if !can_y_scroll {
1468            bubble_y = y
1469        } else if new_y < 0.0 {
1470            bubble_y = -new_y;
1471            node.scroll_offset.y = 0.0;
1472        } else if new_y > scroll_height {
1473            bubble_y = scroll_height - new_y;
1474            node.scroll_offset.y = scroll_height;
1475        } else {
1476            node.scroll_offset.y = new_y;
1477        }
1478
1479        let has_changed = node.scroll_offset != initial;
1480
1481        if has_changed {
1482            let layout = node.final_layout;
1483            let event = BlitzScrollEvent {
1484                scroll_top: node.scroll_offset.y,
1485                scroll_left: node.scroll_offset.x,
1486                scroll_width: layout.scroll_width() as i32,
1487                scroll_height: layout.scroll_height() as i32,
1488                client_width: layout.size.width as i32,
1489                client_height: layout.size.height as i32,
1490            };
1491
1492            dispatch_event(DomEvent::new(node_id, DomEventData::Scroll(event)));
1493        }
1494
1495        if bubble_x != 0.0 || bubble_y != 0.0 {
1496            if let Some(parent) = node.parent {
1497                return self.scroll_node_by_has_changed(parent, bubble_x, bubble_y, dispatch_event)
1498                    | has_changed;
1499            } else {
1500                return self.scroll_viewport_by_has_changed(bubble_x, bubble_y) | has_changed;
1501            }
1502        }
1503
1504        has_changed
1505    }
1506
1507    pub fn scroll_viewport_by(&mut self, x: f64, y: f64) {
1508        self.scroll_viewport_by_has_changed(x, y);
1509    }
1510
1511    /// Scroll the viewport by the given values
1512    pub fn scroll_viewport_by_has_changed(&mut self, x: f64, y: f64) -> bool {
1513        let content_size = self.root_element().final_layout.size;
1514        let new_scroll = (self.viewport_scroll.x - x, self.viewport_scroll.y - y);
1515        let window_width = self.viewport.window_size.0 as f64 / self.viewport.scale() as f64;
1516        let window_height = self.viewport.window_size.1 as f64 / self.viewport.scale() as f64;
1517
1518        let initial = self.viewport_scroll;
1519        self.viewport_scroll.x = f64::max(
1520            0.0,
1521            f64::min(new_scroll.0, content_size.width as f64 - window_width),
1522        );
1523        self.viewport_scroll.y = f64::max(
1524            0.0,
1525            f64::min(new_scroll.1, content_size.height as f64 - window_height),
1526        );
1527
1528        self.viewport_scroll != initial
1529    }
1530
1531    pub fn scroll_by(
1532        &mut self,
1533        anchor_node_id: Option<usize>,
1534        scroll_x: f64,
1535        scroll_y: f64,
1536        dispatch_event: &mut dyn FnMut(DomEvent),
1537    ) -> bool {
1538        if let Some(anchor_node_id) = anchor_node_id {
1539            self.scroll_node_by_has_changed(anchor_node_id, scroll_x, scroll_y, dispatch_event)
1540        } else {
1541            self.scroll_viewport_by_has_changed(scroll_x, scroll_y)
1542        }
1543    }
1544
1545    pub fn viewport_scroll(&self) -> crate::Point<f64> {
1546        self.viewport_scroll
1547    }
1548
1549    pub fn set_viewport_scroll(&mut self, scroll: crate::Point<f64>) {
1550        self.viewport_scroll = scroll;
1551    }
1552
1553    /// Computes the size and position of the `Node` relative to the viewport
1554    pub fn get_client_bounding_rect(&self, node_id: usize) -> Option<BoundingRect> {
1555        let node = self.get_node(node_id)?;
1556        let pos = node.absolute_position(0.0, 0.0);
1557
1558        Some(BoundingRect {
1559            x: pos.x as f64 - self.viewport_scroll.x,
1560            y: pos.y as f64 - self.viewport_scroll.y,
1561            width: node.unrounded_layout.size.width as f64,
1562            height: node.unrounded_layout.size.height as f64,
1563        })
1564    }
1565
1566    pub fn find_title_node(&self) -> Option<&Node> {
1567        TreeTraverser::new(self)
1568            .find(|node_id| {
1569                self.nodes[*node_id]
1570                    .data
1571                    .is_element_with_tag_name(&local_name!("title"))
1572            })
1573            .map(|node_id| &self.nodes[node_id])
1574    }
1575
1576    pub fn with_text_input(
1577        &mut self,
1578        node_id: usize,
1579        cb: impl FnOnce(PlainEditorDriver<TextBrush>),
1580    ) {
1581        let Some(node) = self.nodes.get_mut(node_id) else {
1582            return;
1583        };
1584
1585        if let Some(text_input) = node
1586            .element_data_mut()
1587            .and_then(|el| el.text_input_data_mut())
1588        {
1589            let mut font_ctx = self.font_ctx.lock().unwrap();
1590            let layout_ctx = &mut self.layout_ctx;
1591            let driver = text_input.editor.driver(&mut font_ctx, layout_ctx);
1592            cb(driver)
1593        }
1594    }
1595
1596    pub(crate) fn compute_has_canvas(&self) -> bool {
1597        TreeTraverser::new(self).any(|node_id| {
1598            let node = &self.nodes[node_id];
1599            let Some(element) = node.element_data() else {
1600                return false;
1601            };
1602            if element.name.local == local_name!("canvas") && element.has_attr(local_name!("src")) {
1603                return true;
1604            }
1605
1606            false
1607        })
1608    }
1609
1610    // Text selection methods
1611
1612    /// Find the text position (inline_root_id, byte_offset) at a given point.
1613    /// Uses hit() for proper coordinate transformation, then finds the inline root
1614    /// and byte offset.
1615    pub fn find_text_position(&self, x: f32, y: f32) -> Option<(usize, usize)> {
1616        let hit = self.hit(x, y)?;
1617        let hit_node = self.get_node(hit.node_id)?;
1618        let inline_root = hit_node.inline_root_ancestor()?;
1619        let byte_offset = inline_root.text_offset_at_point(hit.x, hit.y)?;
1620        Some((inline_root.id, byte_offset))
1621    }
1622
1623    /// Set the text selection range (creates a new selection from anchor to focus)
1624    pub fn set_text_selection(
1625        &mut self,
1626        anchor_node: usize,
1627        anchor_offset: usize,
1628        focus_node: usize,
1629        focus_offset: usize,
1630    ) {
1631        self.text_selection =
1632            TextSelection::new(anchor_node, anchor_offset, focus_node, focus_offset);
1633
1634        // For anonymous blocks, switch to storing parent+sibling_index (stable reference)
1635        if let (Some(parent), Some(idx)) = self.anonymous_block_location(anchor_node) {
1636            self.text_selection
1637                .anchor
1638                .set_anonymous(parent, idx, anchor_offset);
1639        }
1640        if let (Some(parent), Some(idx)) = self.anonymous_block_location(focus_node) {
1641            self.text_selection
1642                .focus
1643                .set_anonymous(parent, idx, focus_offset);
1644        }
1645    }
1646
1647    /// Get the parent ID and sibling index for a node if it's an anonymous block.
1648    /// Returns (None, None) for non-anonymous blocks.
1649    fn anonymous_block_location(&self, node_id: usize) -> (Option<usize>, Option<usize>) {
1650        let Some(node) = self.get_node(node_id) else {
1651            return (None, None);
1652        };
1653
1654        if !node.is_anonymous() {
1655            return (None, None);
1656        }
1657
1658        let Some(parent_id) = node.parent else {
1659            return (None, None);
1660        };
1661
1662        let Some(parent) = self.get_node(parent_id) else {
1663            return (Some(parent_id), None);
1664        };
1665
1666        let layout_children = parent.layout_children.borrow();
1667        let Some(children) = layout_children.as_ref() else {
1668            return (Some(parent_id), None);
1669        };
1670
1671        // Find the index of this anonymous block among siblings
1672        let mut anon_index = 0;
1673        for &child_id in children.iter() {
1674            if child_id == node_id {
1675                return (Some(parent_id), Some(anon_index));
1676            }
1677            if self.get_node(child_id).is_some_and(|n| n.is_anonymous()) {
1678                anon_index += 1;
1679            }
1680        }
1681
1682        (Some(parent_id), None)
1683    }
1684
1685    /// Clear the text selection
1686    pub fn clear_text_selection(&mut self) {
1687        self.text_selection.clear();
1688    }
1689
1690    /// Update the selection focus point (used during mouse drag to extend selection).
1691    pub fn update_selection_focus(&mut self, focus_node: usize, focus_offset: usize) {
1692        // For anonymous blocks, store parent+sibling_index; otherwise store node directly
1693        if let (Some(parent), Some(idx)) = self.anonymous_block_location(focus_node) {
1694            self.text_selection
1695                .focus
1696                .set_anonymous(parent, idx, focus_offset);
1697        } else {
1698            self.text_selection.set_focus(focus_node, focus_offset);
1699        }
1700    }
1701
1702    /// Extend text selection to the given point. Returns true if selection was updated.
1703    /// This is a convenience method that combines find_text_position and update_selection_focus.
1704    pub fn extend_text_selection_to_point(&mut self, x: f32, y: f32) -> bool {
1705        if !self.text_selection.anchor.is_some() {
1706            return false;
1707        }
1708
1709        if let Some((node, offset)) = self.find_text_position(x, y) {
1710            self.update_selection_focus(node, offset);
1711            self.shell_provider.request_redraw();
1712            true
1713        } else {
1714            false
1715        }
1716    }
1717
1718    /// Find the Nth anonymous block under a parent.
1719    fn find_anonymous_block_by_index(
1720        &self,
1721        parent_id: usize,
1722        target_index: usize,
1723    ) -> Option<usize> {
1724        let parent = self.get_node(parent_id)?;
1725        let layout_children = parent.layout_children.borrow();
1726        let children = layout_children.as_ref()?;
1727
1728        children
1729            .iter()
1730            .filter(|&&child_id| self.get_node(child_id).is_some_and(|n| n.is_anonymous()))
1731            .nth(target_index)
1732            .copied()
1733    }
1734
1735    /// Check if there is an active (non-empty) text selection
1736    pub fn has_text_selection(&self) -> bool {
1737        self.text_selection.is_active()
1738    }
1739
1740    /// Get the selected text content, supporting selection across multiple inline roots.
1741    pub fn get_selected_text(&self) -> Option<String> {
1742        let ranges = self.get_text_selection_ranges();
1743        if ranges.is_empty() {
1744            return None;
1745        }
1746
1747        let mut result = String::new();
1748        for (node_id, start, end) in &ranges {
1749            let node = self.get_node(*node_id)?;
1750            let element_data = node.element_data()?;
1751            let inline_layout = element_data.inline_layout_data.as_ref()?;
1752
1753            if *end > inline_layout.text.len() {
1754                continue;
1755            }
1756
1757            if !result.is_empty() {
1758                result.push(' ');
1759            }
1760            result.push_str(&inline_layout.text[*start..*end]);
1761        }
1762
1763        if result.is_empty() {
1764            None
1765        } else {
1766            Some(result)
1767        }
1768    }
1769
1770    /// Get all selection ranges as Vec<(node_id, start_offset, end_offset)>.
1771    /// Returns empty vec if no selection.
1772    pub fn get_text_selection_ranges(&self) -> Vec<(usize, usize, usize)> {
1773        let lookup = |parent_id, idx| self.find_anonymous_block_by_index(parent_id, idx);
1774
1775        let anchor_node = match self.text_selection.anchor.resolve_node_id(lookup) {
1776            Some(id) => id,
1777            None => return Vec::new(),
1778        };
1779        let focus_node = match self.text_selection.focus.resolve_node_id(lookup) {
1780            Some(id) => id,
1781            None => return Vec::new(),
1782        };
1783
1784        // Single node selection
1785        if anchor_node == focus_node {
1786            let start = self
1787                .text_selection
1788                .anchor
1789                .offset
1790                .min(self.text_selection.focus.offset);
1791            let end = self
1792                .text_selection
1793                .anchor
1794                .offset
1795                .max(self.text_selection.focus.offset);
1796
1797            if start == end {
1798                return Vec::new();
1799            }
1800            return vec![(anchor_node, start, end)];
1801        }
1802
1803        // Multi-node selection: collect all inline roots between anchor and focus
1804        let inline_roots = self.collect_inline_roots_in_range(anchor_node, focus_node);
1805        if inline_roots.is_empty() {
1806            return Vec::new();
1807        }
1808
1809        // Determine document order using the collected inline_roots order
1810        // (inline_roots is already in document order from first to last)
1811        let first_in_roots = inline_roots[0];
1812
1813        let (first_node, first_offset, last_node, last_offset) =
1814            if first_in_roots == anchor_node || (first_in_roots != focus_node) {
1815                // anchor is first (or neither endpoint is in roots, which shouldn't happen)
1816                (
1817                    anchor_node,
1818                    self.text_selection.anchor.offset,
1819                    focus_node,
1820                    self.text_selection.focus.offset,
1821                )
1822            } else {
1823                // focus is first
1824                (
1825                    focus_node,
1826                    self.text_selection.focus.offset,
1827                    anchor_node,
1828                    self.text_selection.anchor.offset,
1829                )
1830            };
1831
1832        let mut ranges = Vec::with_capacity(inline_roots.len());
1833
1834        for &node_id in &inline_roots {
1835            let Some(node) = self.get_node(node_id) else {
1836                continue;
1837            };
1838            let Some(element_data) = node.element_data() else {
1839                continue;
1840            };
1841            let Some(inline_layout) = element_data.inline_layout_data.as_ref() else {
1842                continue;
1843            };
1844
1845            let text_len = inline_layout.text.len();
1846
1847            if node_id == first_node && node_id == last_node {
1848                let start = first_offset.min(last_offset);
1849                let end = first_offset.max(last_offset);
1850                if start < end && end <= text_len {
1851                    ranges.push((node_id, start, end));
1852                }
1853            } else if node_id == first_node {
1854                if first_offset < text_len {
1855                    ranges.push((node_id, first_offset, text_len));
1856                }
1857            } else if node_id == last_node {
1858                if last_offset > 0 && last_offset <= text_len {
1859                    ranges.push((node_id, 0, last_offset));
1860                }
1861            } else if text_len > 0 {
1862                ranges.push((node_id, 0, text_len));
1863            }
1864        }
1865
1866        ranges
1867    }
1868}
1869
1870pub struct BoundingRect {
1871    pub x: f64,
1872    pub y: f64,
1873    pub width: f64,
1874    pub height: f64,
1875}
1876
1877impl AsRef<BaseDocument> for BaseDocument {
1878    fn as_ref(&self) -> &BaseDocument {
1879        self
1880    }
1881}
1882
1883impl AsMut<BaseDocument> for BaseDocument {
1884    fn as_mut(&mut self) -> &mut BaseDocument {
1885        self
1886    }
1887}