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