1use crate::events::handle_dom_event;
2use crate::font_metrics::BlitzFontMetricsProvider;
3use crate::layout::construct::{
4 ConstructionTask, ConstructionTaskData, ConstructionTaskResult, ConstructionTaskResultData,
5 build_inline_layout_into, collect_layout_children,
6};
7use crate::layout::damage::{ALL_DAMAGE, CONSTRUCT_BOX, CONSTRUCT_DESCENDENT, CONSTRUCT_FC};
8use crate::mutator::ViewportMut;
9use crate::net::{CssHandler, Resource, StylesheetLoader};
10use crate::node::{ImageData, NodeFlags, RasterImageData, SpecialElementData, Status, TextBrush};
11use crate::stylo_to_cursor_icon::stylo_to_cursor_icon;
12use crate::traversal::TreeTraverser;
13use crate::url::DocumentUrl;
14use crate::util::ImageType;
15use crate::{
16 DEFAULT_CSS, DocumentConfig, DocumentMutator, DummyHtmlParserProvider, ElementData,
17 EventDriver, HtmlParserProvider, NON_INCREMENTAL, Node, NodeData, NoopEventHandler,
18 TextNodeData,
19};
20use blitz_traits::devtools::DevtoolSettings;
21use blitz_traits::events::{DomEvent, HitResult, UiEvent};
22use blitz_traits::navigation::{DummyNavigationProvider, NavigationProvider};
23use blitz_traits::net::{DummyNetProvider, NetProvider, Request, SharedProvider};
24use blitz_traits::shell::{ColorScheme, DummyShellProvider, ShellProvider, Viewport};
25use cursor_icon::CursorIcon;
26use debug_timer::debug_timer;
27use linebender_resource_handle::Blob;
28use markup5ever::local_name;
29use parley::{FontContext, LayoutContext};
30use selectors::{Element, matching::QuirksMode};
31use slab::Slab;
32use std::any::Any;
33use std::cell::RefCell;
34use std::collections::{BTreeMap, Bound, HashMap, HashSet};
35use std::ops::{Deref, DerefMut};
36use std::str::FromStr;
37use std::sync::atomic::{AtomicUsize, Ordering};
38use std::sync::{Arc, Mutex};
39use std::task::Context as TaskContext;
40use style::Atom;
41use style::animation::DocumentAnimationSet;
42use style::attr::{AttrIdentifier, AttrValue};
43use style::data::{ElementData as StyloElementData, ElementStyles};
44use style::media_queries::MediaType;
45use style::properties::ComputedValues;
46use style::properties::style_structs::Font;
47use style::queries::values::PrefersColorScheme;
48use style::selector_parser::ServoElementSnapshot;
49use style::servo_arc::Arc as ServoArc;
50use style::values::GenericAtomIdent;
51use style::values::computed::Overflow;
52use style::{
53 dom::{TDocument, TNode},
54 media_queries::{Device, MediaList},
55 selector_parser::SnapshotMap,
56 shared_lock::{SharedRwLock, StylesheetGuards},
57 stylesheets::{AllowImportRules, DocumentStyleSheet, Origin, Stylesheet},
58 stylist::Stylist,
59};
60use taffy::AvailableSpace;
61use url::Url;
62
63#[cfg(feature = "parallel-construct")]
64use rayon::prelude::*;
65
66thread_local! {
67 static LAYOUT_CTX: RefCell<Option<Box<LayoutContext<TextBrush>>>> = const { RefCell::new(None) };
68 static FONT_CTX: RefCell<Option<Box<FontContext>>> = const { RefCell::new(None) };
69}
70
71#[cfg(feature = "incremental")]
72use style::selector_parser::RestyleDamage;
73
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 reload_resource_by_href(&mut self, href_to_reload: &str) {
566 for &node_id in self.nodes_to_stylesheet.keys() {
567 let node = &self.nodes[node_id];
568 let Some(element) = node.element_data() else {
569 continue;
570 };
571
572 if element.name.local == local_name!("link") {
573 if let Some(href) = element.attr(local_name!("href")) {
574 if href == href_to_reload {
576 let resolved_href = self.resolve_url(href);
577 self.net_provider.fetch(
578 self.id(),
579 Request::get(resolved_href.clone()),
580 Box::new(CssHandler {
581 node: node_id,
582 source_url: resolved_href,
583 guard: self.guard.clone(),
584 provider: self.net_provider.clone(),
585 }),
586 );
587 }
588 }
589 }
590 }
591 }
592
593 pub fn process_style_element(&mut self, target_id: usize) {
594 let css = self.nodes[target_id].text_content();
595 let css = html_escape::decode_html_entities(&css);
596 let sheet = self.make_stylesheet(&css, Origin::Author);
597 self.add_stylesheet_for_node(sheet, target_id);
598 }
599
600 pub fn remove_user_agent_stylesheet(&mut self, contents: &str) {
601 if let Some(sheet) = self.ua_stylesheets.remove(contents) {
602 self.stylist.remove_stylesheet(sheet, &self.guard.read());
603 }
604 }
605
606 pub fn add_user_agent_stylesheet(&mut self, css: &str) {
607 let sheet = self.make_stylesheet(css, Origin::UserAgent);
608 self.ua_stylesheets.insert(css.to_string(), sheet.clone());
609 self.stylist.append_stylesheet(sheet, &self.guard.read());
610 }
611
612 pub fn make_stylesheet(&self, css: impl AsRef<str>, origin: Origin) -> DocumentStyleSheet {
613 let data = Stylesheet::from_str(
614 css.as_ref(),
615 self.url.url_extra_data(),
616 origin,
617 ServoArc::new(self.guard.wrap(MediaList::empty())),
618 self.guard.clone(),
619 Some(&StylesheetLoader(self.id, self.net_provider.clone())),
620 None,
621 QuirksMode::NoQuirks,
622 AllowImportRules::Yes,
623 );
624
625 DocumentStyleSheet(ServoArc::new(data))
626 }
627
628 pub fn upsert_stylesheet_for_node(&mut self, node_id: usize) {
629 let raw_styles = self.nodes[node_id].text_content();
630 let sheet = self.make_stylesheet(raw_styles, Origin::Author);
631 self.add_stylesheet_for_node(sheet, node_id);
632 }
633
634 pub fn add_stylesheet_for_node(&mut self, stylesheet: DocumentStyleSheet, node_id: usize) {
635 let old = self.nodes_to_stylesheet.insert(node_id, stylesheet.clone());
636
637 if let Some(old) = old {
638 self.stylist.remove_stylesheet(old, &self.guard.read())
639 }
640
641 let element = &mut self.nodes[node_id].element_data_mut().unwrap();
643 element.special_data = SpecialElementData::Stylesheet(stylesheet.clone());
644
645 let insertion_point = self
647 .nodes_to_stylesheet
648 .range((Bound::Excluded(node_id), Bound::Unbounded))
649 .next()
650 .map(|(_, sheet)| sheet);
651
652 if let Some(insertion_point) = insertion_point {
653 self.stylist.insert_stylesheet_before(
654 stylesheet,
655 insertion_point.clone(),
656 &self.guard.read(),
657 )
658 } else {
659 self.stylist
660 .append_stylesheet(stylesheet, &self.guard.read())
661 }
662 }
663
664 pub fn load_resource(&mut self, resource: Resource) {
665 match resource {
666 Resource::Css(node_id, css) => {
667 self.add_stylesheet_for_node(css, node_id);
668 }
669 Resource::Image(node_id, kind, width, height, image_data) => {
670 let node = self.get_node_mut(node_id).unwrap();
671
672 match kind {
673 ImageType::Image => {
674 node.element_data_mut().unwrap().special_data =
675 SpecialElementData::Image(Box::new(ImageData::Raster(
676 RasterImageData::new(width, height, image_data),
677 )));
678
679 node.cache.clear();
681 node.insert_damage(ALL_DAMAGE);
682 }
683 ImageType::Background(idx) => {
684 if let Some(Some(bg_image)) = node
685 .element_data_mut()
686 .and_then(|el| el.background_images.get_mut(idx))
687 {
688 bg_image.status = Status::Ok;
689 bg_image.image =
690 ImageData::Raster(RasterImageData::new(width, height, image_data))
691 }
692 }
693 }
694 }
695 #[cfg(feature = "svg")]
696 Resource::Svg(node_id, kind, tree) => {
697 let node = self.get_node_mut(node_id).unwrap();
698
699 match kind {
700 ImageType::Image => {
701 node.element_data_mut().unwrap().special_data =
702 SpecialElementData::Image(Box::new(ImageData::Svg(tree)));
703
704 node.cache.clear();
706 node.insert_damage(ALL_DAMAGE);
707 }
708 ImageType::Background(idx) => {
709 if let Some(Some(bg_image)) = node
710 .element_data_mut()
711 .and_then(|el| el.background_images.get_mut(idx))
712 {
713 bg_image.status = Status::Ok;
714 bg_image.image = ImageData::Svg(tree);
715 }
716 }
717 }
718 }
719 Resource::Font(bytes) => {
720 let font = Blob::new(Arc::new(bytes));
721
722 let mut font_ctx = self.font_ctx.lock().unwrap();
725 font_ctx.collection.register_fonts(font.clone(), None);
726
727 #[cfg(feature = "parallel-construct")]
728 {
729 let doc_font_ctx = &*font_ctx;
730 rayon::broadcast(|_ctx| {
731 FONT_CTX.with_borrow_mut(|font_ctx| {
732 match font_ctx {
733 None => {
734 println!(
735 "Initialising FontContext for thread {:?}",
736 std::thread::current().id()
737 );
738 *font_ctx = Some(Box::new(doc_font_ctx.clone()));
739 }
740 Some(font_ctx) => {
741 font_ctx.collection.register_fonts(font.clone(), None);
742 }
743 };
744 })
745 });
746 }
747 drop(font_ctx);
748
749 self.invalidate_inline_contexts();
751 }
752 Resource::None => {
753 }
755 _ => {}
756 }
757 }
758
759 pub fn snapshot_node(&mut self, node_id: usize) {
760 let node = &mut self.nodes[node_id];
761 let opaque_node_id = TNode::opaque(&&*node);
762 node.has_snapshot = true;
763 node.snapshot_handled
764 .store(false, std::sync::atomic::Ordering::SeqCst);
765
766 if let Some(_existing_snapshot) = self.snapshots.get_mut(&opaque_node_id) {
768 } else {
771 let attrs: Option<Vec<_>> = node.attrs().map(|attrs| {
772 attrs
773 .iter()
774 .map(|attr| {
775 let ident = AttrIdentifier {
776 local_name: GenericAtomIdent(attr.name.local.clone()),
777 name: GenericAtomIdent(attr.name.local.clone()),
778 namespace: GenericAtomIdent(attr.name.ns.clone()),
779 prefix: None,
780 };
781
782 let value = if attr.name.local == local_name!("id") {
783 AttrValue::Atom(Atom::from(&*attr.value))
784 } else if attr.name.local == local_name!("class") {
785 let classes = attr
786 .value
787 .split_ascii_whitespace()
788 .map(Atom::from)
789 .collect();
790 AttrValue::TokenList(attr.value.clone(), classes)
791 } else {
792 AttrValue::String(attr.value.clone())
793 };
794
795 (ident, value)
796 })
797 .collect()
798 });
799
800 let changed_attrs = attrs
801 .as_ref()
802 .map(|attrs| attrs.iter().map(|attr| attr.0.name.clone()).collect())
803 .unwrap_or_default();
804
805 self.snapshots.insert(
806 opaque_node_id,
807 ServoElementSnapshot {
808 state: Some(node.element_state),
809 attrs,
810 changed_attrs,
811 class_changed: true,
812 id_changed: true,
813 other_attributes_changed: true,
814 },
815 );
816 }
817 }
818
819 pub fn snapshot_node_and(&mut self, node_id: usize, cb: impl FnOnce(&mut Node)) {
820 self.snapshot_node(node_id);
821 cb(&mut self.nodes[node_id]);
822 }
823
824 pub fn resolve(&mut self, current_time_for_animations: f64) {
826 if TDocument::as_node(&&self.nodes[0])
827 .first_element_child()
828 .is_none()
829 {
830 println!("No DOM - not resolving");
831 return;
832 }
833
834 let root_node_id = self.root_element().id;
835 debug_timer!(timer, feature = "log_phase_times");
836
837 self.resolve_stylist(current_time_for_animations);
839 timer.record_time("style");
840
841 #[cfg(feature = "incremental")]
843 self.propagate_damage_flags(root_node_id, RestyleDamage::empty());
844 #[cfg(feature = "incremental")]
845 timer.record_time("damage");
846
847 self.resolve_layout_children();
849 timer.record_time("construct");
850
851 self.resolve_deferred_tasks();
852 timer.record_time("pconstruct");
853
854 self.flush_styles_to_layout(root_node_id);
856 timer.record_time("flush");
857
858 self.resolve_layout();
860 timer.record_time("layout");
861
862 #[cfg(feature = "incremental")]
864 {
865 for (_, node) in self.nodes.iter_mut() {
866 node.clear_damage_mut();
867 }
868 timer.record_time("c_damage");
869 }
870
871 timer.print_times("Resolve: ");
872 }
873
874 pub fn hit(&self, x: f32, y: f32) -> Option<HitResult> {
876 if TDocument::as_node(&&self.nodes[0])
877 .first_element_child()
878 .is_none()
879 {
880 println!("No DOM - not resolving");
881 return None;
882 }
883
884 self.root_element().hit(x, y)
885 }
886
887 pub fn focus_next_node(&mut self) -> Option<usize> {
888 let focussed_node_id = self.get_focussed_node_id()?;
889 let id = self.next_node(&self.nodes[focussed_node_id], |node| node.is_focussable())?;
890 self.set_focus_to(id);
891 Some(id)
892 }
893
894 pub fn clear_focus(&mut self) {
896 if let Some(id) = self.focus_node_id {
897 self.snapshot_node_and(id, |node| node.blur());
898 self.focus_node_id = None;
899 }
900 }
901
902 pub fn set_mousedown_node_id(&mut self, node_id: Option<usize>) {
903 self.mousedown_node_id = node_id;
904 }
905 pub fn set_focus_to(&mut self, focus_node_id: usize) -> bool {
906 if Some(focus_node_id) == self.focus_node_id {
907 return false;
908 }
909
910 #[cfg(feature = "tracing")]
911 tracing::info!("Focussed node {focus_node_id}");
912
913 if let Some(id) = self.focus_node_id {
915 self.snapshot_node_and(id, |node| node.blur());
916 }
917
918 self.snapshot_node_and(focus_node_id, |node| node.focus());
920
921 self.focus_node_id = Some(focus_node_id);
922
923 true
924 }
925
926 pub fn active_node(&mut self) -> bool {
927 let Some(hover_node_id) = self.get_hover_node_id() else {
928 return false;
929 };
930
931 if let Some(active_node_id) = self.active_node_id {
932 if active_node_id == hover_node_id {
933 return true;
934 }
935 self.unactive_node();
936 }
937
938 let active_node_id = Some(hover_node_id);
939
940 let node_path = self.maybe_node_layout_ancestors(active_node_id);
941 for &id in node_path.iter() {
942 self.snapshot_node_and(id, |node| node.active());
943 }
944
945 self.active_node_id = active_node_id;
946
947 true
948 }
949
950 pub fn unactive_node(&mut self) -> bool {
951 let Some(active_node_id) = self.active_node_id.take() else {
952 return false;
953 };
954
955 let node_path = self.maybe_node_layout_ancestors(Some(active_node_id));
956 for &id in node_path.iter() {
957 self.snapshot_node_and(id, |node| node.unactive());
958 }
959
960 true
961 }
962
963 pub fn set_hover_to(&mut self, x: f32, y: f32) -> bool {
964 let hit = self.hit(x, y);
965 let hover_node_id = hit.map(|hit| hit.node_id);
966
967 if hover_node_id == self.hover_node_id {
969 return false;
970 }
971
972 let old_node_path = self.maybe_node_layout_ancestors(self.hover_node_id);
973 let new_node_path = self.maybe_node_layout_ancestors(hover_node_id);
974 let same_count = old_node_path
975 .iter()
976 .zip(&new_node_path)
977 .take_while(|(o, n)| o == n)
978 .count();
979 for &id in old_node_path.iter().skip(same_count) {
980 self.snapshot_node_and(id, |node| node.unhover());
981 }
982 for &id in new_node_path.iter().skip(same_count) {
983 self.snapshot_node_and(id, |node| node.hover());
984 }
985
986 self.hover_node_id = hover_node_id;
987
988 let cursor = self.get_cursor().unwrap_or_default();
990 self.shell_provider.set_cursor(cursor);
991
992 self.shell_provider.request_redraw();
994
995 true
996 }
997
998 pub fn get_hover_node_id(&self) -> Option<usize> {
999 self.hover_node_id
1000 }
1001
1002 pub fn set_viewport(&mut self, viewport: Viewport) {
1003 let scale_has_changed = viewport.scale_f64() != self.viewport.scale_f64();
1004 self.viewport = viewport;
1005 self.set_stylist_device(make_device(&self.viewport, self.font_ctx.clone()));
1006 self.scroll_viewport_by(0.0, 0.0); if scale_has_changed {
1009 self.invalidate_inline_contexts();
1010 }
1011 }
1012
1013 pub fn viewport(&self) -> &Viewport {
1014 &self.viewport
1015 }
1016
1017 pub fn viewport_mut(&mut self) -> ViewportMut<'_> {
1018 ViewportMut::new(self)
1019 }
1020
1021 pub fn zoom_by(&mut self, increment: f32) {
1022 *self.viewport.zoom_mut() += increment;
1023 self.set_viewport(self.viewport.clone());
1024 }
1025
1026 pub fn zoom_to(&mut self, zoom: f32) {
1027 *self.viewport.zoom_mut() = zoom;
1028 self.set_viewport(self.viewport.clone());
1029 }
1030
1031 pub fn get_viewport(&self) -> Viewport {
1032 self.viewport.clone()
1033 }
1034
1035 pub fn devtools(&self) -> &DevtoolSettings {
1036 &self.devtool_settings
1037 }
1038
1039 pub fn devtools_mut(&mut self) -> &mut DevtoolSettings {
1040 &mut self.devtool_settings
1041 }
1042
1043 pub fn is_animating(&self) -> bool {
1044 self.has_canvas | self.has_active_animations
1045 }
1046
1047 pub fn set_stylist_device(&mut self, device: Device) {
1049 let origins = {
1050 let guard = &self.guard;
1051 let guards = StylesheetGuards {
1052 author: &guard.read(),
1053 ua_or_user: &guard.read(),
1054 };
1055 self.stylist.set_device(device, &guards)
1056 };
1057 self.stylist.force_stylesheet_origins_dirty(origins);
1058 }
1059
1060 pub fn stylist_device(&mut self) -> &Device {
1061 self.stylist.device()
1062 }
1063
1064 pub fn resolve_layout_children(&mut self) {
1066 resolve_layout_children_recursive(self, self.root_node().id);
1067
1068 fn resolve_layout_children_recursive(doc: &mut BaseDocument, node_id: usize) {
1069 let mut damage = doc.nodes[node_id].damage().unwrap_or(ALL_DAMAGE);
1070 let _flags = doc.nodes[node_id].flags;
1071
1072 if NON_INCREMENTAL || damage.intersects(CONSTRUCT_FC | CONSTRUCT_BOX) {
1073 let mut layout_children = Vec::new();
1075 let mut anonymous_block: Option<usize> = None;
1076 collect_layout_children(doc, node_id, &mut layout_children, &mut anonymous_block);
1077
1078 for child_id in layout_children.iter().copied() {
1080 resolve_layout_children_recursive(doc, child_id);
1081 doc.nodes[child_id].layout_parent.set(Some(node_id));
1082 if let Some(data) = doc.nodes[child_id].stylo_element_data.get_mut() {
1083 data.damage
1084 .remove(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
1085 }
1086 }
1087
1088 *doc.nodes[node_id].layout_children.borrow_mut() = Some(layout_children.clone());
1089 *doc.nodes[node_id].paint_children.borrow_mut() = Some(layout_children);
1090
1091 damage.remove(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
1092 } else {
1094 let layout_children = doc.nodes[node_id].layout_children.borrow_mut().take();
1096 if let Some(layout_children) = layout_children {
1097 for child_id in layout_children.iter().copied() {
1099 resolve_layout_children_recursive(doc, child_id);
1100 doc.nodes[child_id].layout_parent.set(Some(node_id));
1101 }
1102
1103 *doc.nodes[node_id].layout_children.borrow_mut() = Some(layout_children);
1104 }
1105
1106 }
1109
1110 doc.nodes[node_id].set_damage(damage);
1111 }
1112 }
1113
1114 pub fn resolve_deferred_tasks(&mut self) {
1115 let mut deferred_construction_nodes = std::mem::take(&mut self.deferred_construction_nodes);
1116
1117 deferred_construction_nodes.sort_unstable_by_key(|task| task.node_id);
1119 deferred_construction_nodes.dedup_by_key(|task| task.node_id);
1120
1121 #[cfg(feature = "parallel-construct")]
1122 let iter = deferred_construction_nodes.into_par_iter();
1123 #[cfg(not(feature = "parallel-construct"))]
1124 let iter = deferred_construction_nodes.into_iter();
1125
1126 let results: Vec<ConstructionTaskResult> = iter
1127 .map(|task: ConstructionTask| match task.data {
1128 ConstructionTaskData::InlineLayout(mut layout) => {
1129 #[cfg(feature = "parallel-construct")]
1130 let mut layout_ctx = LAYOUT_CTX
1131 .take()
1132 .unwrap_or_else(|| Box::new(LayoutContext::new()));
1133 #[cfg(feature = "parallel-construct")]
1134 let layout_ctx_mut = &mut layout_ctx;
1135
1136 #[cfg(feature = "parallel-construct")]
1137 let mut font_ctx = FONT_CTX
1138 .take()
1139 .unwrap_or_else(|| Box::new(self.font_ctx.lock().unwrap().clone()));
1140 #[cfg(feature = "parallel-construct")]
1141 let font_ctx_mut = &mut font_ctx;
1142
1143 #[cfg(not(feature = "parallel-construct"))]
1144 let layout_ctx_mut = &mut self.layout_ctx;
1145 #[cfg(not(feature = "parallel-construct"))]
1146 let font_ctx_mut = &mut *self.font_ctx.lock().unwrap();
1147
1148 layout.content_widths = None;
1149 build_inline_layout_into(
1150 &self.nodes,
1151 layout_ctx_mut,
1152 font_ctx_mut,
1153 &mut layout,
1154 self.viewport.scale(),
1155 task.node_id,
1156 );
1157
1158 #[cfg(feature = "parallel-construct")]
1159 {
1160 LAYOUT_CTX.set(Some(layout_ctx));
1161 FONT_CTX.set(Some(font_ctx));
1162 }
1163
1164 ConstructionTaskResult {
1171 node_id: task.node_id,
1172 data: ConstructionTaskResultData::InlineLayout(layout),
1173 }
1174 }
1175 })
1176 .collect();
1177
1178 for result in results {
1179 match result.data {
1180 ConstructionTaskResultData::InlineLayout(layout) => {
1181 self.nodes[result.node_id].cache.clear();
1182 self.nodes[result.node_id]
1183 .element_data_mut()
1184 .unwrap()
1185 .inline_layout_data = Some(layout);
1186 }
1187 }
1188 }
1189
1190 self.deferred_construction_nodes.clear();
1191 }
1192
1193 pub fn resolve_layout(&mut self) {
1198 let size = self.stylist.device().au_viewport_size();
1199
1200 let available_space = taffy::Size {
1201 width: AvailableSpace::Definite(size.width.to_f32_px()),
1202 height: AvailableSpace::Definite(size.height.to_f32_px()),
1203 };
1204
1205 let root_element_id = taffy::NodeId::from(self.root_element().id);
1206
1207 taffy::compute_root_layout(self, root_element_id, available_space);
1210 taffy::round_layout(self, root_element_id);
1211
1212 }
1215
1216 pub fn get_cursor(&self) -> Option<CursorIcon> {
1217 let node = &self.nodes[self.get_hover_node_id()?];
1219
1220 let style = node.primary_styles()?;
1221 let keyword = stylo_to_cursor_icon(style.clone_cursor().keyword);
1222
1223 if keyword != CursorIcon::Default {
1225 return Some(keyword);
1226 }
1227
1228 if node.is_text_node()
1230 || node
1231 .element_data()
1232 .is_some_and(|e| e.text_input_data().is_some())
1233 {
1234 return Some(CursorIcon::Text);
1235 }
1236
1237 let mut maybe_node = Some(node);
1239 while let Some(node) = maybe_node {
1240 if node.is_link() {
1241 return Some(CursorIcon::Pointer);
1242 }
1243
1244 maybe_node = node.layout_parent.get().map(|node_id| node.with(node_id));
1245 }
1246
1247 Some(CursorIcon::Default)
1249 }
1250
1251 pub fn scroll_node_by(&mut self, node_id: usize, x: f64, y: f64) {
1252 self.scroll_node_by_has_changed(node_id, x, y);
1253 }
1254
1255 pub fn scroll_node_by_has_changed(&mut self, node_id: usize, x: f64, y: f64) -> bool {
1259 let Some(node) = self.nodes.get_mut(node_id) else {
1260 return false;
1261 };
1262
1263 let is_html_or_body = node.data.downcast_element().is_some_and(|e| {
1264 let tag = &e.name.local;
1265 tag == "html" || tag == "body"
1266 });
1267
1268 let (can_x_scroll, can_y_scroll) = node
1269 .primary_styles()
1270 .map(|styles| {
1271 (
1272 matches!(styles.clone_overflow_x(), Overflow::Scroll | Overflow::Auto),
1273 matches!(styles.clone_overflow_y(), Overflow::Scroll | Overflow::Auto)
1274 || (styles.clone_overflow_y() == Overflow::Visible && is_html_or_body),
1275 )
1276 })
1277 .unwrap_or((false, false));
1278
1279 let initial = node.scroll_offset;
1280 let new_x = node.scroll_offset.x - x;
1281 let new_y = node.scroll_offset.y - y;
1282
1283 let mut bubble_x = 0.0;
1284 let mut bubble_y = 0.0;
1285
1286 let scroll_width = node.final_layout.scroll_width() as f64;
1287 let scroll_height = node.final_layout.scroll_height() as f64;
1288
1289 if !can_x_scroll {
1291 bubble_x = x
1292 } else if new_x < 0.0 {
1293 bubble_x = -new_x;
1294 node.scroll_offset.x = 0.0;
1295 } else if new_x > scroll_width {
1296 bubble_x = scroll_width - new_x;
1297 node.scroll_offset.x = scroll_width;
1298 } else {
1299 node.scroll_offset.x = new_x;
1300 }
1301
1302 if !can_y_scroll {
1303 bubble_y = y
1304 } else if new_y < 0.0 {
1305 bubble_y = -new_y;
1306 node.scroll_offset.y = 0.0;
1307 } else if new_y > scroll_height {
1308 bubble_y = scroll_height - new_y;
1309 node.scroll_offset.y = scroll_height;
1310 } else {
1311 node.scroll_offset.y = new_y;
1312 }
1313
1314 let has_changed = node.scroll_offset != initial;
1315
1316 if bubble_x != 0.0 || bubble_y != 0.0 {
1317 if let Some(parent) = node.parent {
1318 return self.scroll_node_by_has_changed(parent, bubble_x, bubble_y) | has_changed;
1319 } else {
1320 return self.scroll_viewport_by_has_changed(bubble_x, bubble_y) | has_changed;
1321 }
1322 }
1323
1324 has_changed
1325 }
1326
1327 pub fn scroll_viewport_by(&mut self, x: f64, y: f64) {
1328 self.scroll_viewport_by_has_changed(x, y);
1329 }
1330
1331 pub fn scroll_viewport_by_has_changed(&mut self, x: f64, y: f64) -> bool {
1333 let content_size = self.root_element().final_layout.size;
1334 let new_scroll = (self.viewport_scroll.x - x, self.viewport_scroll.y - y);
1335 let window_width = self.viewport.window_size.0 as f64 / self.viewport.scale() as f64;
1336 let window_height = self.viewport.window_size.1 as f64 / self.viewport.scale() as f64;
1337
1338 let initial = self.viewport_scroll;
1339 self.viewport_scroll.x = f64::max(
1340 0.0,
1341 f64::min(new_scroll.0, content_size.width as f64 - window_width),
1342 );
1343 self.viewport_scroll.y = f64::max(
1344 0.0,
1345 f64::min(new_scroll.1, content_size.height as f64 - window_height),
1346 );
1347
1348 self.viewport_scroll != initial
1349 }
1350
1351 pub fn viewport_scroll(&self) -> crate::Point<f64> {
1352 self.viewport_scroll
1353 }
1354
1355 pub fn set_viewport_scroll(&mut self, scroll: crate::Point<f64>) {
1356 self.viewport_scroll = scroll;
1357 }
1358
1359 pub fn find_title_node(&self) -> Option<&Node> {
1360 TreeTraverser::new(self)
1361 .find(|node_id| {
1362 self.nodes[*node_id]
1363 .data
1364 .is_element_with_tag_name(&local_name!("title"))
1365 })
1366 .map(|node_id| &self.nodes[node_id])
1367 }
1368
1369 pub(crate) fn compute_has_canvas(&self) -> bool {
1370 TreeTraverser::new(self).any(|node_id| {
1371 let node = &self.nodes[node_id];
1372 let Some(element) = node.element_data() else {
1373 return false;
1374 };
1375 if element.name.local == local_name!("canvas") && element.has_attr(local_name!("src")) {
1376 return true;
1377 }
1378
1379 false
1380 })
1381 }
1382}
1383
1384impl AsRef<BaseDocument> for BaseDocument {
1385 fn as_ref(&self) -> &BaseDocument {
1386 self
1387 }
1388}
1389
1390impl AsMut<BaseDocument> for BaseDocument {
1391 fn as_mut(&mut self) -> &mut BaseDocument {
1392 self
1393 }
1394}