tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

style_adjuster.rs (42987B)


      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 //! A struct to encapsulate all the style fixups and flags propagations
      6 //! a computed style needs in order for it to adhere to the CSS spec.
      7 
      8 use crate::computed_value_flags::ComputedValueFlags;
      9 use crate::dom::TElement;
     10 use crate::logical_geometry::PhysicalSide;
     11 use crate::properties::longhands::display::computed_value::T as Display;
     12 use crate::properties::longhands::float::computed_value::T as Float;
     13 use crate::properties::longhands::position::computed_value::T as Position;
     14 #[cfg(feature = "gecko")]
     15 use crate::properties::longhands::{
     16    contain::computed_value::T as Contain, container_type::computed_value::T as ContainerType,
     17    content_visibility::computed_value::T as ContentVisibility,
     18    overflow_x::computed_value::T as Overflow,
     19 };
     20 use crate::properties::{ComputedValues, StyleBuilder};
     21 use crate::values::computed::position::{
     22    PositionTryFallbacksTryTactic, PositionTryFallbacksTryTacticKeyword, TryTacticAdjustment,
     23 };
     24 use crate::values::specified::align::AlignFlags;
     25 
     26 #[cfg(feature = "gecko")]
     27 use selectors::parser::PseudoElement;
     28 
     29 /// A struct that implements all the adjustment methods.
     30 ///
     31 /// NOTE(emilio): If new adjustments are introduced that depend on reset
     32 /// properties of the parent, you may need tweaking the
     33 /// `ChildCascadeRequirement` code in `matching.rs`.
     34 ///
     35 /// NOTE(emilio): Also, if new adjustments are introduced that break the
     36 /// following invariant:
     37 ///
     38 ///   Given same tag name, namespace, rules and parent style, two elements would
     39 ///   end up with exactly the same style.
     40 ///
     41 /// Then you need to adjust the lookup_by_rules conditions in the sharing cache.
     42 pub struct StyleAdjuster<'a, 'b: 'a> {
     43    style: &'a mut StyleBuilder<'b>,
     44 }
     45 
     46 #[cfg(feature = "gecko")]
     47 fn is_topmost_svg_svg_element<E>(e: E) -> bool
     48 where
     49    E: TElement,
     50 {
     51    debug_assert!(e.is_svg_element());
     52    if e.local_name() != &*atom!("svg") {
     53        return false;
     54    }
     55 
     56    let parent = match e.traversal_parent() {
     57        Some(n) => n,
     58        None => return true,
     59    };
     60 
     61    if !parent.is_svg_element() {
     62        return true;
     63    }
     64 
     65    parent.local_name() == &*atom!("foreignObject")
     66 }
     67 
     68 // https://drafts.csswg.org/css-display/#unbox
     69 #[cfg(feature = "gecko")]
     70 fn is_effective_display_none_for_display_contents<E>(element: E) -> bool
     71 where
     72    E: TElement,
     73 {
     74    use crate::Atom;
     75 
     76    const SPECIAL_HTML_ELEMENTS: [Atom; 16] = [
     77        atom!("br"),
     78        atom!("wbr"),
     79        atom!("meter"),
     80        atom!("progress"),
     81        atom!("canvas"),
     82        atom!("embed"),
     83        atom!("object"),
     84        atom!("audio"),
     85        atom!("iframe"),
     86        atom!("img"),
     87        atom!("video"),
     88        atom!("frame"),
     89        atom!("frameset"),
     90        atom!("input"),
     91        atom!("textarea"),
     92        atom!("select"),
     93    ];
     94 
     95    // https://drafts.csswg.org/css-display/#unbox-svg
     96    //
     97    // There's a note about "Unknown elements", but there's not a good way to
     98    // know what that means, or to get that information from here, and no other
     99    // UA implements this either.
    100    const SPECIAL_SVG_ELEMENTS: [Atom; 6] = [
    101        atom!("svg"),
    102        atom!("a"),
    103        atom!("g"),
    104        atom!("use"),
    105        atom!("tspan"),
    106        atom!("textPath"),
    107    ];
    108 
    109    // https://drafts.csswg.org/css-display/#unbox-html
    110    if element.is_html_element() {
    111        let local_name = element.local_name();
    112        return SPECIAL_HTML_ELEMENTS
    113            .iter()
    114            .any(|name| &**name == local_name);
    115    }
    116 
    117    // https://drafts.csswg.org/css-display/#unbox-svg
    118    if element.is_svg_element() {
    119        if is_topmost_svg_svg_element(element) {
    120            return true;
    121        }
    122        let local_name = element.local_name();
    123        return !SPECIAL_SVG_ELEMENTS
    124            .iter()
    125            .any(|name| &**name == local_name);
    126    }
    127 
    128    // https://drafts.csswg.org/css-display/#unbox-mathml
    129    if element.is_mathml_element() {
    130        return true;
    131    }
    132 
    133    false
    134 }
    135 
    136 impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
    137    /// Trivially constructs a new StyleAdjuster.
    138    #[inline]
    139    pub fn new(style: &'a mut StyleBuilder<'b>) -> Self {
    140        StyleAdjuster { style }
    141    }
    142 
    143    /// <https://fullscreen.spec.whatwg.org/#new-stacking-layer>
    144    ///
    145    ///    Any position value other than 'absolute' and 'fixed' are
    146    ///    computed to 'absolute' if the element is in a top layer.
    147    ///
    148    fn adjust_for_top_layer(&mut self) {
    149        if !self.style.in_top_layer() {
    150            return;
    151        }
    152        if !self.style.is_absolutely_positioned() {
    153            self.style.mutate_box().set_position(Position::Absolute);
    154        }
    155        if self.style.get_box().clone_display().is_contents() {
    156            self.style.mutate_box().set_display(Display::Block);
    157        }
    158    }
    159 
    160    /// -webkit-box with line-clamp and vertical orientation gets turned into
    161    /// flow-root at computed-value time.
    162    ///
    163    /// This makes the element not be a flex container, with all that it
    164    /// implies, but it should be safe. It matches blink, see
    165    /// https://bugzilla.mozilla.org/show_bug.cgi?id=1786147#c10
    166    #[cfg(feature = "gecko")]
    167    fn adjust_for_webkit_line_clamp(&mut self) {
    168        use crate::properties::longhands::_moz_box_orient::computed_value::T as BoxOrient;
    169        use crate::values::specified::box_::{DisplayInside, DisplayOutside};
    170        let box_style = self.style.get_box();
    171        if box_style.clone__webkit_line_clamp().is_none() {
    172            return;
    173        }
    174        let disp = box_style.clone_display();
    175        if disp.inside() != DisplayInside::WebkitBox {
    176            return;
    177        }
    178        if self.style.get_xul().clone__moz_box_orient() != BoxOrient::Vertical {
    179            return;
    180        }
    181        let new_display = if disp.outside() == DisplayOutside::Block {
    182            Display::FlowRoot
    183        } else {
    184            debug_assert_eq!(disp.outside(), DisplayOutside::Inline);
    185            Display::InlineBlock
    186        };
    187        self.style
    188            .mutate_box()
    189            .set_adjusted_display(new_display, false);
    190    }
    191 
    192    /// CSS 2.1 section 9.7:
    193    ///
    194    ///    If 'position' has the value 'absolute' or 'fixed', [...] the computed
    195    ///    value of 'float' is 'none'.
    196    ///
    197    fn adjust_for_position(&mut self) {
    198        if self.style.is_absolutely_positioned() && self.style.is_floating() {
    199            self.style.mutate_box().set_float(Float::None);
    200        }
    201    }
    202 
    203    /// Whether we should skip any item-based display property blockification on
    204    /// this element.
    205    fn skip_item_display_fixup<E>(&self, element: Option<E>) -> bool
    206    where
    207        E: TElement,
    208    {
    209        if let Some(pseudo) = self.style.pseudo {
    210            return pseudo.skip_item_display_fixup();
    211        }
    212 
    213        element.is_some_and(|e| e.skip_item_display_fixup())
    214    }
    215 
    216    /// Apply the blockification rules based on the table in CSS 2.2 section 9.7.
    217    /// <https://drafts.csswg.org/css2/visuren.html#dis-pos-flo>
    218    /// A ::marker pseudo-element with 'list-style-position:outside' needs to
    219    /// have its 'display' blockified, unless the ::marker is for an inline
    220    /// list-item (for which 'list-style-position:outside' behaves as 'inside').
    221    /// https://drafts.csswg.org/css-lists-3/#list-style-position-property
    222    fn blockify_if_necessary<E>(&mut self, layout_parent_style: &ComputedValues, element: Option<E>)
    223    where
    224        E: TElement,
    225    {
    226        let mut blockify = false;
    227        macro_rules! blockify_if {
    228            ($if_what:expr) => {
    229                if !blockify {
    230                    blockify = $if_what;
    231                }
    232            };
    233        }
    234 
    235        blockify_if!(self.style.is_root_element);
    236        if !self.skip_item_display_fixup(element) {
    237            let parent_display = layout_parent_style.get_box().clone_display();
    238            blockify_if!(parent_display.is_item_container());
    239        }
    240 
    241        let is_item_or_root = blockify;
    242 
    243        blockify_if!(self.style.is_floating());
    244        blockify_if!(self.style.is_absolutely_positioned());
    245 
    246        if !blockify {
    247            return;
    248        }
    249 
    250        let display = self.style.get_box().clone_display();
    251        let blockified_display = display.equivalent_block_display(self.style.is_root_element);
    252        if display != blockified_display {
    253            self.style
    254                .mutate_box()
    255                .set_adjusted_display(blockified_display, is_item_or_root);
    256        }
    257    }
    258 
    259    /// Compute a few common flags for both text and element's style.
    260    fn set_bits(&mut self) {
    261        let box_style = self.style.get_box();
    262        let display = box_style.clone_display();
    263 
    264        if !display.is_contents() {
    265            if !self
    266                .style
    267                .get_text()
    268                .clone_text_decoration_line()
    269                .is_empty()
    270            {
    271                self.style
    272                    .add_flags(ComputedValueFlags::HAS_TEXT_DECORATION_LINES);
    273            }
    274 
    275            if self.style.get_effects().clone_opacity() == 0. {
    276                self.style
    277                    .add_flags(ComputedValueFlags::IS_IN_OPACITY_ZERO_SUBTREE);
    278            }
    279        } else if self
    280            .style
    281            .get_parent_box()
    282            .clone_display()
    283            .is_item_container()
    284            || self
    285                .style
    286                .get_parent_flags()
    287                .contains(ComputedValueFlags::DIPLAY_CONTENTS_IN_ITEM_CONTAINER)
    288        {
    289            self.style
    290                .add_flags(ComputedValueFlags::DIPLAY_CONTENTS_IN_ITEM_CONTAINER);
    291        }
    292 
    293        if self.style.pseudo.is_some_and(|p| p.is_first_line()) {
    294            self.style
    295                .add_flags(ComputedValueFlags::IS_IN_FIRST_LINE_SUBTREE);
    296        }
    297 
    298        if self.style.is_root_element {
    299            self.style
    300                .add_flags(ComputedValueFlags::IS_ROOT_ELEMENT_STYLE);
    301        }
    302 
    303        #[cfg(feature = "gecko")]
    304        if box_style
    305            .clone_effective_containment()
    306            .contains(Contain::STYLE)
    307        {
    308            self.style
    309                .add_flags(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_CONTAIN_STYLE);
    310        }
    311 
    312        if box_style.clone_container_type().is_size_container_type() {
    313            self.style
    314                .add_flags(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE);
    315        }
    316 
    317        #[cfg(feature = "servo")]
    318        if self.style.get_parent_column().is_multicol() {
    319            self.style.add_flags(ComputedValueFlags::CAN_BE_FRAGMENTED);
    320        }
    321    }
    322 
    323    /// Adjust the style for text style.
    324    ///
    325    /// The adjustments here are a subset of the adjustments generally, because
    326    /// text only inherits properties.
    327    ///
    328    /// Note that this, for Gecko, comes through Servo_ComputedValues_Inherit.
    329    #[cfg(feature = "gecko")]
    330    pub fn adjust_for_text(&mut self) {
    331        debug_assert!(!self.style.is_root_element);
    332        self.adjust_for_text_combine_upright();
    333        self.adjust_for_text_in_ruby();
    334        self.set_bits();
    335    }
    336 
    337    /// Change writing mode of the text frame for text-combine-upright.
    338    ///
    339    /// It is safe to look at our own style because we are looking at inherited
    340    /// properties, and text is just plain inheritance.
    341    ///
    342    /// TODO(emilio): we should (Gecko too) revise these adjustments in presence
    343    /// of display: contents.
    344    ///
    345    /// FIXME(emilio): How does this play with logical properties? Doesn't
    346    /// mutating writing-mode change the potential physical sides chosen?
    347    #[cfg(feature = "gecko")]
    348    fn adjust_for_text_combine_upright(&mut self) {
    349        use crate::computed_values::text_combine_upright::T as TextCombineUpright;
    350        use crate::computed_values::writing_mode::T as WritingMode;
    351        use crate::logical_geometry;
    352 
    353        let writing_mode = self.style.get_inherited_box().clone_writing_mode();
    354        let text_combine_upright = self.style.get_inherited_text().clone_text_combine_upright();
    355 
    356        if matches!(
    357            writing_mode,
    358            WritingMode::VerticalRl | WritingMode::VerticalLr
    359        ) && text_combine_upright == TextCombineUpright::All
    360        {
    361            self.style.add_flags(ComputedValueFlags::IS_TEXT_COMBINED);
    362            self.style
    363                .mutate_inherited_box()
    364                .set_writing_mode(WritingMode::HorizontalTb);
    365            self.style.writing_mode =
    366                logical_geometry::WritingMode::new(self.style.get_inherited_box());
    367        }
    368    }
    369 
    370    /// Unconditionally propagates the line break suppression flag to text, and
    371    /// additionally it applies it if it is in any ruby box.
    372    ///
    373    /// This is necessary because its parent may not itself have the flag set
    374    /// (e.g. ruby or ruby containers), thus we may not inherit the flag from
    375    /// them.
    376    #[cfg(feature = "gecko")]
    377    fn adjust_for_text_in_ruby(&mut self) {
    378        let parent_display = self.style.get_parent_box().clone_display();
    379        if parent_display.is_ruby_type()
    380            || self
    381                .style
    382                .get_parent_flags()
    383                .contains(ComputedValueFlags::SHOULD_SUPPRESS_LINEBREAK)
    384        {
    385            self.style
    386                .add_flags(ComputedValueFlags::SHOULD_SUPPRESS_LINEBREAK);
    387        }
    388    }
    389 
    390    /// <https://drafts.csswg.org/css-writing-modes-3/#block-flow:>
    391    ///
    392    ///    If a box has a different writing-mode value than its containing
    393    ///    block:
    394    ///
    395    ///        - If the box has a specified display of inline, its display
    396    ///          computes to inline-block. [CSS21]
    397    ///
    398    /// This matches the adjustment that Gecko does, not exactly following
    399    /// the spec. See also:
    400    ///
    401    /// <https://lists.w3.org/Archives/Public/www-style/2017Mar/0045.html>
    402    /// <https://github.com/servo/servo/issues/15754>
    403    fn adjust_for_writing_mode(&mut self, layout_parent_style: &ComputedValues) {
    404        let our_writing_mode = self.style.get_inherited_box().clone_writing_mode();
    405        let parent_writing_mode = layout_parent_style.get_inherited_box().clone_writing_mode();
    406 
    407        if our_writing_mode != parent_writing_mode
    408            && self.style.get_box().clone_display() == Display::Inline
    409        {
    410            // TODO(emilio): Figure out if we can just set the adjusted display
    411            // on Gecko too and unify this code path.
    412            if cfg!(feature = "servo") {
    413                self.style
    414                    .mutate_box()
    415                    .set_adjusted_display(Display::InlineBlock, false);
    416            } else {
    417                self.style.mutate_box().set_display(Display::InlineBlock);
    418            }
    419        }
    420    }
    421 
    422    /// CSS overflow-x and overflow-y require some fixup as well in some cases.
    423    /// https://drafts.csswg.org/css-overflow-3/#overflow-properties
    424    /// "Computed value: as specified, except with `visible`/`clip` computing to
    425    /// `auto`/`hidden` (respectively) if one of `overflow-x` or `overflow-y` is
    426    /// neither `visible` nor `clip`."
    427    fn adjust_for_overflow(&mut self) {
    428        let overflow_x = self.style.get_box().clone_overflow_x();
    429        let overflow_y = self.style.get_box().clone_overflow_y();
    430        if overflow_x == overflow_y {
    431            return; // optimization for the common case
    432        }
    433 
    434        if overflow_x.is_scrollable() != overflow_y.is_scrollable() {
    435            let box_style = self.style.mutate_box();
    436            box_style.set_overflow_x(overflow_x.to_scrollable());
    437            box_style.set_overflow_y(overflow_y.to_scrollable());
    438        }
    439    }
    440 
    441    #[cfg(feature = "gecko")]
    442    fn adjust_for_contain(&mut self) {
    443        let box_style = self.style.get_box();
    444        let container_type = box_style.clone_container_type();
    445        let content_visibility = box_style.clone_content_visibility();
    446        if !container_type.is_size_container_type()
    447            && content_visibility == ContentVisibility::Visible
    448        {
    449            debug_assert_eq!(
    450                box_style.clone_contain(),
    451                box_style.clone_effective_containment()
    452            );
    453            return;
    454        }
    455        let old_contain = box_style.clone_contain();
    456        let mut new_contain = old_contain;
    457        match content_visibility {
    458            ContentVisibility::Visible => {},
    459            // `content-visibility:auto` also applies size containment when content
    460            // is not relevant (and therefore skipped). This is checked in
    461            // nsIFrame::GetContainSizeAxes.
    462            ContentVisibility::Auto => {
    463                new_contain.insert(Contain::LAYOUT | Contain::PAINT | Contain::STYLE)
    464            },
    465            ContentVisibility::Hidden => new_contain
    466                .insert(Contain::LAYOUT | Contain::PAINT | Contain::SIZE | Contain::STYLE),
    467        }
    468        if container_type.intersects(ContainerType::INLINE_SIZE) {
    469            // https://drafts.csswg.org/css-contain-3/#valdef-container-type-inline-size:
    470            //     Applies layout containment, style containment, and inline-size
    471            //     containment to the principal box.
    472            new_contain.insert(Contain::STYLE | Contain::INLINE_SIZE);
    473        } else if container_type.intersects(ContainerType::SIZE) {
    474            // https://drafts.csswg.org/css-contain-3/#valdef-container-type-size:
    475            //     Applies layout containment, style containment, and size
    476            //     containment to the principal box.
    477            new_contain.insert(Contain::STYLE | Contain::SIZE);
    478        }
    479        if new_contain == old_contain {
    480            debug_assert_eq!(
    481                box_style.clone_contain(),
    482                box_style.clone_effective_containment()
    483            );
    484            return;
    485        }
    486        self.style
    487            .mutate_box()
    488            .set_effective_containment(new_contain);
    489    }
    490 
    491    /// content-visibility: auto should force contain-intrinsic-size to gain
    492    /// an auto value
    493    ///
    494    /// <https://github.com/w3c/csswg-drafts/issues/8407>
    495    #[cfg(feature = "gecko")]
    496    fn adjust_for_contain_intrinsic_size(&mut self) {
    497        let content_visibility = self.style.get_box().clone_content_visibility();
    498        if content_visibility != ContentVisibility::Auto {
    499            return;
    500        }
    501 
    502        let pos = self.style.get_position();
    503        let new_width = pos.clone_contain_intrinsic_width().add_auto_if_needed();
    504        let new_height = pos.clone_contain_intrinsic_height().add_auto_if_needed();
    505        if new_width.is_none() && new_height.is_none() {
    506            return;
    507        }
    508 
    509        let pos = self.style.mutate_position();
    510        if let Some(width) = new_width {
    511            pos.set_contain_intrinsic_width(width);
    512        }
    513        if let Some(height) = new_height {
    514            pos.set_contain_intrinsic_height(height);
    515        }
    516    }
    517 
    518    /// Handles the relevant sections in:
    519    ///
    520    /// https://drafts.csswg.org/css-display/#unbox-html
    521    ///
    522    /// And forbidding display: contents in pseudo-elements, at least for now.
    523    #[cfg(feature = "gecko")]
    524    fn adjust_for_prohibited_display_contents<E>(&mut self, element: Option<E>)
    525    where
    526        E: TElement,
    527    {
    528        if self.style.get_box().clone_display() != Display::Contents {
    529            return;
    530        }
    531 
    532        // FIXME(emilio): ::before and ::after should support display: contents, see bug 1418138.
    533        if self.style.pseudo.is_some_and(|p| !p.is_element_backed()) {
    534            self.style.mutate_box().set_display(Display::Inline);
    535            return;
    536        }
    537 
    538        let element = match element {
    539            Some(e) => e,
    540            None => return,
    541        };
    542 
    543        if is_effective_display_none_for_display_contents(element) {
    544            self.style.mutate_box().set_display(Display::None);
    545        }
    546    }
    547 
    548    /// <textarea>'s editor root needs to inherit the overflow value from its
    549    /// parent, but we need to make sure it's still scrollable.
    550    #[cfg(feature = "gecko")]
    551    fn adjust_for_text_control_editing_root(&mut self) {
    552        use crate::properties::longhands::white_space_collapse::computed_value::T as WhiteSpaceCollapse;
    553        use crate::selector_parser::PseudoElement;
    554 
    555        if self.style.pseudo != Some(&PseudoElement::MozTextControlEditingRoot) {
    556            return;
    557        }
    558 
    559        let old_collapse = self.style.get_inherited_text().clone_white_space_collapse();
    560        let new_collapse = match old_collapse {
    561            WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces => old_collapse,
    562            WhiteSpaceCollapse::Collapse
    563            | WhiteSpaceCollapse::PreserveSpaces
    564            | WhiteSpaceCollapse::PreserveBreaks => WhiteSpaceCollapse::Preserve,
    565        };
    566        if new_collapse != old_collapse {
    567            self.style
    568                .mutate_inherited_text()
    569                .set_white_space_collapse(new_collapse);
    570        }
    571 
    572        let box_style = self.style.get_box();
    573        let overflow_x = box_style.clone_overflow_x();
    574        let overflow_y = box_style.clone_overflow_y();
    575 
    576        // If at least one is scrollable we'll adjust the other one in
    577        // adjust_for_overflow if needed.
    578        if overflow_x.is_scrollable() || overflow_y.is_scrollable() {
    579            return;
    580        }
    581 
    582        let box_style = self.style.mutate_box();
    583        box_style.set_overflow_x(Overflow::Auto);
    584        box_style.set_overflow_y(Overflow::Auto);
    585    }
    586 
    587    /// If a <fieldset> has grid/flex display type, we need to inherit
    588    /// this type into its ::-moz-fieldset-content anonymous box.
    589    #[cfg(feature = "gecko")]
    590    fn adjust_for_fieldset_content(&mut self) {
    591        use crate::selector_parser::PseudoElement;
    592        if self.style.pseudo != Some(&PseudoElement::FieldsetContent) {
    593            return;
    594        }
    595        let parent_display = self.style.get_parent_box().clone_display();
    596        debug_assert!(
    597            !parent_display.is_contents(),
    598            "How did we create a fieldset-content box with display: contents?"
    599        );
    600        let new_display = match parent_display {
    601            Display::Flex | Display::InlineFlex => Some(Display::Flex),
    602            Display::Grid | Display::InlineGrid => Some(Display::Grid),
    603            _ => None,
    604        };
    605        if let Some(new_display) = new_display {
    606            self.style.mutate_box().set_display(new_display);
    607        }
    608    }
    609 
    610    /// -moz-center, -moz-left and -moz-right are used for HTML's alignment.
    611    ///
    612    /// This is covering the <div align="right"><table>...</table></div> case.
    613    ///
    614    /// In this case, we don't want to inherit the text alignment into the
    615    /// table.
    616    fn adjust_for_table_text_align(&mut self) {
    617        use crate::properties::longhands::text_align::computed_value::T as TextAlign;
    618        if self.style.get_box().clone_display() != Display::Table {
    619            return;
    620        }
    621 
    622        match self.style.get_inherited_text().clone_text_align() {
    623            TextAlign::MozLeft | TextAlign::MozCenter | TextAlign::MozRight => {},
    624            _ => return,
    625        }
    626 
    627        self.style
    628            .mutate_inherited_text()
    629            .set_text_align(TextAlign::Start)
    630    }
    631 
    632    #[cfg(feature = "gecko")]
    633    fn should_suppress_linebreak<E>(&self, element: Option<E>) -> bool
    634    where
    635        E: TElement,
    636    {
    637        // Line break suppression should only be propagated to in-flow children.
    638        if self.style.is_floating() || self.style.is_absolutely_positioned() {
    639            return false;
    640        }
    641        let parent_display = self.style.get_parent_box().clone_display();
    642        if self
    643            .style
    644            .get_parent_flags()
    645            .contains(ComputedValueFlags::SHOULD_SUPPRESS_LINEBREAK)
    646        {
    647            // Line break suppression is propagated to any children of
    648            // line participants, and across display: contents boundaries.
    649            if parent_display.is_line_participant() || parent_display.is_contents() {
    650                return true;
    651            }
    652        }
    653        match self.style.get_box().clone_display() {
    654            // Ruby base and text are always non-breakable.
    655            Display::RubyBase | Display::RubyText => true,
    656            // Ruby base container and text container are breakable.
    657            // Non-HTML elements may not form ruby base / text container because
    658            // they may not respect ruby-internal display values, so we can't
    659            // make them escaped from line break suppression.
    660            // Note that, when certain HTML tags, e.g. form controls, have ruby
    661            // level container display type, they could also escape from the
    662            // line break suppression flag while they shouldn't. However, it is
    663            // generally fine as far as they can't break the line inside them.
    664            Display::RubyBaseContainer | Display::RubyTextContainer
    665                if element.map_or(true, |e| e.is_html_element()) =>
    666            {
    667                false
    668            },
    669            // Anything else is non-breakable if and only if its layout parent
    670            // has a ruby display type, because any of the ruby boxes can be
    671            // anonymous.
    672            _ => parent_display.is_ruby_type(),
    673        }
    674    }
    675 
    676    /// Do ruby-related style adjustments, which include:
    677    /// * propagate the line break suppression flag,
    678    /// * inlinify block descendants,
    679    /// * suppress border and padding for ruby level containers,
    680    /// * correct unicode-bidi.
    681    #[cfg(feature = "gecko")]
    682    fn adjust_for_ruby<E>(&mut self, element: Option<E>)
    683    where
    684        E: TElement,
    685    {
    686        use crate::properties::longhands::unicode_bidi::computed_value::T as UnicodeBidi;
    687 
    688        let self_display = self.style.get_box().clone_display();
    689        // Check whether line break should be suppressed for this element.
    690        if self.should_suppress_linebreak(element) {
    691            self.style
    692                .add_flags(ComputedValueFlags::SHOULD_SUPPRESS_LINEBREAK);
    693            // Inlinify the display type if allowed.
    694            if !self.skip_item_display_fixup(element) {
    695                let inline_display = self_display.inlinify();
    696                if self_display != inline_display {
    697                    self.style
    698                        .mutate_box()
    699                        .set_adjusted_display(inline_display, false);
    700                }
    701            }
    702        }
    703        // Suppress border and padding for ruby level containers.
    704        // This is actually not part of the spec. It is currently unspecified
    705        // how border and padding should be handled for ruby level container,
    706        // and suppressing them here make it easier for layout to handle.
    707        if self_display.is_ruby_level_container() {
    708            self.style.reset_border_struct();
    709            self.style.reset_padding_struct();
    710        }
    711 
    712        // Force bidi isolation on all internal ruby boxes and ruby container
    713        // per spec https://drafts.csswg.org/css-ruby-1/#bidi
    714        if self_display.is_ruby_type() {
    715            let new_value = match self.style.get_text().clone_unicode_bidi() {
    716                UnicodeBidi::Normal | UnicodeBidi::Embed => Some(UnicodeBidi::Isolate),
    717                UnicodeBidi::BidiOverride => Some(UnicodeBidi::IsolateOverride),
    718                _ => None,
    719            };
    720            if let Some(new_value) = new_value {
    721                self.style.mutate_text().set_unicode_bidi(new_value);
    722            }
    723        }
    724    }
    725 
    726    /// Computes the RELEVANT_LINK_VISITED flag based on the parent style and on
    727    /// whether we're a relevant link.
    728    ///
    729    /// NOTE(emilio): We don't do this for text styles, which is... dubious, but
    730    /// Gecko doesn't seem to do it either. It's extremely easy to do if needed
    731    /// though.
    732    ///
    733    /// FIXME(emilio): This isn't technically a style adjustment thingie, could
    734    /// it move somewhere else?
    735    fn adjust_for_visited<E>(&mut self, element: Option<E>)
    736    where
    737        E: TElement,
    738    {
    739        if !self.style.has_visited_style() {
    740            return;
    741        }
    742 
    743        let is_link_element = self.style.pseudo.is_none() && element.map_or(false, |e| e.is_link());
    744 
    745        if !is_link_element {
    746            return;
    747        }
    748 
    749        if element.unwrap().is_visited_link() {
    750            self.style
    751                .add_flags(ComputedValueFlags::IS_RELEVANT_LINK_VISITED);
    752        } else {
    753            // Need to remove to handle unvisited link inside visited.
    754            self.style
    755                .remove_flags(ComputedValueFlags::IS_RELEVANT_LINK_VISITED);
    756        }
    757    }
    758 
    759    /// Resolves "justify-items: legacy" based on the inherited style if needed
    760    /// to comply with:
    761    ///
    762    /// <https://drafts.csswg.org/css-align/#valdef-justify-items-legacy>
    763    #[cfg(feature = "gecko")]
    764    fn adjust_for_justify_items(&mut self) {
    765        use crate::values::specified::align;
    766        let justify_items = self.style.get_position().clone_justify_items();
    767        if justify_items.specified != align::JustifyItems::legacy() {
    768            return;
    769        }
    770 
    771        let parent_justify_items = self.style.get_parent_position().clone_justify_items();
    772 
    773        if !parent_justify_items.computed.contains(AlignFlags::LEGACY) {
    774            return;
    775        }
    776 
    777        if parent_justify_items.computed == justify_items.computed {
    778            return;
    779        }
    780 
    781        self.style
    782            .mutate_position()
    783            .set_computed_justify_items(parent_justify_items.computed);
    784    }
    785 
    786    /// If '-webkit-appearance' is 'menulist' on a <select> element then
    787    /// the computed value of 'line-height' is 'normal'.
    788    ///
    789    /// https://github.com/w3c/csswg-drafts/issues/3257
    790    #[cfg(feature = "gecko")]
    791    fn adjust_for_appearance<E>(&mut self, element: Option<E>)
    792    where
    793        E: TElement,
    794    {
    795        use crate::properties::longhands::appearance::computed_value::T as Appearance;
    796        use crate::properties::longhands::line_height::computed_value::T as LineHeight;
    797 
    798        let box_ = self.style.get_box();
    799        let appearance = match box_.clone_appearance() {
    800            Appearance::Auto => box_.clone__moz_default_appearance(),
    801            a => a,
    802        };
    803 
    804        if appearance == Appearance::Menulist {
    805            if self.style.get_font().clone_line_height() == LineHeight::normal() {
    806                return;
    807            }
    808            if self.style.pseudo.is_some() {
    809                return;
    810            }
    811            let is_html_select_element = element.map_or(false, |e| {
    812                e.is_html_element() && e.local_name() == &*atom!("select")
    813            });
    814            if !is_html_select_element {
    815                return;
    816            }
    817            self.style
    818                .mutate_font()
    819                .set_line_height(LineHeight::normal());
    820        }
    821    }
    822 
    823    /// A legacy ::marker (i.e. no 'content') without an author-specified 'font-family'
    824    /// and 'list-style-type:disc|circle|square|disclosure-closed|disclosure-open'
    825    /// is assigned 'font-family:-moz-bullet-font'. (This is for <ul><li> etc.)
    826    /// We don't want synthesized italic/bold for this font, so turn that off too.
    827    /// Likewise for 'letter/word-spacing' -- unless the author specified it then reset
    828    /// them to their initial value because traditionally we never added such spacing
    829    /// between a legacy bullet and the list item's content, so we keep that behavior
    830    /// for web-compat reasons.
    831    /// We intentionally don't check 'list-style-image' below since we want it to use
    832    /// the same font as its fallback ('list-style-type') in case it fails to load.
    833    #[cfg(feature = "gecko")]
    834    fn adjust_for_marker_pseudo(&mut self) {
    835        use crate::values::computed::counters::Content;
    836        use crate::values::computed::font::{FontFamily, FontSynthesis, FontSynthesisStyle};
    837        use crate::values::computed::text::{LetterSpacing, WordSpacing};
    838 
    839        let is_legacy_marker = self.style.pseudo.map_or(false, |p| p.is_marker())
    840            && self.style.get_list().clone_list_style_type().is_bullet()
    841            && self.style.get_counters().clone_content() == Content::Normal;
    842        if !is_legacy_marker {
    843            return;
    844        }
    845        let flags = self.style.flags.get();
    846        if !flags.contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_FAMILY) {
    847            self.style
    848                .mutate_font()
    849                .set_font_family(FontFamily::moz_bullet().clone());
    850 
    851            // FIXME(mats): We can remove this if support for font-synthesis is added to @font-face rules.
    852            // Then we can add it to the @font-face rule in html.css instead.
    853            // https://github.com/w3c/csswg-drafts/issues/6081
    854            if !flags.contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_WEIGHT) {
    855                self.style
    856                    .mutate_font()
    857                    .set_font_synthesis_weight(FontSynthesis::None);
    858            }
    859            if !flags.contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_STYLE) {
    860                self.style
    861                    .mutate_font()
    862                    .set_font_synthesis_style(FontSynthesisStyle::None);
    863            }
    864        }
    865        if !flags.contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_LETTER_SPACING) {
    866            self.style
    867                .mutate_inherited_text()
    868                .set_letter_spacing(LetterSpacing::normal());
    869        }
    870        if !flags.contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_WORD_SPACING) {
    871            self.style
    872                .mutate_inherited_text()
    873                .set_word_spacing(WordSpacing::normal());
    874        }
    875    }
    876 
    877    /// Performs adjustments for position-try-fallbacks. The properties that need adjustments here
    878    /// are luckily not affected by previous adjustments nor by other computed-value-time effects,
    879    /// so we can just perform them here.
    880    ///
    881    /// NOTE(emilio): If we ever perform the interleaving dance, this could / should probably move
    882    /// around to the specific properties' to_computed_value implementations, but that seems
    883    /// overkill for now.
    884    fn adjust_for_try_tactic(&mut self, tactic: &PositionTryFallbacksTryTactic) {
    885        debug_assert!(!tactic.is_empty());
    886        // TODO: This is supposed to use the containing block's WM (bug 1995256).
    887        let wm = self.style.writing_mode;
    888        // TODO: Flip inset / margin / sizes percentages and anchor lookup sides as necessary.
    889        for tactic in tactic.iter() {
    890            use PositionTryFallbacksTryTacticKeyword::*;
    891            match tactic {
    892                FlipBlock => {
    893                    self.flip_self_alignment(/* block = */ true);
    894                    self.flip_insets_and_margins(/* horizontal = */ wm.is_vertical());
    895                },
    896                FlipInline => {
    897                    self.flip_self_alignment(/* block = */ false);
    898                    self.flip_insets_and_margins(/* horizontal = */ wm.is_horizontal());
    899                },
    900                FlipX => {
    901                    self.flip_self_alignment(/* block = */ wm.is_vertical());
    902                    self.flip_insets_and_margins(/* horizontal = */ true);
    903                },
    904                FlipY => {
    905                    self.flip_self_alignment(/* block = */ wm.is_horizontal());
    906                    self.flip_insets_and_margins(/* horizontal = */ false);
    907                },
    908                FlipStart => {
    909                    self.flip_start();
    910                },
    911            }
    912            self.apply_position_area_tactic(*tactic);
    913        }
    914    }
    915 
    916    fn apply_position_area_tactic(&mut self, tactic: PositionTryFallbacksTryTacticKeyword) {
    917        let pos = self.style.get_position();
    918        let old = pos.clone_position_area();
    919        let wm = self.style.writing_mode;
    920        let new = old.with_tactic(wm, tactic);
    921        if new == old {
    922            return;
    923        }
    924        let pos = self.style.mutate_position();
    925        pos.set_position_area(new);
    926    }
    927 
    928    // TODO: Could avoid some clones here and below.
    929    fn swap_insets(&mut self, a_side: PhysicalSide, b_side: PhysicalSide) {
    930        debug_assert_ne!(a_side, b_side);
    931        let pos = self.style.mutate_position();
    932        let mut a = pos.get_inset(a_side).clone();
    933        a.try_tactic_adjustment(a_side, b_side);
    934        let mut b = pos.get_inset(b_side).clone();
    935        b.try_tactic_adjustment(b_side, a_side);
    936        pos.set_inset(a_side, b);
    937        pos.set_inset(b_side, a);
    938    }
    939 
    940    fn swap_margins(&mut self, a_side: PhysicalSide, b_side: PhysicalSide) {
    941        debug_assert_ne!(a_side, b_side);
    942        let margin = self.style.get_margin();
    943        let mut a = margin.get_margin(a_side).clone();
    944        a.try_tactic_adjustment(a_side, b_side);
    945        let mut b = margin.get_margin(b_side).clone();
    946        b.try_tactic_adjustment(b_side, a_side);
    947        let margin = self.style.mutate_margin();
    948        margin.set_margin(a_side, b);
    949        margin.set_margin(b_side, a);
    950    }
    951 
    952    fn swap_sizes(&mut self, block_start: PhysicalSide, inline_start: PhysicalSide) {
    953        let pos = self.style.mutate_position();
    954        let mut min_width = pos.clone_min_width();
    955        min_width.try_tactic_adjustment(inline_start, block_start);
    956        let mut max_width = pos.clone_max_width();
    957        max_width.try_tactic_adjustment(inline_start, block_start);
    958        let mut width = pos.clone_width();
    959        width.try_tactic_adjustment(inline_start, block_start);
    960 
    961        let mut min_height = pos.clone_min_height();
    962        min_height.try_tactic_adjustment(block_start, inline_start);
    963        let mut max_height = pos.clone_max_height();
    964        max_height.try_tactic_adjustment(block_start, inline_start);
    965        let mut height = pos.clone_height();
    966        height.try_tactic_adjustment(block_start, inline_start);
    967 
    968        let pos = self.style.mutate_position();
    969        pos.set_width(height);
    970        pos.set_height(width);
    971        pos.set_max_width(max_height);
    972        pos.set_max_height(max_width);
    973        pos.set_min_width(min_height);
    974        pos.set_min_height(min_width);
    975    }
    976 
    977    fn flip_start(&mut self) {
    978        let wm = self.style.writing_mode;
    979        let bs = wm.block_start_physical_side();
    980        let is = wm.inline_start_physical_side();
    981        let be = wm.block_end_physical_side();
    982        let ie = wm.inline_end_physical_side();
    983        self.swap_sizes(bs, is);
    984        self.swap_insets(bs, is);
    985        self.swap_insets(ie, be);
    986        self.swap_margins(bs, is);
    987        self.swap_margins(ie, be);
    988        self.flip_alignment_start();
    989    }
    990 
    991    fn flip_insets_and_margins(&mut self, horizontal: bool) {
    992        if horizontal {
    993            self.swap_insets(PhysicalSide::Left, PhysicalSide::Right);
    994            self.swap_margins(PhysicalSide::Left, PhysicalSide::Right);
    995        } else {
    996            self.swap_insets(PhysicalSide::Top, PhysicalSide::Bottom);
    997            self.swap_margins(PhysicalSide::Top, PhysicalSide::Bottom);
    998        }
    999    }
   1000 
   1001    fn flip_alignment_start(&mut self) {
   1002        let pos = self.style.get_position();
   1003        let align = pos.clone_align_self();
   1004        let mut justify = pos.clone_justify_self();
   1005        if align == justify {
   1006            return;
   1007        }
   1008 
   1009        // Fix-up potential justify-self: {left, right} values which might end up as alignment
   1010        // values.
   1011        if matches!(justify.value(), AlignFlags::LEFT | AlignFlags::RIGHT) {
   1012            let left = justify.value() == AlignFlags::LEFT;
   1013            let ltr = self.style.writing_mode.is_bidi_ltr();
   1014            justify = justify.with_value(if left == ltr {
   1015                AlignFlags::SELF_START
   1016            } else {
   1017                AlignFlags::SELF_END
   1018            });
   1019        }
   1020 
   1021        let pos = self.style.mutate_position();
   1022        pos.set_align_self(justify);
   1023        pos.set_justify_self(align);
   1024    }
   1025 
   1026    fn flip_self_alignment(&mut self, block: bool) {
   1027        let pos = self.style.get_position();
   1028        let cur = if block {
   1029            pos.clone_align_self()
   1030        } else {
   1031            pos.clone_justify_self()
   1032        };
   1033        let flipped = cur.flip_position();
   1034        if flipped == cur {
   1035            return;
   1036        }
   1037        let pos = self.style.mutate_position();
   1038        if block {
   1039            pos.set_align_self(flipped);
   1040        } else {
   1041            pos.set_justify_self(flipped);
   1042        }
   1043    }
   1044 
   1045    /// Adjusts the style to account for various fixups that don't fit naturally into the cascade.
   1046    pub fn adjust<E>(
   1047        &mut self,
   1048        layout_parent_style: &ComputedValues,
   1049        element: Option<E>,
   1050        try_tactic: &PositionTryFallbacksTryTactic,
   1051    ) where
   1052        E: TElement,
   1053    {
   1054        if cfg!(debug_assertions) {
   1055            if let Some(e) = element {
   1056                if let Some(p) = e.implemented_pseudo_element() {
   1057                    // It'd be nice to assert `self.style.pseudo == Some(&pseudo)`,
   1058                    // but we do resolve ::-moz-list pseudos on ::before / ::after
   1059                    // content, sigh.
   1060                    debug_assert!(
   1061                        self.style.pseudo.is_some(),
   1062                        "Someone really messed up (no pseudo style for {e:?}, {p:?})"
   1063                    );
   1064                }
   1065            }
   1066        }
   1067        // FIXME(emilio): The apply_declarations callsite in Servo's
   1068        // animation, and the font stuff for Gecko
   1069        // (Stylist::compute_for_declarations) should pass an element to
   1070        // cascade(), then we can make this assertion hold everywhere.
   1071        // debug_assert!(
   1072        //     element.is_some() || self.style.pseudo.is_some(),
   1073        //     "Should always have an element around for non-pseudo styles"
   1074        // );
   1075 
   1076        self.adjust_for_visited(element);
   1077        #[cfg(feature = "gecko")]
   1078        {
   1079            self.adjust_for_prohibited_display_contents(element);
   1080            self.adjust_for_fieldset_content();
   1081            // NOTE: It's important that this happens before
   1082            // adjust_for_overflow.
   1083            self.adjust_for_text_control_editing_root();
   1084        }
   1085        self.adjust_for_top_layer();
   1086        self.blockify_if_necessary(layout_parent_style, element);
   1087        #[cfg(feature = "gecko")]
   1088        self.adjust_for_webkit_line_clamp();
   1089        self.adjust_for_position();
   1090        self.adjust_for_overflow();
   1091        #[cfg(feature = "gecko")]
   1092        {
   1093            self.adjust_for_contain();
   1094            self.adjust_for_contain_intrinsic_size();
   1095            self.adjust_for_justify_items();
   1096        }
   1097        self.adjust_for_table_text_align();
   1098        self.adjust_for_writing_mode(layout_parent_style);
   1099        #[cfg(feature = "gecko")]
   1100        {
   1101            self.adjust_for_ruby(element);
   1102            self.adjust_for_appearance(element);
   1103            self.adjust_for_marker_pseudo();
   1104        }
   1105        if !try_tactic.is_empty() {
   1106            self.adjust_for_try_tactic(try_tactic);
   1107        }
   1108        self.set_bits();
   1109    }
   1110 }