1use std::rc::Rc;
7
8use app_units::Au;
9use compositing_traits::display_list::ScrollTree;
10use euclid::default::{Point2D, Rect};
11use euclid::{SideOffsets2D, Size2D};
12use itertools::Itertools;
13use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
14use layout_api::{
15 BoxAreaType, LayoutElementType, LayoutNodeType, OffsetParentResponse, ScrollParentResponse,
16};
17use script::layout_dom::{ServoLayoutNode, ServoThreadSafeLayoutNode};
18use servo_arc::Arc as ServoArc;
19use servo_geometry::{FastLayoutTransform, au_rect_to_f32_rect, f32_rect_to_au_rect};
20use servo_url::ServoUrl;
21use style::computed_values::display::T as Display;
22use style::computed_values::position::T as Position;
23use style::computed_values::visibility::T as Visibility;
24use style::computed_values::white_space_collapse::T as WhiteSpaceCollapseValue;
25use style::context::{QuirksMode, SharedStyleContext, StyleContext, ThreadLocalStyleContext};
26use style::dom::{NodeInfo, OpaqueNode, TElement, TNode};
27use style::properties::style_structs::Font;
28use style::properties::{
29 ComputedValues, Importance, LonghandId, PropertyDeclarationBlock, PropertyDeclarationId,
30 PropertyId, ShorthandId, SourcePropertyDeclaration, parse_one_declaration_into,
31};
32use style::selector_parser::PseudoElement;
33use style::shared_lock::SharedRwLock;
34use style::stylesheets::{CssRuleType, Origin, UrlExtraData};
35use style::stylist::RuleInclusion;
36use style::traversal::resolve_style;
37use style::values::computed::{Float, Size};
38use style::values::generics::font::LineHeight;
39use style::values::generics::position::AspectRatio;
40use style::values::specified::GenericGridTemplateComponent;
41use style::values::specified::box_::DisplayInside;
42use style::values::specified::text::TextTransformCase;
43use style_traits::{ParsingMode, ToCss};
44
45use crate::ArcRefCell;
46use crate::display_list::StackingContextTree;
47use crate::dom::NodeExt;
48use crate::flow::inline::construct::{TextTransformation, WhitespaceCollapse, capitalize_string};
49use crate::fragment_tree::{
50 BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo,
51};
52use crate::style_ext::ComputedValuesExt;
53use crate::taffy::SpecificTaffyGridInfo;
54
55fn root_transform_for_layout_node(
58 scroll_tree: &ScrollTree,
59 node: ServoThreadSafeLayoutNode<'_>,
60) -> Option<FastLayoutTransform> {
61 let fragments = node.fragments_for_pseudo(None);
62 let box_fragment = fragments
63 .first()
64 .and_then(Fragment::retrieve_box_fragment)?
65 .borrow();
66 let scroll_tree_node_id = box_fragment.spatial_tree_node.borrow();
67 let scroll_tree_node_id = (*scroll_tree_node_id)?;
68 Some(scroll_tree.cumulative_node_to_root_transform(&scroll_tree_node_id))
69}
70
71pub(crate) fn process_box_area_request(
72 stacking_context_tree: &StackingContextTree,
73 node: ServoThreadSafeLayoutNode<'_>,
74 area: BoxAreaType,
75) -> Option<Rect<Au>> {
76 let rects: Vec<_> = node
77 .fragments_for_pseudo(None)
78 .iter()
79 .filter_map(|node| node.cumulative_box_area_rect(area))
80 .collect();
81 if rects.is_empty() {
82 return None;
83 }
84 let rect_union = rects.iter().fold(Rect::zero(), |unioned_rect, rect| {
85 rect.to_untyped().union(&unioned_rect)
86 });
87
88 let Some(transform) =
89 root_transform_for_layout_node(&stacking_context_tree.compositor_info.scroll_tree, node)
90 else {
91 return Some(Rect::new(rect_union.origin, Size2D::zero()));
92 };
93
94 transform_au_rectangle(rect_union, transform)
95}
96
97pub(crate) fn process_box_areas_request(
98 stacking_context_tree: &StackingContextTree,
99 node: ServoThreadSafeLayoutNode<'_>,
100 area: BoxAreaType,
101) -> Vec<Rect<Au>> {
102 let fragments = node.fragments_for_pseudo(None);
103 let box_areas = fragments
104 .iter()
105 .filter_map(|node| node.cumulative_box_area_rect(area))
106 .map(|rect| rect.to_untyped());
107
108 let Some(transform) =
109 root_transform_for_layout_node(&stacking_context_tree.compositor_info.scroll_tree, node)
110 else {
111 return box_areas
112 .map(|rect| Rect::new(rect.origin, Size2D::zero()))
113 .collect();
114 };
115
116 box_areas
117 .filter_map(|rect| transform_au_rectangle(rect, transform))
118 .collect()
119}
120
121pub fn process_client_rect_request(node: ServoThreadSafeLayoutNode<'_>) -> Rect<i32> {
122 node.fragments_for_pseudo(None)
123 .first()
124 .map(Fragment::client_rect)
125 .unwrap_or_default()
126}
127
128pub fn process_node_scroll_area_request(
130 requested_node: Option<ServoThreadSafeLayoutNode<'_>>,
131 fragment_tree: Option<Rc<FragmentTree>>,
132) -> Rect<i32> {
133 let Some(tree) = fragment_tree else {
134 return Rect::zero();
135 };
136
137 let rect = match requested_node {
138 Some(node) => node
139 .fragments_for_pseudo(None)
140 .first()
141 .map(Fragment::scrolling_area)
142 .unwrap_or_default(),
143 None => tree.scrollable_overflow(),
144 };
145
146 Rect::new(
147 Point2D::new(rect.origin.x.to_f32_px(), rect.origin.y.to_f32_px()),
148 Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px()),
149 )
150 .round()
151 .to_i32()
152 .to_untyped()
153}
154
155pub fn process_resolved_style_request(
158 context: &SharedStyleContext,
159 node: ServoLayoutNode<'_>,
160 pseudo: &Option<PseudoElement>,
161 property: &PropertyId,
162) -> String {
163 if !node.as_element().unwrap().has_data() {
164 return process_resolved_style_request_for_unstyled_node(context, node, pseudo, property);
165 }
166
167 let layout_element = node.to_threadsafe().as_element().unwrap();
170 let layout_element = match pseudo {
171 Some(pseudo_element_type) => {
172 match layout_element.with_pseudo(*pseudo_element_type) {
173 Some(layout_element) => layout_element,
174 None => {
175 return String::new();
179 },
180 }
181 },
182 None => layout_element,
183 };
184
185 let style = &*layout_element.style(context);
186 let longhand_id = match *property {
187 PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
188 Ok(longhand_id) => longhand_id,
189 Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
190 },
191 PropertyId::Custom(ref name) => {
192 return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
193 },
194 }
195 .to_physical(style.writing_mode);
196
197 let computed_style = |fragment: Option<&Fragment>| match longhand_id {
198 LonghandId::MinWidth
199 if style.clone_min_width() == Size::Auto &&
200 !should_honor_min_size_auto(fragment, style) =>
201 {
202 String::from("0px")
203 },
204 LonghandId::MinHeight
205 if style.clone_min_height() == Size::Auto &&
206 !should_honor_min_size_auto(fragment, style) =>
207 {
208 String::from("0px")
209 },
210 _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
211 };
212
213 if longhand_id == LonghandId::LineHeight {
222 let font = style.get_font();
223 let font_size = font.font_size.computed_size();
224 return match font.line_height {
225 LineHeight::Normal => computed_style(None),
228 LineHeight::Number(value) => (font_size * value.0).to_css_string(),
229 LineHeight::Length(value) => value.0.to_css_string(),
230 };
231 }
232
233 let display = style.get_box().display;
237 if display.is_none() || display.is_contents() {
238 return computed_style(None);
239 }
240
241 let resolve_for_fragment = |fragment: &Fragment| {
242 let (content_rect, margins, padding, specific_layout_info) = match fragment {
243 Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
244 let box_fragment = box_fragment.borrow();
245 if style.get_box().position != Position::Static {
246 let resolved_insets = || box_fragment.calculate_resolved_insets_if_positioned();
247 match longhand_id {
248 LonghandId::Top => return resolved_insets().top.to_css_string(),
249 LonghandId::Right => {
250 return resolved_insets().right.to_css_string();
251 },
252 LonghandId::Bottom => {
253 return resolved_insets().bottom.to_css_string();
254 },
255 LonghandId::Left => {
256 return resolved_insets().left.to_css_string();
257 },
258 _ => {},
259 }
260 }
261 let content_rect = box_fragment.content_rect;
262 let margins = box_fragment.margin;
263 let padding = box_fragment.padding;
264 let specific_layout_info = box_fragment.specific_layout_info().cloned();
265 (content_rect, margins, padding, specific_layout_info)
266 },
267 Fragment::Positioning(positioning_fragment) => {
268 let content_rect = positioning_fragment.borrow().rect;
269 (
270 content_rect,
271 SideOffsets2D::zero(),
272 SideOffsets2D::zero(),
273 None,
274 )
275 },
276 _ => return computed_style(Some(fragment)),
277 };
278
279 if display.inside() == DisplayInside::Grid {
285 if let Some(SpecificLayoutInfo::Grid(info)) = specific_layout_info {
286 if let Some(value) = resolve_grid_template(&info, style, longhand_id) {
287 return value;
288 }
289 }
290 }
291
292 match longhand_id {
300 LonghandId::Width if resolved_size_should_be_used_value(fragment) => {
301 content_rect.size.width
302 },
303 LonghandId::Height if resolved_size_should_be_used_value(fragment) => {
304 content_rect.size.height
305 },
306 LonghandId::MarginBottom => margins.bottom,
307 LonghandId::MarginTop => margins.top,
308 LonghandId::MarginLeft => margins.left,
309 LonghandId::MarginRight => margins.right,
310 LonghandId::PaddingBottom => padding.bottom,
311 LonghandId::PaddingTop => padding.top,
312 LonghandId::PaddingLeft => padding.left,
313 LonghandId::PaddingRight => padding.right,
314 _ => return computed_style(Some(fragment)),
315 }
316 .to_css_string()
317 };
318
319 node.to_threadsafe()
320 .fragments_for_pseudo(*pseudo)
321 .first()
322 .map(resolve_for_fragment)
323 .unwrap_or_else(|| computed_style(None))
324}
325
326fn resolved_size_should_be_used_value(fragment: &Fragment) -> bool {
327 match fragment {
330 Fragment::Box(box_fragment) => !box_fragment.borrow().is_inline_box(),
331 Fragment::Float(_) |
332 Fragment::Positioning(_) |
333 Fragment::AbsoluteOrFixedPositioned(_) |
334 Fragment::Image(_) |
335 Fragment::IFrame(_) => true,
336 Fragment::Text(_) => false,
337 }
338}
339
340fn should_honor_min_size_auto(fragment: Option<&Fragment>, style: &ComputedValues) -> bool {
341 let Some(Fragment::Box(box_fragment)) = fragment else {
350 return false;
351 };
352 let flags = box_fragment.borrow().base.flags;
353 flags.contains(FragmentFlags::IS_FLEX_OR_GRID_ITEM) ||
354 style.clone_aspect_ratio() != AspectRatio::auto()
355}
356
357fn resolve_grid_template(
358 grid_info: &SpecificTaffyGridInfo,
359 style: &ComputedValues,
360 longhand_id: LonghandId,
361) -> Option<String> {
362 fn serialize_standalone_non_subgrid_track_list(track_sizes: &[Au]) -> Option<String> {
364 match track_sizes.is_empty() {
365 true => None,
369 false => Some(
376 track_sizes
377 .iter()
378 .map(|size| size.to_css_string())
379 .join(" "),
380 ),
381 }
382 }
383
384 let (track_info, computed_value) = match longhand_id {
385 LonghandId::GridTemplateRows => (&grid_info.rows, &style.get_position().grid_template_rows),
386 LonghandId::GridTemplateColumns => (
387 &grid_info.columns,
388 &style.get_position().grid_template_columns,
389 ),
390 _ => return None,
391 };
392
393 match computed_value {
394 GenericGridTemplateComponent::None |
398 GenericGridTemplateComponent::TrackList(_) |
399 GenericGridTemplateComponent::Masonry => {
400 serialize_standalone_non_subgrid_track_list(&track_info.sizes)
401 },
402
403 GenericGridTemplateComponent::Subgrid(_) => None,
411 }
412}
413
414pub fn process_resolved_style_request_for_unstyled_node(
415 context: &SharedStyleContext,
416 node: ServoLayoutNode<'_>,
417 pseudo: &Option<PseudoElement>,
418 property: &PropertyId,
419) -> String {
420 if pseudo.is_some() {
422 return String::new();
423 }
424
425 let mut tlc = ThreadLocalStyleContext::new();
426 let mut context = StyleContext {
427 shared: context,
428 thread_local: &mut tlc,
429 };
430
431 let element = node.as_element().unwrap();
432 let styles = resolve_style(
433 &mut context,
434 element,
435 RuleInclusion::All,
436 pseudo.as_ref(),
437 None,
438 );
439 let style = styles.primary();
440 let longhand_id = match *property {
441 PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
442 Ok(longhand_id) => longhand_id,
443 Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
444 },
445 PropertyId::Custom(ref name) => {
446 return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
447 },
448 };
449
450 match longhand_id {
451 LonghandId::MinWidth if style.clone_min_width() == Size::Auto => String::from("0px"),
454 LonghandId::MinHeight if style.clone_min_height() == Size::Auto => String::from("0px"),
455
456 _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
459 }
460}
461
462fn shorthand_to_css_string(
463 id: style::properties::ShorthandId,
464 style: &style::properties::ComputedValues,
465) -> String {
466 use style::values::resolved::Context;
467 let mut block = PropertyDeclarationBlock::new();
468 let mut dest = String::new();
469 for longhand in id.longhands() {
470 block.push(
471 style.computed_or_resolved_declaration(
472 longhand,
473 Some(&Context {
474 style,
475 for_property: longhand.into(),
476 }),
477 ),
478 Importance::Normal,
479 );
480 }
481 match block.shorthand_to_css(id, &mut dest) {
482 Ok(_) => dest.to_owned(),
483 Err(_) => String::new(),
484 }
485}
486
487struct OffsetParentFragments {
488 parent: ArcRefCell<BoxFragment>,
489 grandparent: Option<Fragment>,
490}
491
492fn offset_parent_fragments(node: ServoLayoutNode<'_>) -> Option<OffsetParentFragments> {
494 let fragment = node
500 .to_threadsafe()
501 .fragments_for_pseudo(None)
502 .first()
503 .cloned()?;
504 let flags = fragment.base()?.flags;
505 if flags.intersects(
506 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
507 ) {
508 return None;
509 }
510 if matches!(
511 fragment, Fragment::Box(fragment) if fragment.borrow().style.get_box().position == Position::Fixed
512 ) {
513 return None;
514 }
515
516 let mut maybe_parent_node = node.parent_node();
523 while let Some(parent_node) = maybe_parent_node {
524 maybe_parent_node = parent_node.parent_node();
525
526 if let Some(parent_fragment) = parent_node
527 .to_threadsafe()
528 .fragments_for_pseudo(None)
529 .first()
530 {
531 let parent_fragment = match parent_fragment {
532 Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment,
533 _ => continue,
534 };
535
536 let grandparent_fragment = maybe_parent_node.and_then(|node| {
537 node.to_threadsafe()
538 .fragments_for_pseudo(None)
539 .first()
540 .cloned()
541 });
542
543 if parent_fragment.borrow().style.get_box().position != Position::Static {
544 return Some(OffsetParentFragments {
545 parent: parent_fragment.clone(),
546 grandparent: grandparent_fragment,
547 });
548 }
549
550 let flags = parent_fragment.borrow().base.flags;
551 if flags.intersects(
552 FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT |
553 FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT,
554 ) {
555 return Some(OffsetParentFragments {
556 parent: parent_fragment.clone(),
557 grandparent: grandparent_fragment,
558 });
559 }
560 }
561 }
562
563 None
564}
565
566#[inline]
567pub fn process_offset_parent_query(node: ServoLayoutNode<'_>) -> Option<OffsetParentResponse> {
568 let fragment = node
585 .to_threadsafe()
586 .fragments_for_pseudo(None)
587 .first()
588 .cloned()?;
589 let mut border_box = fragment.cumulative_box_area_rect(BoxAreaType::Border)?;
590
591 let Some(offset_parent_fragment) = offset_parent_fragments(node) else {
596 return Some(OffsetParentResponse {
597 node_address: None,
598 rect: border_box.to_untyped(),
599 });
600 };
601
602 let parent_fragment = offset_parent_fragment.parent.borrow();
603 let parent_is_static_body_element = parent_fragment
604 .base
605 .flags
606 .contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) &&
607 parent_fragment.style.get_box().position == Position::Static;
608
609 let grandparent_box_fragment = || match offset_parent_fragment.grandparent {
618 Some(Fragment::Box(box_fragment)) | Some(Fragment::Float(box_fragment)) => {
619 Some(box_fragment)
620 },
621 _ => None,
622 };
623
624 let parent_offset_rect = if parent_is_static_body_element {
632 if let Some(grandparent_fragment) = grandparent_box_fragment() {
633 let grandparent_fragment = grandparent_fragment.borrow();
634 grandparent_fragment.offset_by_containing_block(&grandparent_fragment.border_rect())
635 } else {
636 parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect())
637 }
638 } else {
639 parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect())
640 };
641
642 border_box = border_box.translate(-parent_offset_rect.origin.to_vector());
643
644 Some(OffsetParentResponse {
645 node_address: parent_fragment.base.tag.map(|tag| tag.node.into()),
646 rect: border_box.to_untyped(),
647 })
648}
649
650#[inline]
653pub(crate) fn process_scroll_parent_query(
654 node: ServoLayoutNode<'_>,
655) -> Option<ScrollParentResponse> {
656 let layout_data = node.to_threadsafe().inner_layout_data()?;
657
658 let layout_box = layout_data.self_box.borrow();
661 let layout_box = layout_box.as_ref()?;
662
663 let (mut current_position_value, flags) = layout_box
664 .with_first_base(|base| (base.style.clone_position(), base.base_fragment_info.flags))?;
665
666 if flags.intersects(
671 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
672 ) {
673 return None;
674 }
675
676 let mut current_ancestor = node.as_element()?;
691 while let Some(ancestor) = current_ancestor.traversal_parent() {
692 current_ancestor = ancestor;
693
694 let Some(layout_data) = ancestor.as_node().to_threadsafe().inner_layout_data() else {
695 continue;
696 };
697 let ancestor_layout_box = layout_data.self_box.borrow();
698 let Some(ancestor_layout_box) = ancestor_layout_box.as_ref() else {
699 continue;
700 };
701
702 let Some((ancestor_style, ancestor_flags)) = ancestor_layout_box
703 .with_first_base(|base| (base.style.clone(), base.base_fragment_info.flags))
704 else {
705 continue;
706 };
707
708 let is_containing_block = match current_position_value {
709 Position::Static | Position::Relative | Position::Sticky => {
710 !ancestor_style.is_inline_box(ancestor_flags)
711 },
712 Position::Absolute => {
713 ancestor_style.establishes_containing_block_for_absolute_descendants(ancestor_flags)
714 },
715 Position::Fixed => {
716 ancestor_style.establishes_containing_block_for_all_descendants(ancestor_flags)
717 },
718 };
719 if !is_containing_block {
720 continue;
721 }
722
723 if ancestor_style.establishes_scroll_container(ancestor_flags) {
724 return Some(ScrollParentResponse::Element(
725 ancestor.as_node().opaque().into(),
726 ));
727 }
728
729 current_position_value = ancestor_style.clone_position();
730 }
731
732 match current_position_value {
733 Position::Fixed => None,
734 _ => Some(ScrollParentResponse::DocumentScrollingElement),
735 }
736}
737
738pub fn get_the_text_steps(node: ServoLayoutNode<'_>) -> String {
740 let mut results = Vec::new();
746 let mut max_req_line_break_count = 0;
747
748 let mut state = Default::default();
750 for child in node.dom_children() {
751 let mut current = rendered_text_collection_steps(child, &mut state);
753 results.append(&mut current);
755 }
756
757 let mut output = Vec::new();
758 for item in results {
759 match item {
760 InnerOrOuterTextItem::Text(s) => {
761 if !s.is_empty() {
763 if max_req_line_break_count > 0 {
764 output.push("\u{000A}".repeat(max_req_line_break_count));
766 max_req_line_break_count = 0;
767 }
768 output.push(s);
769 }
770 },
771 InnerOrOuterTextItem::RequiredLineBreakCount(count) => {
772 if output.is_empty() {
774 continue;
776 }
777 if count > max_req_line_break_count {
781 max_req_line_break_count = count;
782 }
783 },
784 }
785 }
786 output.into_iter().collect()
787}
788
789enum InnerOrOuterTextItem {
790 Text(String),
791 RequiredLineBreakCount(usize),
792}
793
794#[derive(Clone)]
795struct RenderedTextCollectionState {
796 first_table_row: bool,
798 first_table_cell: bool,
800 within_table: bool,
803 may_start_with_whitespace: bool,
805 did_truncate_trailing_white_space: bool,
808 within_table_content: bool,
811}
812
813impl Default for RenderedTextCollectionState {
814 fn default() -> Self {
815 RenderedTextCollectionState {
816 first_table_row: true,
817 first_table_cell: true,
818 may_start_with_whitespace: true,
819 did_truncate_trailing_white_space: false,
820 within_table: false,
821 within_table_content: false,
822 }
823 }
824}
825
826fn rendered_text_collection_steps(
828 node: ServoLayoutNode<'_>,
829 state: &mut RenderedTextCollectionState,
830) -> Vec<InnerOrOuterTextItem> {
831 let mut items = vec![];
835 if !node.is_connected() || !(node.is_element() || node.is_text_node()) {
836 return items;
837 }
838
839 match node.type_id() {
840 LayoutNodeType::Text => {
841 if let Some(element) = node.parent_node() {
842 match element.type_id() {
843 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
845 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
846 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
847 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
848 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
849 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
850 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement) => {
851 return items;
852 },
853 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) => {
857 if let Some(element) = element.parent_node() {
858 if !matches!(
859 element.type_id(),
860 LayoutNodeType::Element(LayoutElementType::HTMLSelectElement)
861 ) {
862 return items;
863 }
864 } else {
865 return items;
866 }
867 },
868 LayoutNodeType::Element(LayoutElementType::HTMLSelectElement) => return items,
869 _ => {},
870 }
871
872 if state.within_table && !state.within_table_content {
876 return items;
877 }
878
879 let Some(style_data) = element.style_data() else {
880 return items;
881 };
882
883 let element_data = style_data.element_data.borrow();
884 let Some(style) = element_data.styles.get_primary() else {
885 return items;
886 };
887
888 if style.get_inherited_box().visibility != Visibility::Visible {
894 return items;
895 }
896
897 let display = style.get_box().display;
901 if display == Display::None {
902 match element.type_id() {
903 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) |
906 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement) => {},
907 _ => {
908 return items;
909 },
910 }
911 }
912
913 let text_content = node.to_threadsafe().node_text_content();
914
915 let white_space_collapse = style.clone_white_space_collapse();
916 let preserve_whitespace = white_space_collapse == WhiteSpaceCollapseValue::Preserve;
917 let is_inline = matches!(
918 display,
919 Display::InlineBlock | Display::InlineFlex | Display::InlineGrid
920 );
921 let trim_beginning_white_space =
925 !preserve_whitespace && (state.may_start_with_whitespace || is_inline);
926 let with_white_space_rules_applied = WhitespaceCollapse::new(
927 text_content.chars(),
928 white_space_collapse,
929 trim_beginning_white_space,
930 );
931
932 let text_transform = style.clone_text_transform().case();
940 let mut transformed_text: String =
941 TextTransformation::new(with_white_space_rules_applied, text_transform)
942 .collect();
943
944 if TextTransformCase::Capitalize == text_transform {
948 transformed_text = capitalize_string(&transformed_text, true);
949 }
950
951 let is_preformatted_element =
952 white_space_collapse == WhiteSpaceCollapseValue::Preserve;
953
954 let is_final_character_whitespace = transformed_text
955 .chars()
956 .next_back()
957 .filter(char::is_ascii_whitespace)
958 .is_some();
959
960 let is_first_character_whitespace = transformed_text
961 .chars()
962 .next()
963 .filter(char::is_ascii_whitespace)
964 .is_some();
965
966 if state.did_truncate_trailing_white_space && !is_first_character_whitespace {
970 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
971 };
972
973 if !transformed_text.is_empty() {
974 if is_final_character_whitespace && !is_preformatted_element {
977 state.may_start_with_whitespace = false;
978 state.did_truncate_trailing_white_space = true;
979 transformed_text.pop();
980 } else {
981 state.may_start_with_whitespace = is_final_character_whitespace;
982 state.did_truncate_trailing_white_space = false;
983 }
984 items.push(InnerOrOuterTextItem::Text(transformed_text));
985 }
986 } else {
987 items.push(InnerOrOuterTextItem::Text(
990 node.to_threadsafe().node_text_content().into(),
991 ));
992 }
993 },
994 LayoutNodeType::Element(LayoutElementType::HTMLBRElement) => {
995 state.did_truncate_trailing_white_space = false;
998 state.may_start_with_whitespace = true;
999 items.push(InnerOrOuterTextItem::Text(String::from("\u{000A}")));
1000 },
1001 _ => {
1002 let Some(style_data) = node.style_data() else {
1005 return items;
1006 };
1007
1008 let element_data = style_data.element_data.borrow();
1009 let Some(style) = element_data.styles.get_primary() else {
1010 return items;
1011 };
1012 let inherited_box = style.get_inherited_box();
1013
1014 if inherited_box.visibility != Visibility::Visible {
1015 for child in node.dom_children() {
1019 items.append(&mut rendered_text_collection_steps(child, state));
1020 }
1021 return items;
1022 }
1023
1024 let style_box = style.get_box();
1025 let display = style_box.display;
1026 let mut surrounding_line_breaks = 0;
1027
1028 if style_box.position == Position::Absolute || style_box.float != Float::None {
1030 surrounding_line_breaks = 1;
1031 }
1032
1033 match display {
1036 Display::Table => {
1037 surrounding_line_breaks = 1;
1038 state.within_table = true;
1039 },
1040 Display::TableCell => {
1045 if !state.first_table_cell {
1046 items.push(InnerOrOuterTextItem::Text(String::from(
1047 "\u{0009}", )));
1049 state.did_truncate_trailing_white_space = false;
1051 }
1052 state.first_table_cell = false;
1053 state.within_table_content = true;
1054 },
1055 Display::TableRow => {
1060 if !state.first_table_row {
1061 items.push(InnerOrOuterTextItem::Text(String::from(
1062 "\u{000A}", )));
1064 state.did_truncate_trailing_white_space = false;
1066 }
1067 state.first_table_row = false;
1068 state.first_table_cell = true;
1069 },
1070 Display::Block => {
1073 surrounding_line_breaks = 1;
1074 },
1075 Display::TableCaption => {
1076 surrounding_line_breaks = 1;
1077 state.within_table_content = true;
1078 },
1079 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock => {
1080 if state.did_truncate_trailing_white_space {
1084 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1085 state.did_truncate_trailing_white_space = false;
1086 state.may_start_with_whitespace = true;
1087 }
1088 },
1089 _ => {},
1090 }
1091
1092 match node.type_id() {
1093 LayoutNodeType::Element(LayoutElementType::HTMLParagraphElement) => {
1096 surrounding_line_breaks = 2;
1097 },
1098 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement) |
1101 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) => {
1102 surrounding_line_breaks = 1;
1103 },
1104 _ => {},
1105 }
1106
1107 if surrounding_line_breaks > 0 {
1108 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1109 surrounding_line_breaks,
1110 ));
1111 state.did_truncate_trailing_white_space = false;
1112 state.may_start_with_whitespace = true;
1113 }
1114
1115 match node.type_id() {
1116 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
1121 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
1122 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
1123 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
1124 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
1125 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
1126 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement) => {
1127 if display != Display::Block && state.did_truncate_trailing_white_space {
1128 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1129 state.did_truncate_trailing_white_space = false;
1130 };
1131 state.may_start_with_whitespace = false;
1132 },
1133 _ => {
1134 for child in node.dom_children() {
1137 items.append(&mut rendered_text_collection_steps(child, state));
1138 }
1139 },
1140 }
1141
1142 match display {
1145 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock => {
1146 state.did_truncate_trailing_white_space = false;
1147 state.may_start_with_whitespace = false;
1148 },
1149 Display::Table => {
1150 state.within_table = false;
1151 },
1152 Display::TableCell | Display::TableCaption => {
1153 state.within_table_content = false;
1154 },
1155 _ => {},
1156 }
1157
1158 if surrounding_line_breaks > 0 {
1159 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1160 surrounding_line_breaks,
1161 ));
1162 state.did_truncate_trailing_white_space = false;
1163 state.may_start_with_whitespace = true;
1164 }
1165 },
1166 };
1167 items
1168}
1169
1170pub fn process_text_index_request(_node: OpaqueNode, _point: Point2D<Au>) -> Option<usize> {
1171 None
1172}
1173
1174pub fn process_resolved_font_style_query<'dom, E>(
1175 context: &SharedStyleContext,
1176 node: E,
1177 value: &str,
1178 url_data: ServoUrl,
1179 shared_lock: &SharedRwLock,
1180) -> Option<ServoArc<Font>>
1181where
1182 E: LayoutNode<'dom>,
1183{
1184 fn create_font_declaration(
1185 value: &str,
1186 url_data: &ServoUrl,
1187 quirks_mode: QuirksMode,
1188 ) -> Option<PropertyDeclarationBlock> {
1189 let mut declarations = SourcePropertyDeclaration::default();
1190 let result = parse_one_declaration_into(
1191 &mut declarations,
1192 PropertyId::NonCustom(ShorthandId::Font.into()),
1193 value,
1194 Origin::Author,
1195 &UrlExtraData(url_data.get_arc()),
1196 None,
1197 ParsingMode::DEFAULT,
1198 quirks_mode,
1199 CssRuleType::Style,
1200 );
1201 let declarations = match result {
1202 Ok(()) => {
1203 let mut block = PropertyDeclarationBlock::new();
1204 block.extend(declarations.drain(), Importance::Normal);
1205 block
1206 },
1207 Err(_) => return None,
1208 };
1209 Some(declarations)
1211 }
1212 fn resolve_for_declarations<'dom, E>(
1213 context: &SharedStyleContext,
1214 parent_style: Option<&ComputedValues>,
1215 declarations: PropertyDeclarationBlock,
1216 shared_lock: &SharedRwLock,
1217 ) -> ServoArc<ComputedValues>
1218 where
1219 E: LayoutNode<'dom>,
1220 {
1221 let parent_style = match parent_style {
1222 Some(parent) => parent,
1223 None => context.stylist.device().default_computed_values(),
1224 };
1225 context
1226 .stylist
1227 .compute_for_declarations::<E::ConcreteElement>(
1228 &context.guards,
1229 parent_style,
1230 ServoArc::new(shared_lock.wrap(declarations)),
1231 )
1232 }
1233
1234 let quirks_mode = context.quirks_mode();
1237 let declarations = create_font_declaration(value, &url_data, quirks_mode)?;
1238
1239 let element = node.as_element().unwrap();
1243 let parent_style = if node.is_connected() {
1244 if element.has_data() {
1245 node.to_threadsafe().as_element().unwrap().style(context)
1246 } else {
1247 let mut tlc = ThreadLocalStyleContext::new();
1248 let mut context = StyleContext {
1249 shared: context,
1250 thread_local: &mut tlc,
1251 };
1252 let styles = resolve_style(&mut context, element, RuleInclusion::All, None, None);
1253 styles.primary().clone()
1254 }
1255 } else {
1256 let default_declarations =
1257 create_font_declaration("10px sans-serif", &url_data, quirks_mode).unwrap();
1258 resolve_for_declarations::<E>(context, None, default_declarations, shared_lock)
1259 };
1260
1261 let computed_values =
1263 resolve_for_declarations::<E>(context, Some(&*parent_style), declarations, shared_lock);
1264
1265 Some(computed_values.clone_font())
1266}
1267
1268fn transform_au_rectangle(
1269 rect_to_transform: Rect<Au>,
1270 transform: FastLayoutTransform,
1271) -> Option<Rect<Au>> {
1272 let rect_to_transform = &au_rect_to_f32_rect(rect_to_transform).cast_unit();
1273 let outer_transformed_rect = match transform {
1274 FastLayoutTransform::Offset(offset) => Some(rect_to_transform.translate(offset)),
1275 FastLayoutTransform::Transform { transform, .. } => {
1276 transform.outer_transformed_rect(rect_to_transform)
1277 },
1278 };
1279 outer_transformed_rect
1280 .map(|transformed_rect| f32_rect_to_au_rect(transformed_rect.to_untyped()))
1281}