layout/
query.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Utilities for querying the layout, as needed by layout.
6use 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
55/// Get a scroll node that would represents this [`ServoLayoutNode`]'s transform and
56/// calculate its cumlative transform from its root scroll node to the scroll node.
57fn 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
128/// <https://drafts.csswg.org/cssom-view/#scrolling-area>
129pub 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
155/// Return the resolved value of property for a given (pseudo)element.
156/// <https://drafts.csswg.org/cssom/#resolved-value>
157pub 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    // We call process_resolved_style_request after performing a whole-document
168    // traversal, so in the common case, the element is styled.
169    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                    // The pseudo doesn't exist, return nothing.  Chrome seems to query
176                    // the element itself in this case, Firefox uses the resolved value.
177                    // https://www.w3.org/Bugs/Public/show_bug.cgi?id=29006
178                    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    // https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle
214    // Here we are trying to conform to the specification that says that getComputedStyle
215    // should return the used values in certain circumstances. For size and positional
216    // properties we might need to walk the Fragment tree to figure those out. We always
217    // fall back to returning the computed value.
218
219    // For line height, the resolved value is the computed value if it
220    // is "normal" and the used value otherwise.
221    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            // There could be a fragment, but it's only interesting for `min-width` and `min-height`,
226            // so just pass None.
227            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    // https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle
234    // The properties that we calculate below all resolve to the computed value
235    // when the element is display:none or display:contents.
236    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        // https://drafts.csswg.org/css-grid/#resolved-track-list
280        // > The grid-template-rows and grid-template-columns properties are
281        // > resolved value special case properties.
282        //
283        // > When an element generates a grid container box...
284        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        // https://drafts.csswg.org/cssom/#resolved-value-special-case-property-like-height
293        // > If the property applies to the element or pseudo-element and the resolved value of the
294        // > display property is not none or contents, then the resolved value is the used value.
295        // > Otherwise the resolved value is the computed value.
296        //
297        // However, all browsers ignore that for margin and padding properties, and resolve to a length
298        // even if the property doesn't apply: https://github.com/w3c/csswg-drafts/issues/10391
299        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    // https://drafts.csswg.org/css-sizing-3/#preferred-size-properties
328    // > Applies to: all elements except non-replaced inlines
329    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    // <https://drafts.csswg.org/css-sizing-3/#automatic-minimum-size>
342    // For backwards-compatibility, the resolved value of an automatic minimum size is zero
343    // for boxes of all CSS2 display types: block and inline boxes, inline blocks, and all
344    // the table layout boxes. It also resolves to zero when no box is generated.
345    //
346    // <https://github.com/w3c/csswg-drafts/issues/11716>
347    // However, when a box is generated and `aspect-ratio` isn't `auto`, we need to preserve
348    // the automatic minimum size as `auto`.
349    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    // https://drafts.csswg.org/css-grid/#resolved-track-list-standalone
363    fn serialize_standalone_non_subgrid_track_list(track_sizes: &[Au]) -> Option<String> {
364        match track_sizes.is_empty() {
365            // Standalone non subgrid grids with empty track lists should compute to `none`.
366            // As of current standard, this behaviour should only invoked by `none` computed value,
367            // therefore we can fallback into computed value resolving.
368            true => None,
369            // <https://drafts.csswg.org/css-grid/#resolved-track-list-standalone>
370            // > - Every track listed individually, whether implicitly or explicitly created,
371            //     without using the repeat() notation.
372            // > - Every track size given as a length in pixels, regardless of sizing function.
373            // > - Adjacent line names collapsed into a single bracketed set.
374            // TODO: implement line names
375            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        // <https://drafts.csswg.org/css-grid/#resolved-track-list-standalone>
395        // > When an element generates a grid container box, the resolved value of its grid-template-rows or
396        // > grid-template-columns property in a standalone axis is the used value, serialized with:
397        GenericGridTemplateComponent::None |
398        GenericGridTemplateComponent::TrackList(_) |
399        GenericGridTemplateComponent::Masonry => {
400            serialize_standalone_non_subgrid_track_list(&track_info.sizes)
401        },
402
403        // <https://drafts.csswg.org/css-grid/#resolved-track-list-subgrid>
404        // > When an element generates a grid container box that is a subgrid, the resolved value of the
405        // > grid-template-rows and grid-template-columns properties represents the used number of columns,
406        // > serialized as the subgrid keyword followed by a list representing each of its lines as a
407        // > line name set of all the line’s names explicitly defined on the subgrid (not including those
408        // > adopted from the parent grid), without using the repeat() notation.
409        // TODO: implement subgrid
410        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    // In a display: none subtree. No pseudo-element exists.
421    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        // <https://drafts.csswg.org/css-sizing-3/#automatic-minimum-size>
452        // The resolved value of an automatic minimum size is zero when no box is generated.
453        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        // No need to care about used values here, since we're on a display: none
457        // subtree, use the computed value.
458        _ => 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
492/// <https://www.w3.org/TR/2016/WD-cssom-view-1-20160317/#dom-htmlelement-offsetparent>
493fn offset_parent_fragments(node: ServoLayoutNode<'_>) -> Option<OffsetParentFragments> {
494    // 1. If any of the following holds true return null and terminate this algorithm:
495    //  * The element does not have an associated CSS layout box.
496    //  * The element is the root element.
497    //  * The element is the HTML body element.
498    //  * The element’s computed value of the position property is fixed.
499    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    // 2.  Return the nearest ancestor element of the element for which at least one of
517    //     the following is true and terminate this algorithm if such an ancestor is found:
518    //  * The computed value of the position property is not static.
519    //  * It is the HTML body element.
520    //  * The computed value of the position property of the element is static and the
521    //    ancestor is one of the following HTML elements: td, th, or table.
522    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    // Only consider the first fragment of the node found as per a
569    // possible interpretation of the specification: "[...] return the
570    // y-coordinate of the top border edge of the first CSS layout box
571    // associated with the element [...]"
572    //
573    // FIXME: Browsers implement this all differently (e.g., [1]) -
574    // Firefox does returns the union of all layout elements of some
575    // sort. Chrome returns the first fragment for a block element (the
576    // same as ours) or the union of all associated fragments in the
577    // first containing block fragment for an inline element. We could
578    // implement Chrome's behavior, but our fragment tree currently
579    // provides insufficient information.
580    //
581    // [1]: https://github.com/w3c/csswg-drafts/issues/4541
582    // > 1. If the element is the HTML body element or does not have any associated CSS
583    //      layout box return zero and terminate this algorithm.
584    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    // 2.  If the offsetParent of the element is null return the x-coordinate of the left
592    //     border edge of the first CSS layout box associated with the element, relative to
593    //     the initial containing block origin, ignoring any transforms that apply to the
594    //     element and its ancestors, and terminate this algorithm.
595    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    // For `offsetLeft`:
610    // 3. Return the result of subtracting the y-coordinate of the top padding edge of the
611    //    first CSS layout box associated with the offsetParent of the element from the
612    //    y-coordinate of the top border edge of the first CSS layout box associated with the
613    //    element, relative to the initial containing block origin, ignoring any transforms
614    //    that apply to the element and its ancestors.
615    //
616    // We generalize this for `offsetRight` as described in the specification.
617    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    // The spec (https://www.w3.org/TR/cssom-view-1/#extensions-to-the-htmlelement-interface)
625    // says that offsetTop/offsetLeft are always relative to the padding box of the offsetParent.
626    // However, in practice this is not true in major browsers in the case that the offsetParent is the body
627    // element and the body element is position:static. In that case offsetLeft/offsetTop are computed
628    // relative to the root node's border box.
629    //
630    // See <https://github.com/w3c/csswg-drafts/issues/10549>.
631    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/// This is an implementation of
651/// <https://drafts.csswg.org/cssom-view/#dom-htmlelement-scrollparent>.
652#[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    // 1. If any of the following holds true, return null and terminate this algorithm:
659    //  - The element does not have an associated box.
660    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    // - The element is the root element.
667    // - The element is the body element.
668    // - The element’s computed value of the position property is fixed and no ancestor
669    //   establishes a fixed position containing block.
670    if flags.intersects(
671        FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
672    ) {
673        return None;
674    }
675
676    // 2. Let ancestor be the containing block of the element in the flat tree and repeat these substeps:
677    // - If ancestor is the initial containing block, return the scrollingElement for the
678    //   element’s document if it is not closed-shadow-hidden from the element, otherwise
679    //   return null.
680    // - If ancestor is not closed-shadow-hidden from the element, and is a scroll
681    //   container, terminate this algorithm and return ancestor.
682    // - If the computed value of the position property of ancestor is fixed, and no
683    //   ancestor establishes a fixed position containing block, terminate this algorithm
684    //   and return null.
685    // - Let ancestor be the containing block of ancestor in the flat tree.
686    //
687    // Notes: We don't follow the specification exactly below, but we follow the spirit.
688    //
689    // TODO: Handle the situation where the ancestor is "closed-shadow-hidden" from the element.
690    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
738/// <https://html.spec.whatwg.org/multipage/#get-the-text-steps>
739pub fn get_the_text_steps(node: ServoLayoutNode<'_>) -> String {
740    // Step 1: If element is not being rendered or if the user agent is a non-CSS user agent, then
741    // return element's descendant text content.
742    // This is taken care of in HTMLElemnent code
743
744    // Step 2: Let results be a new empty list.
745    let mut results = Vec::new();
746    let mut max_req_line_break_count = 0;
747
748    // Step 3: For each child node node of element:
749    let mut state = Default::default();
750    for child in node.dom_children() {
751        // Step 1: Let current be the list resulting in running the rendered text collection steps with node.
752        let mut current = rendered_text_collection_steps(child, &mut state);
753        // Step 2: For each item item in current, append item to results.
754        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                // Step 3.
762                if !s.is_empty() {
763                    if max_req_line_break_count > 0 {
764                        // Step 5.
765                        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                // Step 4.
773                if output.is_empty() {
774                    // Remove required line break count at the start.
775                    continue;
776                }
777                // Store the count if it's the max of this run, but it may be ignored if no text
778                // item is found afterwards, which means that these are consecutive line breaks at
779                // the end.
780                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    /// Used to make sure we don't add a `\n` before the first row
797    first_table_row: bool,
798    /// Used to make sure we don't add a `\t` before the first column
799    first_table_cell: bool,
800    /// Keeps track of whether we're inside a table, since there are special rules like ommiting everything that's not
801    /// inside a TableCell/TableCaption
802    within_table: bool,
803    /// Determines whether we truncate leading whitespaces for normal nodes or not
804    may_start_with_whitespace: bool,
805    /// Is set whenever we truncated a white space char, used to prepend a single space before the next element,
806    /// that way we truncate trailing white space without having to look ahead
807    did_truncate_trailing_white_space: bool,
808    /// Is set to true when we're rendering the children of TableCell/TableCaption elements, that way we render
809    /// everything inside those as normal, while omitting everything that's in a Table but NOT in a Cell/Caption
810    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
826/// <https://html.spec.whatwg.org/multipage/#rendered-text-collection-steps>
827fn rendered_text_collection_steps(
828    node: ServoLayoutNode<'_>,
829    state: &mut RenderedTextCollectionState,
830) -> Vec<InnerOrOuterTextItem> {
831    // Step 1. Let items be the result of running the rendered text collection
832    // steps with each child node of node in tree order,
833    // and then concatenating the results to a single list.
834    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                    // Any text contained in these elements must be ignored.
844                    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                    // Select/Option/OptGroup elements are handled a bit differently.
854                    // Basically: a Select can only contain Options or OptGroups, while
855                    // OptGroups may also contain Options. Everything else gets ignored.
856                    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                // Tables are also a bit special, mainly by only allowing
873                // content within TableCell or TableCaption elements once
874                // we're inside a Table.
875                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                // Step 2: If node's computed value of 'visibility' is not 'visible', then return items.
889                //
890                // We need to do this check here on the Text fragment, if we did it on the element and
891                // just skipped rendering all child nodes then there'd be no way to override the
892                // visibility in a child node.
893                if style.get_inherited_box().visibility != Visibility::Visible {
894                    return items;
895                }
896
897                // Step 3: If node is not being rendered, then return items. For the purpose of this step,
898                // the following elements must act as described if the computed value of the 'display'
899                // property is not 'none':
900                let display = style.get_box().display;
901                if display == Display::None {
902                    match element.type_id() {
903                        // Even if set to Display::None, Option/OptGroup elements need to
904                        // be rendered.
905                        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                // Now we need to decide on whether to remove beginning white space or not, this
922                // is mainly decided by the elements we rendered before, but may be overwritten by the white-space
923                // property.
924                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                // Step 4: If node is a Text node, then for each CSS text box produced by node, in
933                // content order, compute the text of the box after application of the CSS
934                // 'white-space' processing rules and 'text-transform' rules, set items to the list
935                // of the resulting strings, and return items. The CSS 'white-space' processing
936                // rules are slightly modified: collapsible spaces at the end of lines are always
937                // collapsed, but they are only removed if the line is the last line of the block,
938                // or it ends with a br element. Soft hyphens should be preserved.
939                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                // Since iterator for capitalize not doing anything, we must handle it outside here
945                // FIXME: This assumes the element always start at a word boundary. But can fail:
946                // a<span style="text-transform: capitalize">b</span>c
947                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                // By truncating trailing white space and then adding it back in once we
967                // encounter another text node we can ensure no trailing white space for
968                // normal text without having to look ahead
969                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                    // Here we decide whether to keep or truncate the final white
975                    // space character, if there is one.
976                    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                // If we don't have a parent element then there's no style data available,
988                // in this (pretty unlikely) case we just return the Text fragment as is.
989                items.push(InnerOrOuterTextItem::Text(
990                    node.to_threadsafe().node_text_content().into(),
991                ));
992            }
993        },
994        LayoutNodeType::Element(LayoutElementType::HTMLBRElement) => {
995            // Step 5: If node is a br element, then append a string containing a single U+000A
996            // LF code point to items.
997            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            // First we need to gather some infos to setup the various flags
1003            // before rendering the child nodes
1004            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                // If the element is not visible then we'll immediatly render all children,
1016                // skipping all other processing.
1017                // We can't just stop here since a child can override a parents visibility.
1018                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            // Treat absolutely positioned or floated elements like Block elements
1029            if style_box.position == Position::Absolute || style_box.float != Float::None {
1030                surrounding_line_breaks = 1;
1031            }
1032
1033            // Depending on the display property we have to do various things
1034            // before we can render the child nodes.
1035            match display {
1036                Display::Table => {
1037                    surrounding_line_breaks = 1;
1038                    state.within_table = true;
1039                },
1040                // Step 6: If node's computed value of 'display' is 'table-cell',
1041                // and node's CSS box is not the last 'table-cell' box of its
1042                // enclosing 'table-row' box, then append a string containing
1043                // a single U+0009 TAB code point to items.
1044                Display::TableCell => {
1045                    if !state.first_table_cell {
1046                        items.push(InnerOrOuterTextItem::Text(String::from(
1047                            "\u{0009}", /* tab */
1048                        )));
1049                        // Make sure we don't add a white-space we removed from the previous node
1050                        state.did_truncate_trailing_white_space = false;
1051                    }
1052                    state.first_table_cell = false;
1053                    state.within_table_content = true;
1054                },
1055                // Step 7: If node's computed value of 'display' is 'table-row',
1056                // and node's CSS box is not the last 'table-row' box of the nearest
1057                // ancestor 'table' box, then append a string containing a single U+000A
1058                // LF code point to items.
1059                Display::TableRow => {
1060                    if !state.first_table_row {
1061                        items.push(InnerOrOuterTextItem::Text(String::from(
1062                            "\u{000A}", /* Line Feed */
1063                        )));
1064                        // Make sure we don't add a white-space we removed from the previous node
1065                        state.did_truncate_trailing_white_space = false;
1066                    }
1067                    state.first_table_row = false;
1068                    state.first_table_cell = true;
1069                },
1070                // Step 9: If node's used value of 'display' is block-level or 'table-caption',
1071                // then append 1 (a required line break count) at the beginning and end of items.
1072                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                    // InlineBlock's are a bit strange, in that they don't produce a Linebreak, yet
1081                    // disable white space truncation before and after it, making it one of the few
1082                    // cases where one can have multiple white space characters following one another.
1083                    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                // Step 8: If node is a p element, then append 2 (a required line break count) at
1094                // the beginning and end of items.
1095                LayoutNodeType::Element(LayoutElementType::HTMLParagraphElement) => {
1096                    surrounding_line_breaks = 2;
1097                },
1098                // Option/OptGroup elements should go on separate lines, by treating them like
1099                // Block elements we can achieve that.
1100                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                // Any text/content contained in these elements is ignored.
1117                // However we still need to check whether we have to prepend a
1118                // space, since for example <span>asd <input> qwe</span> must
1119                // product "asd  qwe" (note the 2 spaces)
1120                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                    // Now we can finally iterate over all children, appending whatever
1135                    // they produce to items.
1136                    for child in node.dom_children() {
1137                        items.append(&mut rendered_text_collection_steps(child, state));
1138                    }
1139                },
1140            }
1141
1142            // Depending on the display property we still need to do some
1143            // cleanup after rendering all child nodes
1144            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        // TODO: Force to set line-height property to 'normal' font property.
1210        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    // https://html.spec.whatwg.org/multipage/#dom-context-2d-font
1235    // 1. Parse the given font property value
1236    let quirks_mode = context.quirks_mode();
1237    let declarations = create_font_declaration(value, &url_data, quirks_mode)?;
1238
1239    // TODO: Reject 'inherit' and 'initial' values for the font property.
1240
1241    // 2. Get resolved styles for the parent element
1242    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    // 3. Resolve the parsed value with resolved styles of the parent element
1262    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}