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