1use crate::events::handle_dom_event;
2use crate::font_metrics::BlitzFontMetricsProvider;
3use crate::layout::construct::{
4 ConstructionTask, ConstructionTaskData, ConstructionTaskResult, ConstructionTaskResultData,
5 build_inline_layout_into, collect_layout_children,
6};
7use crate::layout::damage::{ALL_DAMAGE, CONSTRUCT_BOX, CONSTRUCT_DESCENDENT, CONSTRUCT_FC};
8use crate::mutator::ViewportMut;
9use crate::net::{Resource, StylesheetLoader};
10use crate::node::{ImageData, NodeFlags, RasterImageData, SpecialElementData, Status, TextBrush};
11use crate::stylo_to_cursor_icon::stylo_to_cursor_icon;
12use crate::traversal::TreeTraverser;
13use crate::url::DocumentUrl;
14use crate::util::ImageType;
15use crate::{
16 DEFAULT_CSS, DocumentConfig, DocumentMutator, DummyHtmlParserProvider, ElementData,
17 EventDriver, HtmlParserProvider, NON_INCREMENTAL, Node, NodeData, NoopEventHandler,
18 TextNodeData,
19};
20use blitz_traits::devtools::DevtoolSettings;
21use blitz_traits::events::{DomEvent, HitResult, UiEvent};
22use blitz_traits::navigation::{DummyNavigationProvider, NavigationProvider};
23use blitz_traits::net::{DummyNetProvider, NetProvider, SharedProvider};
24use blitz_traits::shell::{ColorScheme, DummyShellProvider, ShellProvider, Viewport};
25use cursor_icon::CursorIcon;
26use debug_timer::debug_timer;
27use linebender_resource_handle::Blob;
28use markup5ever::local_name;
29use parley::{FontContext, LayoutContext};
30use selectors::{Element, matching::QuirksMode};
31use slab::Slab;
32use std::any::Any;
33use std::cell::RefCell;
34use std::collections::{BTreeMap, Bound, HashMap, HashSet};
35use std::ops::{Deref, DerefMut};
36use std::str::FromStr;
37use std::sync::atomic::{AtomicUsize, Ordering};
38use std::sync::{Arc, Mutex};
39use std::task::Context as TaskContext;
40use style::Atom;
41use style::animation::DocumentAnimationSet;
42use style::attr::{AttrIdentifier, AttrValue};
43use style::data::{ElementData as StyloElementData, ElementStyles};
44use style::media_queries::MediaType;
45use style::properties::ComputedValues;
46use style::properties::style_structs::Font;
47use style::queries::values::PrefersColorScheme;
48use style::selector_parser::ServoElementSnapshot;
49use style::servo_arc::Arc as ServoArc;
50use style::values::GenericAtomIdent;
51use style::values::computed::Overflow;
52use style::{
53 dom::{TDocument, TNode},
54 media_queries::{Device, MediaList},
55 selector_parser::SnapshotMap,
56 shared_lock::{SharedRwLock, StylesheetGuards},
57 stylesheets::{AllowImportRules, DocumentStyleSheet, Origin, Stylesheet},
58 stylist::Stylist,
59};
60use taffy::AvailableSpace;
61use url::Url;
62
63#[cfg(feature = "parallel-construct")]
64use rayon::prelude::*;
65
66thread_local! {
67 static LAYOUT_CTX: RefCell<Option<Box<LayoutContext<TextBrush>>>> = const { RefCell::new(None) };
68 static FONT_CTX: RefCell<Option<Box<FontContext>>> = const { RefCell::new(None) };
69}
70
71#[cfg(feature = "incremental")]
72use style::selector_parser::RestyleDamage;
73
74pub trait Document: Deref<Target = BaseDocument> + DerefMut + 'static {
77 fn handle_ui_event(&mut self, event: UiEvent) {
79 let mut driver = EventDriver::new((*self).mutate(), NoopEventHandler);
80 driver.handle_ui_event(event);
81 }
82
83 fn poll(&mut self, task_context: Option<TaskContext>) -> bool {
85 let _ = task_context;
87 false
88 }
89
90 fn as_any_mut(&mut self) -> &mut dyn Any;
91
92 fn id(&self) -> usize {
94 self.id
95 }
96}
97
98pub struct BaseDocument {
99 id: usize,
101
102 pub(crate) url: DocumentUrl,
105 pub(crate) devtool_settings: DevtoolSettings,
107 pub(crate) viewport: Viewport,
109 pub(crate) viewport_scroll: crate::Point<f64>,
111
112 pub(crate) nodes: Box<Slab<Node>>,
117
118 pub(crate) stylist: Stylist,
121 pub(crate) animations: DocumentAnimationSet,
122 pub(crate) guard: SharedRwLock,
124 pub(crate) snapshots: SnapshotMap,
126
127 pub(crate) font_ctx: Arc<Mutex<parley::FontContext>>,
130 pub(crate) layout_ctx: parley::LayoutContext<TextBrush>,
132
133 pub(crate) hover_node_id: Option<usize>,
135 pub(crate) focus_node_id: Option<usize>,
137 pub(crate) active_node_id: Option<usize>,
139 pub(crate) mousedown_node_id: Option<usize>,
141
142 pub(crate) has_active_animations: bool,
144 pub(crate) has_canvas: bool,
146
147 pub(crate) nodes_to_id: HashMap<String, usize>,
149 pub(crate) nodes_to_stylesheet: BTreeMap<usize, DocumentStyleSheet>,
151 pub(crate) ua_stylesheets: HashMap<String, DocumentStyleSheet>,
154 pub(crate) controls_to_form: HashMap<usize, usize>,
156 pub(crate) changed_nodes: HashSet<usize>,
158 pub(crate) deferred_construction_nodes: Vec<ConstructionTask>,
160
161 pub net_provider: Arc<dyn NetProvider<Resource>>,
164 pub navigation_provider: Arc<dyn NavigationProvider>,
167 pub shell_provider: Arc<dyn ShellProvider>,
169 pub html_parser_provider: Arc<dyn HtmlParserProvider>,
171}
172
173pub(crate) fn make_device(viewport: &Viewport, font_ctx: Arc<Mutex<FontContext>>) -> Device {
174 let width = viewport.window_size.0 as f32 / viewport.scale();
175 let height = viewport.window_size.1 as f32 / viewport.scale();
176 let viewport_size = euclid::Size2D::new(width, height);
177 let device_pixel_ratio = euclid::Scale::new(viewport.scale());
178
179 Device::new(
180 MediaType::screen(),
181 selectors::matching::QuirksMode::NoQuirks,
182 viewport_size,
183 device_pixel_ratio,
184 Box::new(BlitzFontMetricsProvider { font_ctx }),
185 ComputedValues::initial_values_with_font_override(Font::initial_values()),
186 match viewport.color_scheme {
187 ColorScheme::Light => PrefersColorScheme::Light,
188 ColorScheme::Dark => PrefersColorScheme::Dark,
189 },
190 )
191}
192
193impl BaseDocument {
194 pub fn new(config: DocumentConfig) -> Self {
196 static ID_GENERATOR: AtomicUsize = AtomicUsize::new(1);
197
198 let id = ID_GENERATOR.fetch_add(1, Ordering::SeqCst);
199
200 let font_ctx = config
201 .font_ctx
202 .unwrap_or_else(|| {
208 let mut font_ctx = FontContext::default();
216 font_ctx
217 .collection
218 .register_fonts(Blob::new(Arc::new(crate::BULLET_FONT) as _), None);
219 font_ctx
220 });
221 let font_ctx = Arc::new(Mutex::new(font_ctx));
222
223 let viewport = config.viewport.unwrap_or_default();
224 let device = make_device(&viewport, font_ctx.clone());
225 let stylist = Stylist::new(device, QuirksMode::NoQuirks);
226 let snapshots = SnapshotMap::new();
227 let nodes = Box::new(Slab::new());
228 let guard = SharedRwLock::new();
229 let nodes_to_id = HashMap::new();
230
231 style_config::set_bool("layout.flexbox.enabled", true);
233 style_config::set_bool("layout.grid.enabled", true);
234 style_config::set_bool("layout.legacy_layout", true);
235 style_config::set_bool("layout.unimplemented", true);
236 style_config::set_bool("layout.columns.enabled", true);
237
238 let base_url = config
239 .base_url
240 .and_then(|url| DocumentUrl::from_str(&url).ok())
241 .unwrap_or_default();
242
243 let net_provider = config
244 .net_provider
245 .unwrap_or_else(|| Arc::new(DummyNetProvider));
246 let navigation_provider = config
247 .navigation_provider
248 .unwrap_or_else(|| Arc::new(DummyNavigationProvider));
249 let shell_provider = config
250 .shell_provider
251 .unwrap_or_else(|| Arc::new(DummyShellProvider));
252 let html_parser_provider = config
253 .html_parser_provider
254 .unwrap_or_else(|| Arc::new(DummyHtmlParserProvider));
255
256 let mut doc = Self {
257 id,
258 guard,
259 nodes,
260 stylist,
261 animations: DocumentAnimationSet::default(),
262 snapshots,
263 nodes_to_id,
264 viewport,
265 devtool_settings: DevtoolSettings::default(),
266 viewport_scroll: crate::Point::ZERO,
267 url: base_url,
268 ua_stylesheets: HashMap::new(),
269 nodes_to_stylesheet: BTreeMap::new(),
270 font_ctx,
271 layout_ctx: parley::LayoutContext::new(),
272
273 hover_node_id: None,
274 focus_node_id: None,
275 active_node_id: None,
276 mousedown_node_id: None,
277 has_active_animations: false,
278 has_canvas: false,
279 changed_nodes: HashSet::new(),
280 deferred_construction_nodes: Vec::new(),
281 controls_to_form: HashMap::new(),
282 net_provider,
283 navigation_provider,
284 shell_provider,
285 html_parser_provider,
286 };
287
288 doc.create_node(NodeData::Document);
290 doc.root_node_mut().flags.insert(NodeFlags::IS_IN_DOCUMENT);
291
292 match config.ua_stylesheets {
293 Some(stylesheets) => {
294 for ss in &stylesheets {
295 doc.add_user_agent_stylesheet(ss);
296 }
297 }
298 None => doc.add_user_agent_stylesheet(DEFAULT_CSS),
299 }
300
301 let stylo_element_data = StyloElementData {
303 styles: ElementStyles {
304 primary: Some(
305 ComputedValues::initial_values_with_font_override(Font::initial_values())
306 .to_arc(),
307 ),
308 ..Default::default()
309 },
310 ..Default::default()
311 };
312 *doc.root_node().stylo_element_data.borrow_mut() = Some(stylo_element_data);
313
314 doc
315 }
316
317 pub fn set_net_provider(&mut self, net_provider: SharedProvider<Resource>) {
319 self.net_provider = net_provider;
320 }
321
322 pub fn set_navigation_provider(&mut self, navigation_provider: Arc<dyn NavigationProvider>) {
324 self.navigation_provider = navigation_provider;
325 }
326
327 pub fn set_shell_provider(&mut self, shell_provider: Arc<dyn ShellProvider>) {
329 self.shell_provider = shell_provider;
330 }
331
332 pub fn set_html_parser_provider(&mut self, html_parser_provider: Arc<dyn HtmlParserProvider>) {
334 self.html_parser_provider = html_parser_provider;
335 }
336
337 pub fn set_base_url(&mut self, url: &str) {
339 self.url = DocumentUrl::from(Url::parse(url).unwrap());
340 }
341
342 pub fn guard(&self) -> &SharedRwLock {
343 &self.guard
344 }
345
346 pub fn tree(&self) -> &Slab<Node> {
347 &self.nodes
348 }
349
350 pub fn id(&self) -> usize {
351 self.id
352 }
353
354 pub fn get_node(&self, node_id: usize) -> Option<&Node> {
355 self.nodes.get(node_id)
356 }
357
358 pub fn get_node_mut(&mut self, node_id: usize) -> Option<&mut Node> {
359 self.nodes.get_mut(node_id)
360 }
361
362 pub fn get_focussed_node_id(&self) -> Option<usize> {
363 self.focus_node_id
364 .or(self.try_root_element().map(|el| el.id))
365 }
366
367 pub fn mutate<'doc>(&'doc mut self) -> DocumentMutator<'doc> {
368 DocumentMutator::new(self)
369 }
370
371 pub fn handle_dom_event<F: FnMut(DomEvent)>(
372 &mut self,
373 event: &mut DomEvent,
374 dispatch_event: F,
375 ) {
376 handle_dom_event(self, event, dispatch_event)
377 }
378
379 pub fn as_any_mut(&mut self) -> &mut dyn Any {
380 self
381 }
382
383 pub fn label_bound_input_element(&self, label_node_id: usize) -> Option<&Node> {
390 let label_element = self.nodes[label_node_id].element_data()?;
391 if let Some(target_element_dom_id) = label_element.attr(local_name!("for")) {
392 TreeTraverser::new(self)
393 .filter_map(|id| {
394 let node = self.get_node(id)?;
395 let element_data = node.element_data()?;
396 if element_data.name.local != local_name!("input") {
397 return None;
398 }
399 let id = element_data.id.as_ref()?;
400 if *id == *target_element_dom_id {
401 Some(node)
402 } else {
403 None
404 }
405 })
406 .next()
407 } else {
408 TreeTraverser::new_with_root(self, label_node_id)
409 .filter_map(|child_id| {
410 let node = self.get_node(child_id)?;
411 let element_data = node.element_data()?;
412 if element_data.name.local == local_name!("input") {
413 Some(node)
414 } else {
415 None
416 }
417 })
418 .next()
419 }
420 }
421
422 pub fn toggle_checkbox(el: &mut ElementData) -> bool {
423 let Some(is_checked) = el.checkbox_input_checked_mut() else {
424 return false;
425 };
426 *is_checked = !*is_checked;
427
428 *is_checked
429 }
430
431 pub fn toggle_radio(&mut self, radio_set_name: String, target_radio_id: usize) {
432 for i in 0..self.nodes.len() {
433 let node = &mut self.nodes[i];
434 if let Some(node_data) = node.data.downcast_element_mut() {
435 if node_data.attr(local_name!("name")) == Some(&radio_set_name) {
436 let was_clicked = i == target_radio_id;
437 let Some(is_checked) = node_data.checkbox_input_checked_mut() else {
438 continue;
439 };
440 *is_checked = was_clicked;
441 }
442 }
443 }
444 }
445
446 pub fn set_style_property(&mut self, node_id: usize, name: &str, value: &str) {
447 self.nodes[node_id]
448 .element_data_mut()
449 .unwrap()
450 .set_style_property(name, value, &self.guard, self.url.url_extra_data());
451 }
452
453 pub fn remove_style_property(&mut self, node_id: usize, name: &str) {
454 self.nodes[node_id]
455 .element_data_mut()
456 .unwrap()
457 .remove_style_property(name, &self.guard, self.url.url_extra_data());
458 }
459
460 pub fn root_node(&self) -> &Node {
461 &self.nodes[0]
462 }
463
464 pub fn root_node_mut(&mut self) -> &mut Node {
465 &mut self.nodes[0]
466 }
467
468 pub fn try_root_element(&self) -> Option<&Node> {
469 TDocument::as_node(&self.root_node()).first_element_child()
470 }
471
472 pub fn root_element(&self) -> &Node {
473 TDocument::as_node(&self.root_node())
474 .first_element_child()
475 .unwrap()
476 .as_element()
477 .unwrap()
478 }
479
480 pub fn create_node(&mut self, node_data: NodeData) -> usize {
481 let slab_ptr = self.nodes.as_mut() as *mut Slab<Node>;
482 let guard = self.guard.clone();
483
484 let entry = self.nodes.vacant_entry();
485 let id = entry.key();
486 entry.insert(Node::new(slab_ptr, id, guard, node_data));
487
488 self.changed_nodes.insert(id);
490 id
491 }
492
493 pub fn has_changes(&self) -> bool {
495 self.changed_nodes.is_empty()
496 }
497
498 pub fn create_text_node(&mut self, text: &str) -> usize {
499 let content = text.to_string();
500 let data = NodeData::Text(TextNodeData::new(content));
501 self.create_node(data)
502 }
503
504 pub fn deep_clone_node(&mut self, node_id: usize) -> usize {
505 let node = &self.nodes[node_id];
507 let data = node.data.clone();
508 let children = node.children.clone();
509
510 let new_node_id = self.create_node(data);
512
513 let new_children: Vec<usize> = children
515 .into_iter()
516 .map(|child_id| self.deep_clone_node(child_id))
517 .collect();
518 for &child_id in &new_children {
519 self.nodes[child_id].parent = Some(new_node_id);
520 }
521 self.nodes[new_node_id].children = new_children;
522
523 new_node_id
524 }
525
526 pub(crate) fn remove_and_drop_pe(&mut self, node_id: usize) -> Option<Node> {
527 fn remove_pe_ignoring_parent(doc: &mut BaseDocument, node_id: usize) -> Option<Node> {
528 let mut node = doc.nodes.try_remove(node_id);
529 if let Some(node) = &mut node {
530 for &child in &node.children {
531 remove_pe_ignoring_parent(doc, child);
532 }
533 }
534 node
535 }
536
537 let node = remove_pe_ignoring_parent(self, node_id);
538
539 if let Some(parent_id) = node.as_ref().and_then(|node| node.parent) {
541 let parent = &mut self.nodes[parent_id];
542 parent.children.retain(|id| *id != node_id);
543 }
544
545 node
546 }
547
548 pub(crate) fn resolve_url(&self, raw: &str) -> url::Url {
549 self.url.resolve_relative(raw).unwrap_or_else(|| {
550 panic!(
551 "to be able to resolve {raw} with the base_url: {:?}",
552 *self.url
553 )
554 })
555 }
556
557 pub fn print_tree(&self) {
558 crate::util::walk_tree(0, self.root_node());
559 }
560
561 pub fn print_subtree(&self, node_id: usize) {
562 crate::util::walk_tree(0, &self.nodes[node_id]);
563 }
564
565 pub fn process_style_element(&mut self, target_id: usize) {
566 let css = self.nodes[target_id].text_content();
567 let css = html_escape::decode_html_entities(&css);
568 let sheet = self.make_stylesheet(&css, Origin::Author);
569 self.add_stylesheet_for_node(sheet, target_id);
570 }
571
572 pub fn remove_user_agent_stylesheet(&mut self, contents: &str) {
573 if let Some(sheet) = self.ua_stylesheets.remove(contents) {
574 self.stylist.remove_stylesheet(sheet, &self.guard.read());
575 }
576 }
577
578 pub fn add_user_agent_stylesheet(&mut self, css: &str) {
579 let sheet = self.make_stylesheet(css, Origin::UserAgent);
580 self.ua_stylesheets.insert(css.to_string(), sheet.clone());
581 self.stylist.append_stylesheet(sheet, &self.guard.read());
582 }
583
584 pub fn make_stylesheet(&self, css: impl AsRef<str>, origin: Origin) -> DocumentStyleSheet {
585 let data = Stylesheet::from_str(
586 css.as_ref(),
587 self.url.url_extra_data(),
588 origin,
589 ServoArc::new(self.guard.wrap(MediaList::empty())),
590 self.guard.clone(),
591 Some(&StylesheetLoader(self.id, self.net_provider.clone())),
592 None,
593 QuirksMode::NoQuirks,
594 AllowImportRules::Yes,
595 );
596
597 DocumentStyleSheet(ServoArc::new(data))
598 }
599
600 pub fn upsert_stylesheet_for_node(&mut self, node_id: usize) {
601 let raw_styles = self.nodes[node_id].text_content();
602 let sheet = self.make_stylesheet(raw_styles, Origin::Author);
603 self.add_stylesheet_for_node(sheet, node_id);
604 }
605
606 pub fn add_stylesheet_for_node(&mut self, stylesheet: DocumentStyleSheet, node_id: usize) {
607 let old = self.nodes_to_stylesheet.insert(node_id, stylesheet.clone());
608
609 if let Some(old) = old {
610 self.stylist.remove_stylesheet(old, &self.guard.read())
611 }
612
613 let element = &mut self.nodes[node_id].element_data_mut().unwrap();
615 element.special_data = SpecialElementData::Stylesheet(stylesheet.clone());
616
617 let insertion_point = self
619 .nodes_to_stylesheet
620 .range((Bound::Excluded(node_id), Bound::Unbounded))
621 .next()
622 .map(|(_, sheet)| sheet);
623
624 if let Some(insertion_point) = insertion_point {
625 self.stylist.insert_stylesheet_before(
626 stylesheet,
627 insertion_point.clone(),
628 &self.guard.read(),
629 )
630 } else {
631 self.stylist
632 .append_stylesheet(stylesheet, &self.guard.read())
633 }
634 }
635
636 pub fn load_resource(&mut self, resource: Resource) {
637 match resource {
638 Resource::Css(node_id, css) => {
639 self.add_stylesheet_for_node(css, node_id);
640 }
641 Resource::Image(node_id, kind, width, height, image_data) => {
642 let node = self.get_node_mut(node_id).unwrap();
643
644 match kind {
645 ImageType::Image => {
646 node.element_data_mut().unwrap().special_data =
647 SpecialElementData::Image(Box::new(ImageData::Raster(
648 RasterImageData::new(width, height, image_data),
649 )));
650
651 node.cache.clear();
653 node.insert_damage(ALL_DAMAGE);
654 }
655 ImageType::Background(idx) => {
656 if let Some(Some(bg_image)) = node
657 .element_data_mut()
658 .and_then(|el| el.background_images.get_mut(idx))
659 {
660 bg_image.status = Status::Ok;
661 bg_image.image =
662 ImageData::Raster(RasterImageData::new(width, height, image_data))
663 }
664 }
665 }
666 }
667 #[cfg(feature = "svg")]
668 Resource::Svg(node_id, kind, tree) => {
669 let node = self.get_node_mut(node_id).unwrap();
670
671 match kind {
672 ImageType::Image => {
673 node.element_data_mut().unwrap().special_data =
674 SpecialElementData::Image(Box::new(ImageData::Svg(tree)));
675
676 node.cache.clear();
678 node.insert_damage(ALL_DAMAGE);
679 }
680 ImageType::Background(idx) => {
681 if let Some(Some(bg_image)) = node
682 .element_data_mut()
683 .and_then(|el| el.background_images.get_mut(idx))
684 {
685 bg_image.status = Status::Ok;
686 bg_image.image = ImageData::Svg(tree);
687 }
688 }
689 }
690 }
691 Resource::Font(bytes) => {
692 let font = Blob::new(Arc::new(bytes));
693
694 let mut font_ctx = self.font_ctx.lock().unwrap();
697 font_ctx.collection.register_fonts(font.clone(), None);
698
699 #[cfg(feature = "parallel-construct")]
700 {
701 let doc_font_ctx = &*font_ctx;
702 rayon::broadcast(|_ctx| {
703 FONT_CTX.with_borrow_mut(|font_ctx| {
704 match font_ctx {
705 None => {
706 println!(
707 "Initialising FontContext for thread {:?}",
708 std::thread::current().id()
709 );
710 *font_ctx = Some(Box::new(doc_font_ctx.clone()));
711 }
712 Some(font_ctx) => {
713 font_ctx.collection.register_fonts(font.clone(), None);
714 }
715 };
716 })
717 });
718 }
719 drop(font_ctx);
720
721 self.invalidate_inline_contexts();
723 }
724 Resource::None => {
725 }
727 _ => {}
728 }
729 }
730
731 pub fn snapshot_node(&mut self, node_id: usize) {
732 let node = &mut self.nodes[node_id];
733 let opaque_node_id = TNode::opaque(&&*node);
734 node.has_snapshot = true;
735 node.snapshot_handled
736 .store(false, std::sync::atomic::Ordering::SeqCst);
737
738 if let Some(_existing_snapshot) = self.snapshots.get_mut(&opaque_node_id) {
740 } else {
743 let attrs: Option<Vec<_>> = node.attrs().map(|attrs| {
744 attrs
745 .iter()
746 .map(|attr| {
747 let ident = AttrIdentifier {
748 local_name: GenericAtomIdent(attr.name.local.clone()),
749 name: GenericAtomIdent(attr.name.local.clone()),
750 namespace: GenericAtomIdent(attr.name.ns.clone()),
751 prefix: None,
752 };
753
754 let value = if attr.name.local == local_name!("id") {
755 AttrValue::Atom(Atom::from(&*attr.value))
756 } else if attr.name.local == local_name!("class") {
757 let classes = attr
758 .value
759 .split_ascii_whitespace()
760 .map(Atom::from)
761 .collect();
762 AttrValue::TokenList(attr.value.clone(), classes)
763 } else {
764 AttrValue::String(attr.value.clone())
765 };
766
767 (ident, value)
768 })
769 .collect()
770 });
771
772 let changed_attrs = attrs
773 .as_ref()
774 .map(|attrs| attrs.iter().map(|attr| attr.0.name.clone()).collect())
775 .unwrap_or_default();
776
777 self.snapshots.insert(
778 opaque_node_id,
779 ServoElementSnapshot {
780 state: Some(node.element_state),
781 attrs,
782 changed_attrs,
783 class_changed: true,
784 id_changed: true,
785 other_attributes_changed: true,
786 },
787 );
788 }
789 }
790
791 pub fn snapshot_node_and(&mut self, node_id: usize, cb: impl FnOnce(&mut Node)) {
792 self.snapshot_node(node_id);
793 cb(&mut self.nodes[node_id]);
794 }
795
796 pub fn resolve(&mut self, current_time_for_animations: f64) {
798 if TDocument::as_node(&&self.nodes[0])
799 .first_element_child()
800 .is_none()
801 {
802 println!("No DOM - not resolving");
803 return;
804 }
805
806 let root_node_id = self.root_element().id;
807 debug_timer!(timer, feature = "log_phase_times");
808
809 self.resolve_stylist(current_time_for_animations);
811 timer.record_time("style");
812
813 #[cfg(feature = "incremental")]
815 self.propagate_damage_flags(root_node_id, RestyleDamage::empty());
816 #[cfg(feature = "incremental")]
817 timer.record_time("damage");
818
819 self.resolve_layout_children();
821 timer.record_time("construct");
822
823 self.resolve_deferred_tasks();
824 timer.record_time("pconstruct");
825
826 self.flush_styles_to_layout(root_node_id);
828 timer.record_time("flush");
829
830 self.resolve_layout();
832 timer.record_time("layout");
833
834 #[cfg(feature = "incremental")]
836 {
837 for (_, node) in self.nodes.iter_mut() {
838 node.clear_damage_mut();
839 }
840 timer.record_time("c_damage");
841 }
842
843 timer.print_times("Resolve: ");
844 }
845
846 pub fn hit(&self, x: f32, y: f32) -> Option<HitResult> {
848 if TDocument::as_node(&&self.nodes[0])
849 .first_element_child()
850 .is_none()
851 {
852 println!("No DOM - not resolving");
853 return None;
854 }
855
856 self.root_element().hit(x, y)
857 }
858
859 pub fn focus_next_node(&mut self) -> Option<usize> {
860 let focussed_node_id = self.get_focussed_node_id()?;
861 let id = self.next_node(&self.nodes[focussed_node_id], |node| node.is_focussable())?;
862 self.set_focus_to(id);
863 Some(id)
864 }
865
866 pub fn clear_focus(&mut self) {
868 if let Some(id) = self.focus_node_id {
869 self.snapshot_node_and(id, |node| node.blur());
870 self.focus_node_id = None;
871 }
872 }
873
874 pub fn set_mousedown_node_id(&mut self, node_id: Option<usize>) {
875 self.mousedown_node_id = node_id;
876 }
877 pub fn set_focus_to(&mut self, focus_node_id: usize) -> bool {
878 if Some(focus_node_id) == self.focus_node_id {
879 return false;
880 }
881
882 println!("Focussed node {focus_node_id}");
883
884 if let Some(id) = self.focus_node_id {
886 self.snapshot_node_and(id, |node| node.blur());
887 }
888
889 self.snapshot_node_and(focus_node_id, |node| node.focus());
891
892 self.focus_node_id = Some(focus_node_id);
893
894 true
895 }
896
897 pub fn active_node(&mut self) -> bool {
898 let Some(hover_node_id) = self.get_hover_node_id() else {
899 return false;
900 };
901
902 if let Some(active_node_id) = self.active_node_id {
903 if active_node_id == hover_node_id {
904 return true;
905 }
906 self.unactive_node();
907 }
908
909 let active_node_id = Some(hover_node_id);
910
911 let node_path = self.maybe_node_layout_ancestors(active_node_id);
912 for &id in node_path.iter() {
913 self.snapshot_node_and(id, |node| node.active());
914 }
915
916 self.active_node_id = active_node_id;
917
918 true
919 }
920
921 pub fn unactive_node(&mut self) -> bool {
922 let Some(active_node_id) = self.active_node_id.take() else {
923 return false;
924 };
925
926 let node_path = self.maybe_node_layout_ancestors(Some(active_node_id));
927 for &id in node_path.iter() {
928 self.snapshot_node_and(id, |node| node.unactive());
929 }
930
931 true
932 }
933
934 pub fn set_hover_to(&mut self, x: f32, y: f32) -> bool {
935 let hit = self.hit(x, y);
936 let hover_node_id = hit.map(|hit| hit.node_id);
937
938 if hover_node_id == self.hover_node_id {
940 return false;
941 }
942
943 let old_node_path = self.maybe_node_layout_ancestors(self.hover_node_id);
944 let new_node_path = self.maybe_node_layout_ancestors(hover_node_id);
945 let same_count = old_node_path
946 .iter()
947 .zip(&new_node_path)
948 .take_while(|(o, n)| o == n)
949 .count();
950 for &id in old_node_path.iter().skip(same_count) {
951 self.snapshot_node_and(id, |node| node.unhover());
952 }
953 for &id in new_node_path.iter().skip(same_count) {
954 self.snapshot_node_and(id, |node| node.hover());
955 }
956
957 self.hover_node_id = hover_node_id;
958
959 let cursor = self.get_cursor().unwrap_or_default();
961 self.shell_provider.set_cursor(cursor);
962
963 self.shell_provider.request_redraw();
965
966 true
967 }
968
969 pub fn get_hover_node_id(&self) -> Option<usize> {
970 self.hover_node_id
971 }
972
973 pub fn set_viewport(&mut self, viewport: Viewport) {
974 let scale_has_changed = viewport.scale_f64() != self.viewport.scale_f64();
975 self.viewport = viewport;
976 self.set_stylist_device(make_device(&self.viewport, self.font_ctx.clone()));
977 self.scroll_viewport_by(0.0, 0.0); if scale_has_changed {
980 self.invalidate_inline_contexts();
981 }
982 }
983
984 pub fn viewport(&self) -> &Viewport {
985 &self.viewport
986 }
987
988 pub fn viewport_mut(&mut self) -> ViewportMut<'_> {
989 ViewportMut::new(self)
990 }
991
992 pub fn zoom_by(&mut self, increment: f32) {
993 *self.viewport.zoom_mut() += increment;
994 self.set_viewport(self.viewport.clone());
995 }
996
997 pub fn zoom_to(&mut self, zoom: f32) {
998 *self.viewport.zoom_mut() = zoom;
999 self.set_viewport(self.viewport.clone());
1000 }
1001
1002 pub fn get_viewport(&self) -> Viewport {
1003 self.viewport.clone()
1004 }
1005
1006 pub fn devtools(&self) -> &DevtoolSettings {
1007 &self.devtool_settings
1008 }
1009
1010 pub fn devtools_mut(&mut self) -> &mut DevtoolSettings {
1011 &mut self.devtool_settings
1012 }
1013
1014 pub fn is_animating(&self) -> bool {
1015 self.has_canvas | self.has_active_animations
1016 }
1017
1018 pub fn set_stylist_device(&mut self, device: Device) {
1020 let origins = {
1021 let guard = &self.guard;
1022 let guards = StylesheetGuards {
1023 author: &guard.read(),
1024 ua_or_user: &guard.read(),
1025 };
1026 self.stylist.set_device(device, &guards)
1027 };
1028 self.stylist.force_stylesheet_origins_dirty(origins);
1029 }
1030
1031 pub fn stylist_device(&mut self) -> &Device {
1032 self.stylist.device()
1033 }
1034
1035 pub fn resolve_layout_children(&mut self) {
1037 resolve_layout_children_recursive(self, self.root_node().id);
1038
1039 fn resolve_layout_children_recursive(doc: &mut BaseDocument, node_id: usize) {
1040 let mut damage = doc.nodes[node_id].damage().unwrap_or(ALL_DAMAGE);
1041 let _flags = doc.nodes[node_id].flags;
1042
1043 if NON_INCREMENTAL || damage.intersects(CONSTRUCT_FC | CONSTRUCT_BOX) {
1044 let mut layout_children = Vec::new();
1046 let mut anonymous_block: Option<usize> = None;
1047 collect_layout_children(doc, node_id, &mut layout_children, &mut anonymous_block);
1048
1049 for child_id in layout_children.iter().copied() {
1051 resolve_layout_children_recursive(doc, child_id);
1052 doc.nodes[child_id].layout_parent.set(Some(node_id));
1053 if let Some(data) = doc.nodes[child_id].stylo_element_data.get_mut() {
1054 data.damage
1055 .remove(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
1056 }
1057 }
1058
1059 *doc.nodes[node_id].layout_children.borrow_mut() = Some(layout_children.clone());
1060 *doc.nodes[node_id].paint_children.borrow_mut() = Some(layout_children);
1061
1062 damage.remove(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
1063 } else {
1065 let layout_children = doc.nodes[node_id].layout_children.borrow_mut().take();
1067 if let Some(layout_children) = layout_children {
1068 for child_id in layout_children.iter().copied() {
1070 resolve_layout_children_recursive(doc, child_id);
1071 doc.nodes[child_id].layout_parent.set(Some(node_id));
1072 }
1073
1074 *doc.nodes[node_id].layout_children.borrow_mut() = Some(layout_children);
1075 }
1076
1077 }
1080
1081 doc.nodes[node_id].set_damage(damage);
1082 }
1083 }
1084
1085 pub fn resolve_deferred_tasks(&mut self) {
1086 let mut deferred_construction_nodes = std::mem::take(&mut self.deferred_construction_nodes);
1087
1088 deferred_construction_nodes.sort_unstable_by_key(|task| task.node_id);
1090 deferred_construction_nodes.dedup_by_key(|task| task.node_id);
1091
1092 #[cfg(feature = "parallel-construct")]
1093 let iter = deferred_construction_nodes.into_par_iter();
1094 #[cfg(not(feature = "parallel-construct"))]
1095 let iter = deferred_construction_nodes.into_iter();
1096
1097 let results: Vec<ConstructionTaskResult> = iter
1098 .map(|task: ConstructionTask| match task.data {
1099 ConstructionTaskData::InlineLayout(mut layout) => {
1100 #[cfg(feature = "parallel-construct")]
1101 let mut layout_ctx = LAYOUT_CTX
1102 .take()
1103 .unwrap_or_else(|| Box::new(LayoutContext::new()));
1104 #[cfg(feature = "parallel-construct")]
1105 let layout_ctx_mut = &mut layout_ctx;
1106
1107 #[cfg(feature = "parallel-construct")]
1108 let mut font_ctx = FONT_CTX
1109 .take()
1110 .unwrap_or_else(|| Box::new(self.font_ctx.lock().unwrap().clone()));
1111 #[cfg(feature = "parallel-construct")]
1112 let font_ctx_mut = &mut font_ctx;
1113
1114 #[cfg(not(feature = "parallel-construct"))]
1115 let layout_ctx_mut = &mut self.layout_ctx;
1116 #[cfg(not(feature = "parallel-construct"))]
1117 let font_ctx_mut = &mut *self.font_ctx.lock().unwrap();
1118
1119 layout.content_widths = None;
1120 build_inline_layout_into(
1121 &self.nodes,
1122 layout_ctx_mut,
1123 font_ctx_mut,
1124 &mut layout,
1125 self.viewport.scale(),
1126 task.node_id,
1127 );
1128
1129 #[cfg(feature = "parallel-construct")]
1130 {
1131 LAYOUT_CTX.set(Some(layout_ctx));
1132 FONT_CTX.set(Some(font_ctx));
1133 }
1134
1135 ConstructionTaskResult {
1142 node_id: task.node_id,
1143 data: ConstructionTaskResultData::InlineLayout(layout),
1144 }
1145 }
1146 })
1147 .collect();
1148
1149 for result in results {
1150 match result.data {
1151 ConstructionTaskResultData::InlineLayout(layout) => {
1152 self.nodes[result.node_id].cache.clear();
1153 self.nodes[result.node_id]
1154 .element_data_mut()
1155 .unwrap()
1156 .inline_layout_data = Some(layout);
1157 }
1158 }
1159 }
1160
1161 self.deferred_construction_nodes.clear();
1162 }
1163
1164 pub fn resolve_layout(&mut self) {
1169 let size = self.stylist.device().au_viewport_size();
1170
1171 let available_space = taffy::Size {
1172 width: AvailableSpace::Definite(size.width.to_f32_px()),
1173 height: AvailableSpace::Definite(size.height.to_f32_px()),
1174 };
1175
1176 let root_element_id = taffy::NodeId::from(self.root_element().id);
1177
1178 taffy::compute_root_layout(self, root_element_id, available_space);
1181 taffy::round_layout(self, root_element_id);
1182
1183 }
1186
1187 pub fn get_cursor(&self) -> Option<CursorIcon> {
1188 let node = &self.nodes[self.get_hover_node_id()?];
1190
1191 let style = node.primary_styles()?;
1192 let keyword = stylo_to_cursor_icon(style.clone_cursor().keyword);
1193
1194 if keyword != CursorIcon::Default {
1196 return Some(keyword);
1197 }
1198
1199 if node.is_text_node()
1201 || node
1202 .element_data()
1203 .is_some_and(|e| e.text_input_data().is_some())
1204 {
1205 return Some(CursorIcon::Text);
1206 }
1207
1208 let mut maybe_node = Some(node);
1210 while let Some(node) = maybe_node {
1211 if node.is_link() {
1212 return Some(CursorIcon::Pointer);
1213 }
1214
1215 maybe_node = node.layout_parent.get().map(|node_id| node.with(node_id));
1216 }
1217
1218 Some(CursorIcon::Default)
1220 }
1221
1222 pub fn scroll_node_by(&mut self, node_id: usize, x: f64, y: f64) {
1223 self.scroll_node_by_has_changed(node_id, x, y);
1224 }
1225
1226 pub fn scroll_node_by_has_changed(&mut self, node_id: usize, x: f64, y: f64) -> bool {
1230 let Some(node) = self.nodes.get_mut(node_id) else {
1231 return false;
1232 };
1233
1234 let is_html_or_body = node.data.downcast_element().is_some_and(|e| {
1235 let tag = &e.name.local;
1236 tag == "html" || tag == "body"
1237 });
1238
1239 let (can_x_scroll, can_y_scroll) = node
1240 .primary_styles()
1241 .map(|styles| {
1242 (
1243 matches!(styles.clone_overflow_x(), Overflow::Scroll | Overflow::Auto),
1244 matches!(styles.clone_overflow_y(), Overflow::Scroll | Overflow::Auto)
1245 || (styles.clone_overflow_y() == Overflow::Visible && is_html_or_body),
1246 )
1247 })
1248 .unwrap_or((false, false));
1249
1250 let initial = node.scroll_offset;
1251 let new_x = node.scroll_offset.x - x;
1252 let new_y = node.scroll_offset.y - y;
1253
1254 let mut bubble_x = 0.0;
1255 let mut bubble_y = 0.0;
1256
1257 let scroll_width = node.final_layout.scroll_width() as f64;
1258 let scroll_height = node.final_layout.scroll_height() as f64;
1259
1260 if !can_x_scroll {
1262 bubble_x = x
1263 } else if new_x < 0.0 {
1264 bubble_x = -new_x;
1265 node.scroll_offset.x = 0.0;
1266 } else if new_x > scroll_width {
1267 bubble_x = scroll_width - new_x;
1268 node.scroll_offset.x = scroll_width;
1269 } else {
1270 node.scroll_offset.x = new_x;
1271 }
1272
1273 if !can_y_scroll {
1274 bubble_y = y
1275 } else if new_y < 0.0 {
1276 bubble_y = -new_y;
1277 node.scroll_offset.y = 0.0;
1278 } else if new_y > scroll_height {
1279 bubble_y = scroll_height - new_y;
1280 node.scroll_offset.y = scroll_height;
1281 } else {
1282 node.scroll_offset.y = new_y;
1283 }
1284
1285 let has_changed = node.scroll_offset != initial;
1286
1287 if bubble_x != 0.0 || bubble_y != 0.0 {
1288 if let Some(parent) = node.parent {
1289 return self.scroll_node_by_has_changed(parent, bubble_x, bubble_y) | has_changed;
1290 } else {
1291 return self.scroll_viewport_by_has_changed(bubble_x, bubble_y) | has_changed;
1292 }
1293 }
1294
1295 has_changed
1296 }
1297
1298 pub fn scroll_viewport_by(&mut self, x: f64, y: f64) {
1299 self.scroll_viewport_by_has_changed(x, y);
1300 }
1301
1302 pub fn scroll_viewport_by_has_changed(&mut self, x: f64, y: f64) -> bool {
1304 let content_size = self.root_element().final_layout.size;
1305 let new_scroll = (self.viewport_scroll.x - x, self.viewport_scroll.y - y);
1306 let window_width = self.viewport.window_size.0 as f64 / self.viewport.scale() as f64;
1307 let window_height = self.viewport.window_size.1 as f64 / self.viewport.scale() as f64;
1308
1309 let initial = self.viewport_scroll;
1310 self.viewport_scroll.x = f64::max(
1311 0.0,
1312 f64::min(new_scroll.0, content_size.width as f64 - window_width),
1313 );
1314 self.viewport_scroll.y = f64::max(
1315 0.0,
1316 f64::min(new_scroll.1, content_size.height as f64 - window_height),
1317 );
1318
1319 self.viewport_scroll != initial
1320 }
1321
1322 pub fn viewport_scroll(&self) -> crate::Point<f64> {
1323 self.viewport_scroll
1324 }
1325
1326 pub fn set_viewport_scroll(&mut self, scroll: crate::Point<f64>) {
1327 self.viewport_scroll = scroll;
1328 }
1329
1330 pub fn find_title_node(&self) -> Option<&Node> {
1331 TreeTraverser::new(self)
1332 .find(|node_id| {
1333 self.nodes[*node_id]
1334 .data
1335 .is_element_with_tag_name(&local_name!("title"))
1336 })
1337 .map(|node_id| &self.nodes[node_id])
1338 }
1339
1340 pub(crate) fn compute_has_canvas(&self) -> bool {
1341 TreeTraverser::new(self).any(|node_id| {
1342 let node = &self.nodes[node_id];
1343 let Some(element) = node.element_data() else {
1344 return false;
1345 };
1346 if element.name.local == local_name!("canvas") && element.has_attr(local_name!("src")) {
1347 return true;
1348 }
1349
1350 false
1351 })
1352 }
1353}
1354
1355impl AsRef<BaseDocument> for BaseDocument {
1356 fn as_ref(&self) -> &BaseDocument {
1357 self
1358 }
1359}
1360
1361impl AsMut<BaseDocument> for BaseDocument {
1362 fn as_mut(&mut self) -> &mut BaseDocument {
1363 self
1364 }
1365}