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    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
56/// Get a scroll node that would represents this [`ServoLayoutNode`]'s transform and
57/// calculate its cumlative transform from its root scroll node to the scroll node.
58fn 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
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(
568    scroll_tree: &ScrollTree,
569    node: ServoLayoutNode<'_>,
570) -> Option<OffsetParentResponse> {
571    // Only consider the first fragment of the node found as per a
572    // possible interpretation of the specification: "[...] return the
573    // y-coordinate of the top border edge of the first CSS layout box
574    // associated with the element [...]"
575    //
576    // FIXME: Browsers implement this all differently (e.g., [1]) -
577    // Firefox does returns the union of all layout elements of some
578    // sort. Chrome returns the first fragment for a block element (the
579    // same as ours) or the union of all associated fragments in the
580    // first containing block fragment for an inline element. We could
581    // implement Chrome's behavior, but our fragment tree currently
582    // provides insufficient information.
583    //
584    // [1]: https://github.com/w3c/csswg-drafts/issues/4541
585    // > 1. If the element is the HTML body element or does not have any associated CSS
586    //      layout box return zero and terminate this algorithm.
587    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    // 2.  If the offsetParent of the element is null return the x-coordinate of the left
605    //     border edge of the first CSS layout box associated with the element, relative to
606    //     the initial containing block origin, ignoring any transforms that apply to the
607    //     element and its ancestors, and terminate this algorithm.
608    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    // For `offsetLeft`:
623    // 3. Return the result of subtracting the y-coordinate of the top padding edge of the
624    //    first CSS layout box associated with the offsetParent of the element from the
625    //    y-coordinate of the top border edge of the first CSS layout box associated with the
626    //    element, relative to the initial containing block origin, ignoring any transforms
627    //    that apply to the element and its ancestors.
628    //
629    // We generalize this for `offsetRight` as described in the specification.
630    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    // The spec (https://www.w3.org/TR/cssom-view-1/#extensions-to-the-htmlelement-interface)
638    // says that offsetTop/offsetLeft are always relative to the padding box of the offsetParent.
639    // However, in practice this is not true in major browsers in the case that the offsetParent is the body
640    // element and the body element is position:static. In that case offsetLeft/offsetTop are computed
641    // relative to the root node's border box.
642    //
643    // See <https://github.com/w3c/csswg-drafts/issues/10549>.
644    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/// An implementation of `scrollParent` that can also be used to for `scrollIntoView`:
675/// <https://drafts.csswg.org/cssom-view/#dom-htmlelement-scrollparent>.
676///
677#[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    // 1. If any of the following holds true, return null and terminate this algorithm:
690    //  - The element does not have an associated box.
691    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    // - The element is the root element.
698    // - The element is the body element.
699    //
700    // Note: We only do this for `scrollParent`, which needs to be null. But `scrollIntoView` on the
701    // `<body>` or root element should still bring it into view by scrolling the viewport.
702    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    // - The element’s computed value of the position property is fixed and no ancestor
720    //   establishes a fixed position containing block.
721    //
722    // This is handled below in step 2.
723
724    // 2. Let ancestor be the containing block of the element in the flat tree and repeat these substeps:
725    // - If ancestor is the initial containing block, return the scrollingElement for the
726    //   element’s document if it is not closed-shadow-hidden from the element, otherwise
727    //   return null.
728    // - If ancestor is not closed-shadow-hidden from the element, and is a scroll
729    //   container, terminate this algorithm and return ancestor.
730    // - If the computed value of the position property of ancestor is fixed, and no
731    //   ancestor establishes a fixed position containing block, terminate this algorithm
732    //   and return null.
733    // - Let ancestor be the containing block of ancestor in the flat tree.
734    //
735    // Notes: We don't follow the specification exactly below, but we follow the spirit.
736    //
737    // TODO: Handle the situation where the ancestor is "closed-shadow-hidden" from the element.
738    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
788/// <https://html.spec.whatwg.org/multipage/#get-the-text-steps>
789pub fn get_the_text_steps(node: ServoLayoutNode<'_>) -> String {
790    // Step 1: If element is not being rendered or if the user agent is a non-CSS user agent, then
791    // return element's descendant text content.
792    // This is taken care of in HTMLElemnent code
793
794    // Step 2: Let results be a new empty list.
795    let mut results = Vec::new();
796    let mut max_req_line_break_count = 0;
797
798    // Step 3: For each child node node of element:
799    let mut state = Default::default();
800    for child in node.dom_children() {
801        // Step 1: Let current be the list resulting in running the rendered text collection steps with node.
802        let mut current = rendered_text_collection_steps(child, &mut state);
803        // Step 2: For each item item in current, append item to results.
804        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                // Step 3.
812                if !s.is_empty() {
813                    if max_req_line_break_count > 0 {
814                        // Step 5.
815                        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                // Step 4.
823                if output.is_empty() {
824                    // Remove required line break count at the start.
825                    continue;
826                }
827                // Store the count if it's the max of this run, but it may be ignored if no text
828                // item is found afterwards, which means that these are consecutive line breaks at
829                // the end.
830                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    /// Used to make sure we don't add a `\n` before the first row
847    first_table_row: bool,
848    /// Used to make sure we don't add a `\t` before the first column
849    first_table_cell: bool,
850    /// Keeps track of whether we're inside a table, since there are special rules like ommiting everything that's not
851    /// inside a TableCell/TableCaption
852    within_table: bool,
853    /// Determines whether we truncate leading whitespaces for normal nodes or not
854    may_start_with_whitespace: bool,
855    /// Is set whenever we truncated a white space char, used to prepend a single space before the next element,
856    /// that way we truncate trailing white space without having to look ahead
857    did_truncate_trailing_white_space: bool,
858    /// Is set to true when we're rendering the children of TableCell/TableCaption elements, that way we render
859    /// everything inside those as normal, while omitting everything that's in a Table but NOT in a Cell/Caption
860    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
876/// <https://html.spec.whatwg.org/multipage/#rendered-text-collection-steps>
877fn rendered_text_collection_steps(
878    node: ServoLayoutNode<'_>,
879    state: &mut RenderedTextCollectionState,
880) -> Vec<InnerOrOuterTextItem> {
881    // Step 1. Let items be the result of running the rendered text collection
882    // steps with each child node of node in tree order,
883    // and then concatenating the results to a single list.
884    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                    // Any text contained in these elements must be ignored.
894                    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                    // Select/Option/OptGroup elements are handled a bit differently.
904                    // Basically: a Select can only contain Options or OptGroups, while
905                    // OptGroups may also contain Options. Everything else gets ignored.
906                    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                // Tables are also a bit special, mainly by only allowing
923                // content within TableCell or TableCaption elements once
924                // we're inside a Table.
925                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                // Step 2: If node's computed value of 'visibility' is not 'visible', then return items.
939                //
940                // We need to do this check here on the Text fragment, if we did it on the element and
941                // just skipped rendering all child nodes then there'd be no way to override the
942                // visibility in a child node.
943                if style.get_inherited_box().visibility != Visibility::Visible {
944                    return items;
945                }
946
947                // Step 3: If node is not being rendered, then return items. For the purpose of this step,
948                // the following elements must act as described if the computed value of the 'display'
949                // property is not 'none':
950                let display = style.get_box().display;
951                if display == Display::None {
952                    match element.type_id() {
953                        // Even if set to Display::None, Option/OptGroup elements need to
954                        // be rendered.
955                        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                // Now we need to decide on whether to remove beginning white space or not, this
972                // is mainly decided by the elements we rendered before, but may be overwritten by the white-space
973                // property.
974                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                // Step 4: If node is a Text node, then for each CSS text box produced by node, in
983                // content order, compute the text of the box after application of the CSS
984                // 'white-space' processing rules and 'text-transform' rules, set items to the list
985                // of the resulting strings, and return items. The CSS 'white-space' processing
986                // rules are slightly modified: collapsible spaces at the end of lines are always
987                // collapsed, but they are only removed if the line is the last line of the block,
988                // or it ends with a br element. Soft hyphens should be preserved.
989                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                // Since iterator for capitalize not doing anything, we must handle it outside here
995                // FIXME: This assumes the element always start at a word boundary. But can fail:
996                // a<span style="text-transform: capitalize">b</span>c
997                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                // By truncating trailing white space and then adding it back in once we
1017                // encounter another text node we can ensure no trailing white space for
1018                // normal text without having to look ahead
1019                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                    // Here we decide whether to keep or truncate the final white
1025                    // space character, if there is one.
1026                    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                // If we don't have a parent element then there's no style data available,
1038                // in this (pretty unlikely) case we just return the Text fragment as is.
1039                items.push(InnerOrOuterTextItem::Text(
1040                    node.to_threadsafe().node_text_content().into(),
1041                ));
1042            }
1043        },
1044        LayoutNodeType::Element(LayoutElementType::HTMLBRElement) => {
1045            // Step 5: If node is a br element, then append a string containing a single U+000A
1046            // LF code point to items.
1047            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            // First we need to gather some infos to setup the various flags
1053            // before rendering the child nodes
1054            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                // If the element is not visible then we'll immediatly render all children,
1066                // skipping all other processing.
1067                // We can't just stop here since a child can override a parents visibility.
1068                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            // Treat absolutely positioned or floated elements like Block elements
1079            if style_box.position == Position::Absolute || style_box.float != Float::None {
1080                surrounding_line_breaks = 1;
1081            }
1082
1083            // Depending on the display property we have to do various things
1084            // before we can render the child nodes.
1085            match display {
1086                Display::Table => {
1087                    surrounding_line_breaks = 1;
1088                    state.within_table = true;
1089                },
1090                // Step 6: If node's computed value of 'display' is 'table-cell',
1091                // and node's CSS box is not the last 'table-cell' box of its
1092                // enclosing 'table-row' box, then append a string containing
1093                // a single U+0009 TAB code point to items.
1094                Display::TableCell => {
1095                    if !state.first_table_cell {
1096                        items.push(InnerOrOuterTextItem::Text(String::from(
1097                            "\u{0009}", /* tab */
1098                        )));
1099                        // Make sure we don't add a white-space we removed from the previous node
1100                        state.did_truncate_trailing_white_space = false;
1101                    }
1102                    state.first_table_cell = false;
1103                    state.within_table_content = true;
1104                },
1105                // Step 7: If node's computed value of 'display' is 'table-row',
1106                // and node's CSS box is not the last 'table-row' box of the nearest
1107                // ancestor 'table' box, then append a string containing a single U+000A
1108                // LF code point to items.
1109                Display::TableRow => {
1110                    if !state.first_table_row {
1111                        items.push(InnerOrOuterTextItem::Text(String::from(
1112                            "\u{000A}", /* Line Feed */
1113                        )));
1114                        // Make sure we don't add a white-space we removed from the previous node
1115                        state.did_truncate_trailing_white_space = false;
1116                    }
1117                    state.first_table_row = false;
1118                    state.first_table_cell = true;
1119                },
1120                // Step 9: If node's used value of 'display' is block-level or 'table-caption',
1121                // then append 1 (a required line break count) at the beginning and end of items.
1122                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                    // InlineBlock's are a bit strange, in that they don't produce a Linebreak, yet
1131                    // disable white space truncation before and after it, making it one of the few
1132                    // cases where one can have multiple white space characters following one another.
1133                    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                // Step 8: If node is a p element, then append 2 (a required line break count) at
1144                // the beginning and end of items.
1145                LayoutNodeType::Element(LayoutElementType::HTMLParagraphElement) => {
1146                    surrounding_line_breaks = 2;
1147                },
1148                // Option/OptGroup elements should go on separate lines, by treating them like
1149                // Block elements we can achieve that.
1150                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                // Any text/content contained in these elements is ignored.
1167                // However we still need to check whether we have to prepend a
1168                // space, since for example <span>asd <input> qwe</span> must
1169                // product "asd  qwe" (note the 2 spaces)
1170                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                    // Now we can finally iterate over all children, appending whatever
1185                    // they produce to items.
1186                    for child in node.dom_children() {
1187                        items.append(&mut rendered_text_collection_steps(child, state));
1188                    }
1189                },
1190            }
1191
1192            // Depending on the display property we still need to do some
1193            // cleanup after rendering all child nodes
1194            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        // TODO: Force to set line-height property to 'normal' font property.
1260        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    // https://html.spec.whatwg.org/multipage/#dom-context-2d-font
1285    // 1. Parse the given font property value
1286    let quirks_mode = context.quirks_mode();
1287    let declarations = create_font_declaration(value, &url_data, quirks_mode)?;
1288
1289    // TODO: Reject 'inherit' and 'initial' values for the font property.
1290
1291    // 2. Get resolved styles for the parent element
1292    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    // 3. Resolve the parsed value with resolved styles of the parent element
1312    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}