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 AxesOverflow, BoxAreaType, LayoutElementType, LayoutNodeType, OffsetParentResponse,
16 ScrollContainerQueryFlags, ScrollContainerResponse,
17};
18use script::layout_dom::{ServoLayoutNode, ServoThreadSafeLayoutNode};
19use servo_arc::Arc as ServoArc;
20use servo_geometry::{FastLayoutTransform, au_rect_to_f32_rect, f32_rect_to_au_rect};
21use servo_url::ServoUrl;
22use style::computed_values::display::T as Display;
23use style::computed_values::position::T as Position;
24use style::computed_values::visibility::T as Visibility;
25use style::computed_values::white_space_collapse::T as WhiteSpaceCollapseValue;
26use style::context::{QuirksMode, SharedStyleContext, StyleContext, ThreadLocalStyleContext};
27use style::dom::{NodeInfo, OpaqueNode, TElement, TNode};
28use style::properties::style_structs::Font;
29use style::properties::{
30 ComputedValues, Importance, LonghandId, PropertyDeclarationBlock, PropertyDeclarationId,
31 PropertyId, ShorthandId, SourcePropertyDeclaration, parse_one_declaration_into,
32};
33use style::selector_parser::PseudoElement;
34use style::shared_lock::SharedRwLock;
35use style::stylesheets::{CssRuleType, Origin, UrlExtraData};
36use style::stylist::RuleInclusion;
37use style::traversal::resolve_style;
38use style::values::computed::{Float, Size};
39use style::values::generics::font::LineHeight;
40use style::values::generics::position::AspectRatio;
41use style::values::specified::GenericGridTemplateComponent;
42use style::values::specified::box_::DisplayInside;
43use style::values::specified::text::TextTransformCase;
44use style_traits::{ParsingMode, ToCss};
45
46use crate::ArcRefCell;
47use crate::display_list::StackingContextTree;
48use crate::dom::NodeExt;
49use crate::flow::inline::construct::{TextTransformation, WhitespaceCollapse, capitalize_string};
50use crate::fragment_tree::{
51 BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo,
52};
53use crate::style_ext::ComputedValuesExt;
54use crate::taffy::SpecificTaffyGridInfo;
55
56fn root_transform_for_layout_node(
59 scroll_tree: &ScrollTree,
60 node: ServoThreadSafeLayoutNode<'_>,
61) -> Option<FastLayoutTransform> {
62 let fragments = node.fragments_for_pseudo(None);
63 let box_fragment = fragments
64 .first()
65 .and_then(Fragment::retrieve_box_fragment)?
66 .borrow();
67 let scroll_tree_node_id = box_fragment.spatial_tree_node()?;
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(
568 scroll_tree: &ScrollTree,
569 node: ServoLayoutNode<'_>,
570) -> Option<OffsetParentResponse> {
571 let fragment = node
588 .to_threadsafe()
589 .fragments_for_pseudo(None)
590 .first()
591 .cloned()?;
592 let mut border_box = fragment.cumulative_box_area_rect(BoxAreaType::Border)?;
593 let cumulative_sticky_offsets = fragment
594 .retrieve_box_fragment()
595 .and_then(|box_fragment| box_fragment.borrow().spatial_tree_node())
596 .map(|node_id| {
597 scroll_tree
598 .cumulative_sticky_offsets(node_id)
599 .map(Au::from_f32_px)
600 .cast_unit()
601 });
602 border_box = border_box.translate(cumulative_sticky_offsets.unwrap_or_default());
603
604 let Some(offset_parent_fragment) = offset_parent_fragments(node) else {
609 return Some(OffsetParentResponse {
610 node_address: None,
611 rect: border_box.to_untyped(),
612 });
613 };
614
615 let parent_fragment = offset_parent_fragment.parent.borrow();
616 let parent_is_static_body_element = parent_fragment
617 .base
618 .flags
619 .contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) &&
620 parent_fragment.style.get_box().position == Position::Static;
621
622 let grandparent_box_fragment = || match offset_parent_fragment.grandparent {
631 Some(Fragment::Box(box_fragment)) | Some(Fragment::Float(box_fragment)) => {
632 Some(box_fragment)
633 },
634 _ => None,
635 };
636
637 let parent_offset_rect = if parent_is_static_body_element {
645 if let Some(grandparent_fragment) = grandparent_box_fragment() {
646 let grandparent_fragment = grandparent_fragment.borrow();
647 grandparent_fragment.offset_by_containing_block(&grandparent_fragment.border_rect())
648 } else {
649 parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect())
650 }
651 } else {
652 parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect())
653 }
654 .translate(
655 cumulative_sticky_offsets
656 .and_then(|_| parent_fragment.spatial_tree_node())
657 .map(|node_id| {
658 scroll_tree
659 .cumulative_sticky_offsets(node_id)
660 .map(Au::from_f32_px)
661 .cast_unit()
662 })
663 .unwrap_or_default(),
664 );
665
666 border_box = border_box.translate(-parent_offset_rect.origin.to_vector());
667
668 Some(OffsetParentResponse {
669 node_address: parent_fragment.base.tag.map(|tag| tag.node.into()),
670 rect: border_box.to_untyped(),
671 })
672}
673
674#[inline]
678pub(crate) fn process_scroll_container_query(
679 node: Option<ServoLayoutNode<'_>>,
680 query_flags: ScrollContainerQueryFlags,
681 viewport_overflow: AxesOverflow,
682) -> Option<ScrollContainerResponse> {
683 let Some(node) = node else {
684 return Some(ScrollContainerResponse::Viewport(viewport_overflow));
685 };
686
687 let layout_data = node.to_threadsafe().inner_layout_data()?;
688
689 let layout_box = layout_data.self_box.borrow();
692 let layout_box = layout_box.as_ref()?;
693
694 let (style, flags) =
695 layout_box.with_first_base(|base| (base.style.clone(), base.base_fragment_info.flags))?;
696
697 if query_flags.contains(ScrollContainerQueryFlags::ForScrollParent) &&
703 flags.intersects(
704 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
705 )
706 {
707 return None;
708 }
709
710 if query_flags.contains(ScrollContainerQueryFlags::Inclusive) &&
711 style.establishes_scroll_container(flags)
712 {
713 return Some(ScrollContainerResponse::Element(
714 node.opaque().into(),
715 style.effective_overflow(flags),
716 ));
717 }
718
719 let mut current_position_value = style.clone_position();
739 let mut current_ancestor = node.as_element()?;
740 while let Some(ancestor) = current_ancestor.traversal_parent() {
741 current_ancestor = ancestor;
742
743 let Some(layout_data) = ancestor.as_node().to_threadsafe().inner_layout_data() else {
744 continue;
745 };
746 let ancestor_layout_box = layout_data.self_box.borrow();
747 let Some(ancestor_layout_box) = ancestor_layout_box.as_ref() else {
748 continue;
749 };
750
751 let Some((ancestor_style, ancestor_flags)) = ancestor_layout_box
752 .with_first_base(|base| (base.style.clone(), base.base_fragment_info.flags))
753 else {
754 continue;
755 };
756
757 let is_containing_block = match current_position_value {
758 Position::Static | Position::Relative | Position::Sticky => {
759 !ancestor_style.is_inline_box(ancestor_flags)
760 },
761 Position::Absolute => {
762 ancestor_style.establishes_containing_block_for_absolute_descendants(ancestor_flags)
763 },
764 Position::Fixed => {
765 ancestor_style.establishes_containing_block_for_all_descendants(ancestor_flags)
766 },
767 };
768 if !is_containing_block {
769 continue;
770 }
771
772 if ancestor_style.establishes_scroll_container(ancestor_flags) {
773 return Some(ScrollContainerResponse::Element(
774 ancestor.as_node().opaque().into(),
775 ancestor_style.effective_overflow(ancestor_flags),
776 ));
777 }
778
779 current_position_value = ancestor_style.clone_position();
780 }
781
782 match current_position_value {
783 Position::Fixed => None,
784 _ => Some(ScrollContainerResponse::Viewport(viewport_overflow)),
785 }
786}
787
788pub fn get_the_text_steps(node: ServoLayoutNode<'_>) -> String {
790 let mut results = Vec::new();
796 let mut max_req_line_break_count = 0;
797
798 let mut state = Default::default();
800 for child in node.dom_children() {
801 let mut current = rendered_text_collection_steps(child, &mut state);
803 results.append(&mut current);
805 }
806
807 let mut output = Vec::new();
808 for item in results {
809 match item {
810 InnerOrOuterTextItem::Text(s) => {
811 if !s.is_empty() {
813 if max_req_line_break_count > 0 {
814 output.push("\u{000A}".repeat(max_req_line_break_count));
816 max_req_line_break_count = 0;
817 }
818 output.push(s);
819 }
820 },
821 InnerOrOuterTextItem::RequiredLineBreakCount(count) => {
822 if output.is_empty() {
824 continue;
826 }
827 if count > max_req_line_break_count {
831 max_req_line_break_count = count;
832 }
833 },
834 }
835 }
836 output.into_iter().collect()
837}
838
839enum InnerOrOuterTextItem {
840 Text(String),
841 RequiredLineBreakCount(usize),
842}
843
844#[derive(Clone)]
845struct RenderedTextCollectionState {
846 first_table_row: bool,
848 first_table_cell: bool,
850 within_table: bool,
853 may_start_with_whitespace: bool,
855 did_truncate_trailing_white_space: bool,
858 within_table_content: bool,
861}
862
863impl Default for RenderedTextCollectionState {
864 fn default() -> Self {
865 RenderedTextCollectionState {
866 first_table_row: true,
867 first_table_cell: true,
868 may_start_with_whitespace: true,
869 did_truncate_trailing_white_space: false,
870 within_table: false,
871 within_table_content: false,
872 }
873 }
874}
875
876fn rendered_text_collection_steps(
878 node: ServoLayoutNode<'_>,
879 state: &mut RenderedTextCollectionState,
880) -> Vec<InnerOrOuterTextItem> {
881 let mut items = vec![];
885 if !node.is_connected() || !(node.is_element() || node.is_text_node()) {
886 return items;
887 }
888
889 match node.type_id() {
890 LayoutNodeType::Text => {
891 if let Some(element) = node.parent_node() {
892 match element.type_id() {
893 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
895 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
896 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
897 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
898 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
899 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
900 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement) => {
901 return items;
902 },
903 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) => {
907 if let Some(element) = element.parent_node() {
908 if !matches!(
909 element.type_id(),
910 LayoutNodeType::Element(LayoutElementType::HTMLSelectElement)
911 ) {
912 return items;
913 }
914 } else {
915 return items;
916 }
917 },
918 LayoutNodeType::Element(LayoutElementType::HTMLSelectElement) => return items,
919 _ => {},
920 }
921
922 if state.within_table && !state.within_table_content {
926 return items;
927 }
928
929 let Some(style_data) = element.style_data() else {
930 return items;
931 };
932
933 let element_data = style_data.element_data.borrow();
934 let Some(style) = element_data.styles.get_primary() else {
935 return items;
936 };
937
938 if style.get_inherited_box().visibility != Visibility::Visible {
944 return items;
945 }
946
947 let display = style.get_box().display;
951 if display == Display::None {
952 match element.type_id() {
953 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) |
956 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement) => {},
957 _ => {
958 return items;
959 },
960 }
961 }
962
963 let text_content = node.to_threadsafe().node_text_content();
964
965 let white_space_collapse = style.clone_white_space_collapse();
966 let preserve_whitespace = white_space_collapse == WhiteSpaceCollapseValue::Preserve;
967 let is_inline = matches!(
968 display,
969 Display::InlineBlock | Display::InlineFlex | Display::InlineGrid
970 );
971 let trim_beginning_white_space =
975 !preserve_whitespace && (state.may_start_with_whitespace || is_inline);
976 let with_white_space_rules_applied = WhitespaceCollapse::new(
977 text_content.chars(),
978 white_space_collapse,
979 trim_beginning_white_space,
980 );
981
982 let text_transform = style.clone_text_transform().case();
990 let mut transformed_text: String =
991 TextTransformation::new(with_white_space_rules_applied, text_transform)
992 .collect();
993
994 if TextTransformCase::Capitalize == text_transform {
998 transformed_text = capitalize_string(&transformed_text, true);
999 }
1000
1001 let is_preformatted_element =
1002 white_space_collapse == WhiteSpaceCollapseValue::Preserve;
1003
1004 let is_final_character_whitespace = transformed_text
1005 .chars()
1006 .next_back()
1007 .filter(char::is_ascii_whitespace)
1008 .is_some();
1009
1010 let is_first_character_whitespace = transformed_text
1011 .chars()
1012 .next()
1013 .filter(char::is_ascii_whitespace)
1014 .is_some();
1015
1016 if state.did_truncate_trailing_white_space && !is_first_character_whitespace {
1020 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1021 };
1022
1023 if !transformed_text.is_empty() {
1024 if is_final_character_whitespace && !is_preformatted_element {
1027 state.may_start_with_whitespace = false;
1028 state.did_truncate_trailing_white_space = true;
1029 transformed_text.pop();
1030 } else {
1031 state.may_start_with_whitespace = is_final_character_whitespace;
1032 state.did_truncate_trailing_white_space = false;
1033 }
1034 items.push(InnerOrOuterTextItem::Text(transformed_text));
1035 }
1036 } else {
1037 items.push(InnerOrOuterTextItem::Text(
1040 node.to_threadsafe().node_text_content().into(),
1041 ));
1042 }
1043 },
1044 LayoutNodeType::Element(LayoutElementType::HTMLBRElement) => {
1045 state.did_truncate_trailing_white_space = false;
1048 state.may_start_with_whitespace = true;
1049 items.push(InnerOrOuterTextItem::Text(String::from("\u{000A}")));
1050 },
1051 _ => {
1052 let Some(style_data) = node.style_data() else {
1055 return items;
1056 };
1057
1058 let element_data = style_data.element_data.borrow();
1059 let Some(style) = element_data.styles.get_primary() else {
1060 return items;
1061 };
1062 let inherited_box = style.get_inherited_box();
1063
1064 if inherited_box.visibility != Visibility::Visible {
1065 for child in node.dom_children() {
1069 items.append(&mut rendered_text_collection_steps(child, state));
1070 }
1071 return items;
1072 }
1073
1074 let style_box = style.get_box();
1075 let display = style_box.display;
1076 let mut surrounding_line_breaks = 0;
1077
1078 if style_box.position == Position::Absolute || style_box.float != Float::None {
1080 surrounding_line_breaks = 1;
1081 }
1082
1083 match display {
1086 Display::Table => {
1087 surrounding_line_breaks = 1;
1088 state.within_table = true;
1089 },
1090 Display::TableCell => {
1095 if !state.first_table_cell {
1096 items.push(InnerOrOuterTextItem::Text(String::from(
1097 "\u{0009}", )));
1099 state.did_truncate_trailing_white_space = false;
1101 }
1102 state.first_table_cell = false;
1103 state.within_table_content = true;
1104 },
1105 Display::TableRow => {
1110 if !state.first_table_row {
1111 items.push(InnerOrOuterTextItem::Text(String::from(
1112 "\u{000A}", )));
1114 state.did_truncate_trailing_white_space = false;
1116 }
1117 state.first_table_row = false;
1118 state.first_table_cell = true;
1119 },
1120 Display::Block => {
1123 surrounding_line_breaks = 1;
1124 },
1125 Display::TableCaption => {
1126 surrounding_line_breaks = 1;
1127 state.within_table_content = true;
1128 },
1129 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock => {
1130 if state.did_truncate_trailing_white_space {
1134 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1135 state.did_truncate_trailing_white_space = false;
1136 state.may_start_with_whitespace = true;
1137 }
1138 },
1139 _ => {},
1140 }
1141
1142 match node.type_id() {
1143 LayoutNodeType::Element(LayoutElementType::HTMLParagraphElement) => {
1146 surrounding_line_breaks = 2;
1147 },
1148 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement) |
1151 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) => {
1152 surrounding_line_breaks = 1;
1153 },
1154 _ => {},
1155 }
1156
1157 if surrounding_line_breaks > 0 {
1158 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1159 surrounding_line_breaks,
1160 ));
1161 state.did_truncate_trailing_white_space = false;
1162 state.may_start_with_whitespace = true;
1163 }
1164
1165 match node.type_id() {
1166 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
1171 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
1172 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
1173 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
1174 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
1175 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
1176 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement) => {
1177 if display != Display::Block && state.did_truncate_trailing_white_space {
1178 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1179 state.did_truncate_trailing_white_space = false;
1180 };
1181 state.may_start_with_whitespace = false;
1182 },
1183 _ => {
1184 for child in node.dom_children() {
1187 items.append(&mut rendered_text_collection_steps(child, state));
1188 }
1189 },
1190 }
1191
1192 match display {
1195 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock => {
1196 state.did_truncate_trailing_white_space = false;
1197 state.may_start_with_whitespace = false;
1198 },
1199 Display::Table => {
1200 state.within_table = false;
1201 },
1202 Display::TableCell | Display::TableCaption => {
1203 state.within_table_content = false;
1204 },
1205 _ => {},
1206 }
1207
1208 if surrounding_line_breaks > 0 {
1209 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1210 surrounding_line_breaks,
1211 ));
1212 state.did_truncate_trailing_white_space = false;
1213 state.may_start_with_whitespace = true;
1214 }
1215 },
1216 };
1217 items
1218}
1219
1220pub fn process_text_index_request(_node: OpaqueNode, _point: Point2D<Au>) -> Option<usize> {
1221 None
1222}
1223
1224pub fn process_resolved_font_style_query<'dom, E>(
1225 context: &SharedStyleContext,
1226 node: E,
1227 value: &str,
1228 url_data: ServoUrl,
1229 shared_lock: &SharedRwLock,
1230) -> Option<ServoArc<Font>>
1231where
1232 E: LayoutNode<'dom>,
1233{
1234 fn create_font_declaration(
1235 value: &str,
1236 url_data: &ServoUrl,
1237 quirks_mode: QuirksMode,
1238 ) -> Option<PropertyDeclarationBlock> {
1239 let mut declarations = SourcePropertyDeclaration::default();
1240 let result = parse_one_declaration_into(
1241 &mut declarations,
1242 PropertyId::NonCustom(ShorthandId::Font.into()),
1243 value,
1244 Origin::Author,
1245 &UrlExtraData(url_data.get_arc()),
1246 None,
1247 ParsingMode::DEFAULT,
1248 quirks_mode,
1249 CssRuleType::Style,
1250 );
1251 let declarations = match result {
1252 Ok(()) => {
1253 let mut block = PropertyDeclarationBlock::new();
1254 block.extend(declarations.drain(), Importance::Normal);
1255 block
1256 },
1257 Err(_) => return None,
1258 };
1259 Some(declarations)
1261 }
1262 fn resolve_for_declarations<'dom, E>(
1263 context: &SharedStyleContext,
1264 parent_style: Option<&ComputedValues>,
1265 declarations: PropertyDeclarationBlock,
1266 shared_lock: &SharedRwLock,
1267 ) -> ServoArc<ComputedValues>
1268 where
1269 E: LayoutNode<'dom>,
1270 {
1271 let parent_style = match parent_style {
1272 Some(parent) => parent,
1273 None => context.stylist.device().default_computed_values(),
1274 };
1275 context
1276 .stylist
1277 .compute_for_declarations::<E::ConcreteElement>(
1278 &context.guards,
1279 parent_style,
1280 ServoArc::new(shared_lock.wrap(declarations)),
1281 )
1282 }
1283
1284 let quirks_mode = context.quirks_mode();
1287 let declarations = create_font_declaration(value, &url_data, quirks_mode)?;
1288
1289 let element = node.as_element().unwrap();
1293 let parent_style = if node.is_connected() {
1294 if element.has_data() {
1295 node.to_threadsafe().as_element().unwrap().style(context)
1296 } else {
1297 let mut tlc = ThreadLocalStyleContext::new();
1298 let mut context = StyleContext {
1299 shared: context,
1300 thread_local: &mut tlc,
1301 };
1302 let styles = resolve_style(&mut context, element, RuleInclusion::All, None, None);
1303 styles.primary().clone()
1304 }
1305 } else {
1306 let default_declarations =
1307 create_font_declaration("10px sans-serif", &url_data, quirks_mode).unwrap();
1308 resolve_for_declarations::<E>(context, None, default_declarations, shared_lock)
1309 };
1310
1311 let computed_values =
1313 resolve_for_declarations::<E>(context, Some(&*parent_style), declarations, shared_lock);
1314
1315 Some(computed_values.clone_font())
1316}
1317
1318fn transform_au_rectangle(
1319 rect_to_transform: Rect<Au>,
1320 transform: FastLayoutTransform,
1321) -> Option<Rect<Au>> {
1322 let rect_to_transform = &au_rect_to_f32_rect(rect_to_transform).cast_unit();
1323 let outer_transformed_rect = match transform {
1324 FastLayoutTransform::Offset(offset) => Some(rect_to_transform.translate(offset)),
1325 FastLayoutTransform::Transform { transform, .. } => {
1326 transform.outer_transformed_rect(rect_to_transform)
1327 },
1328 };
1329 outer_transformed_rect
1330 .map(|transformed_rect| f32_rect_to_au_rect(transformed_rect.to_untyped()))
1331}