Bug 1758813 [wpt PR 33142] - Implement RP sign out, a=testonly
[gecko.git] / servo / components / style / style_adjuster.rs
blob98890ce09d599b9d328da0f4957b76958a0b813b
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/. */
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.
8 use crate::computed_value_flags::ComputedValueFlags;
9 use crate::dom::TElement;
10 use crate::properties::longhands::display::computed_value::T as Display;
11 use crate::properties::longhands::float::computed_value::T as Float;
12 use crate::properties::longhands::overflow_x::computed_value::T as Overflow;
13 use crate::properties::longhands::position::computed_value::T as Position;
14 use crate::properties::{self, ComputedValues, StyleBuilder};
16 /// A struct that implements all the adjustment methods.
17 ///
18 /// NOTE(emilio): If new adjustments are introduced that depend on reset
19 /// properties of the parent, you may need tweaking the
20 /// `ChildCascadeRequirement` code in `matching.rs`.
21 ///
22 /// NOTE(emilio): Also, if new adjustments are introduced that break the
23 /// following invariant:
24 ///
25 ///   Given same tag name, namespace, rules and parent style, two elements would
26 ///   end up with exactly the same style.
27 ///
28 /// Then you need to adjust the lookup_by_rules conditions in the sharing cache.
29 pub struct StyleAdjuster<'a, 'b: 'a> {
30     style: &'a mut StyleBuilder<'b>,
33 #[cfg(feature = "gecko")]
34 fn is_topmost_svg_svg_element<E>(e: E) -> bool
35 where
36     E: TElement,
38     debug_assert!(e.is_svg_element());
39     if e.local_name() != &*atom!("svg") {
40         return false;
41     }
43     let parent = match e.traversal_parent() {
44         Some(n) => n,
45         None => return true,
46     };
48     if !parent.is_svg_element() {
49         return true;
50     }
52     parent.local_name() == &*atom!("foreignObject")
55 // https://drafts.csswg.org/css-display/#unbox
56 #[cfg(feature = "gecko")]
57 fn is_effective_display_none_for_display_contents<E>(element: E) -> bool
58 where
59     E: TElement,
61     use crate::Atom;
63     const SPECIAL_HTML_ELEMENTS: [Atom; 16] = [
64         atom!("br"),
65         atom!("wbr"),
66         atom!("meter"),
67         atom!("progress"),
68         atom!("canvas"),
69         atom!("embed"),
70         atom!("object"),
71         atom!("audio"),
72         atom!("iframe"),
73         atom!("img"),
74         atom!("video"),
75         atom!("frame"),
76         atom!("frameset"),
77         atom!("input"),
78         atom!("textarea"),
79         atom!("select"),
80     ];
82     // https://drafts.csswg.org/css-display/#unbox-svg
83     //
84     // There's a note about "Unknown elements", but there's not a good way to
85     // know what that means, or to get that information from here, and no other
86     // UA implements this either.
87     const SPECIAL_SVG_ELEMENTS: [Atom; 6] = [
88         atom!("svg"),
89         atom!("a"),
90         atom!("g"),
91         atom!("use"),
92         atom!("tspan"),
93         atom!("textPath"),
94     ];
96     // https://drafts.csswg.org/css-display/#unbox-html
97     if element.is_html_element() {
98         let local_name = element.local_name();
99         return SPECIAL_HTML_ELEMENTS
100             .iter()
101             .any(|name| &**name == local_name);
102     }
104     // https://drafts.csswg.org/css-display/#unbox-svg
105     if element.is_svg_element() {
106         if is_topmost_svg_svg_element(element) {
107             return true;
108         }
109         let local_name = element.local_name();
110         return !SPECIAL_SVG_ELEMENTS
111             .iter()
112             .any(|name| &**name == local_name);
113     }
115     // https://drafts.csswg.org/css-display/#unbox-mathml
116     //
117     // We always treat XUL as display: none. We don't use display:
118     // contents in XUL anyway, so should be fine to be consistent with
119     // MathML unless there's a use case for it.
120     if element.is_mathml_element() || element.is_xul_element() {
121         return true;
122     }
124     false
127 impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
128     /// Trivially constructs a new StyleAdjuster.
129     #[inline]
130     pub fn new(style: &'a mut StyleBuilder<'b>) -> Self {
131         StyleAdjuster { style }
132     }
134     /// <https://fullscreen.spec.whatwg.org/#new-stacking-layer>
135     ///
136     ///    Any position value other than 'absolute' and 'fixed' are
137     ///    computed to 'absolute' if the element is in a top layer.
138     ///
139     fn adjust_for_top_layer(&mut self) {
140         if !self.style.in_top_layer() {
141             return;
142         }
143         if !self.style.is_absolutely_positioned() {
144             self.style.mutate_box().set_position(Position::Absolute);
145         }
146         if self.style.get_box().clone_display().is_contents() {
147             self.style.mutate_box().set_display(Display::Block);
148         }
149     }
151     /// CSS 2.1 section 9.7:
152     ///
153     ///    If 'position' has the value 'absolute' or 'fixed', [...] the computed
154     ///    value of 'float' is 'none'.
155     ///
156     fn adjust_for_position(&mut self) {
157         if self.style.is_absolutely_positioned() && self.style.is_floating() {
158             self.style.mutate_box().set_float(Float::None);
159         }
160     }
162     /// Whether we should skip any item-based display property blockification on
163     /// this element.
164     fn skip_item_display_fixup<E>(&self, element: Option<E>) -> bool
165     where
166         E: TElement,
167     {
168         if let Some(pseudo) = self.style.pseudo {
169             return pseudo.skip_item_display_fixup();
170         }
172         element.map_or(false, |e| e.skip_item_display_fixup())
173     }
175     /// Apply the blockification rules based on the table in CSS 2.2 section 9.7.
176     /// <https://drafts.csswg.org/css2/visuren.html#dis-pos-flo>
177     /// A ::marker pseudo-element with 'list-style-position:outside' needs to
178     /// have its 'display' blockified, unless the ::marker is for an inline
179     /// list-item (for which 'list-style-position:outside' behaves as 'inside').
180     /// https://drafts.csswg.org/css-lists-3/#list-style-position-property
181     fn blockify_if_necessary<E>(&mut self, layout_parent_style: &ComputedValues, element: Option<E>)
182     where
183         E: TElement,
184     {
185         let mut blockify = false;
186         macro_rules! blockify_if {
187             ($if_what:expr) => {
188                 if !blockify {
189                     blockify = $if_what;
190                 }
191             };
192         }
194         blockify_if!(self.style.is_root_element);
195         if !self.skip_item_display_fixup(element) {
196             let parent_display = layout_parent_style.get_box().clone_display();
197             blockify_if!(parent_display.is_item_container());
198         }
200         let is_item_or_root = blockify;
202         blockify_if!(self.style.is_floating());
203         blockify_if!(self.style.is_absolutely_positioned());
205         if !blockify {
206             return;
207         }
209         let display = self.style.get_box().clone_display();
210         let blockified_display = display.equivalent_block_display(self.style.is_root_element);
211         if display != blockified_display {
212             self.style
213                 .mutate_box()
214                 .set_adjusted_display(blockified_display, is_item_or_root);
215         }
216     }
218     /// Compute a few common flags for both text and element's style.
219     fn set_bits(&mut self) {
220         let display = self.style.get_box().clone_display();
222         if !display.is_contents() {
223             if !self
224                 .style
225                 .get_text()
226                 .clone_text_decoration_line()
227                 .is_empty()
228             {
229                 self.style
230                     .add_flags(ComputedValueFlags::HAS_TEXT_DECORATION_LINES);
231             }
233             if self.style.get_effects().clone_opacity() == 0. {
234                 self.style
235                     .add_flags(ComputedValueFlags::IS_IN_OPACITY_ZERO_SUBTREE);
236             }
237         }
239         if self.style.is_pseudo_element() {
240             self.style
241                 .add_flags(ComputedValueFlags::IS_IN_PSEUDO_ELEMENT_SUBTREE);
242         }
244         if self.style.is_root_element {
245             self.style
246                 .add_flags(ComputedValueFlags::IS_ROOT_ELEMENT_STYLE);
247         }
249         #[cfg(feature = "servo-layout-2013")]
250         {
251             if self.style.get_parent_column().is_multicol() {
252                 self.style.add_flags(ComputedValueFlags::CAN_BE_FRAGMENTED);
253             }
254         }
255     }
257     /// Adjust the style for text style.
258     ///
259     /// The adjustments here are a subset of the adjustments generally, because
260     /// text only inherits properties.
261     ///
262     /// Note that this, for Gecko, comes through Servo_ComputedValues_Inherit.
263     #[cfg(feature = "gecko")]
264     pub fn adjust_for_text(&mut self) {
265         debug_assert!(!self.style.is_root_element);
266         self.adjust_for_text_combine_upright();
267         self.adjust_for_text_in_ruby();
268         self.set_bits();
269     }
271     /// Change writing mode of the text frame for text-combine-upright.
272     ///
273     /// It is safe to look at our own style because we are looking at inherited
274     /// properties, and text is just plain inheritance.
275     ///
276     /// TODO(emilio): we should (Gecko too) revise these adjustments in presence
277     /// of display: contents.
278     ///
279     /// FIXME(emilio): How does this play with logical properties? Doesn't
280     /// mutating writing-mode change the potential physical sides chosen?
281     #[cfg(feature = "gecko")]
282     fn adjust_for_text_combine_upright(&mut self) {
283         use crate::computed_values::text_combine_upright::T as TextCombineUpright;
284         use crate::computed_values::writing_mode::T as WritingMode;
285         use crate::logical_geometry;
287         let writing_mode = self.style.get_inherited_box().clone_writing_mode();
288         let text_combine_upright = self.style.get_inherited_text().clone_text_combine_upright();
290         if matches!(
291             writing_mode,
292             WritingMode::VerticalRl | WritingMode::VerticalLr
293         ) && text_combine_upright == TextCombineUpright::All
294         {
295             self.style.add_flags(ComputedValueFlags::IS_TEXT_COMBINED);
296             self.style
297                 .mutate_inherited_box()
298                 .set_writing_mode(WritingMode::HorizontalTb);
299             self.style.writing_mode =
300                 logical_geometry::WritingMode::new(self.style.get_inherited_box());
301         }
302     }
304     /// Unconditionally propagates the line break suppression flag to text, and
305     /// additionally it applies it if it is in any ruby box.
306     ///
307     /// This is necessary because its parent may not itself have the flag set
308     /// (e.g. ruby or ruby containers), thus we may not inherit the flag from
309     /// them.
310     #[cfg(feature = "gecko")]
311     fn adjust_for_text_in_ruby(&mut self) {
312         let parent_display = self.style.get_parent_box().clone_display();
313         if parent_display.is_ruby_type() ||
314             self.style
315                 .get_parent_flags()
316                 .contains(ComputedValueFlags::SHOULD_SUPPRESS_LINEBREAK)
317         {
318             self.style
319                 .add_flags(ComputedValueFlags::SHOULD_SUPPRESS_LINEBREAK);
320         }
321     }
323     /// <https://drafts.csswg.org/css-writing-modes-3/#block-flow:>
324     ///
325     ///    If a box has a different writing-mode value than its containing
326     ///    block:
327     ///
328     ///        - If the box has a specified display of inline, its display
329     ///          computes to inline-block. [CSS21]
330     ///
331     /// This matches the adjustment that Gecko does, not exactly following
332     /// the spec. See also:
333     ///
334     /// <https://lists.w3.org/Archives/Public/www-style/2017Mar/0045.html>
335     /// <https://github.com/servo/servo/issues/15754>
336     fn adjust_for_writing_mode(&mut self, layout_parent_style: &ComputedValues) {
337         let our_writing_mode = self.style.get_inherited_box().clone_writing_mode();
338         let parent_writing_mode = layout_parent_style.get_inherited_box().clone_writing_mode();
340         if our_writing_mode != parent_writing_mode &&
341             self.style.get_box().clone_display() == Display::Inline
342         {
343             // TODO(emilio): Figure out if we can just set the adjusted display
344             // on Gecko too and unify this code path.
345             if cfg!(feature = "servo") {
346                 self.style
347                     .mutate_box()
348                     .set_adjusted_display(Display::InlineBlock, false);
349             } else {
350                 self.style.mutate_box().set_display(Display::InlineBlock);
351             }
352         }
353     }
355     /// When mathvariant is not "none", font-weight and font-style are
356     /// both forced to "normal".
357     #[cfg(feature = "gecko")]
358     fn adjust_for_mathvariant(&mut self) {
359         use crate::properties::longhands::_moz_math_variant::computed_value::T as MozMathVariant;
360         use crate::properties::longhands::font_weight::computed_value::T as FontWeight;
361         use crate::values::generics::font::FontStyle;
362         if self.style.get_font().clone__moz_math_variant() != MozMathVariant::None {
363             let font_style = self.style.mutate_font();
364             font_style.set_font_weight(FontWeight::normal());
365             font_style.set_font_style(FontStyle::Normal);
366         }
367     }
369     /// This implements an out-of-date spec. The new spec moves the handling of
370     /// this to layout, which Gecko implements but Servo doesn't.
371     ///
372     /// See https://github.com/servo/servo/issues/15229
373     #[cfg(feature = "servo")]
374     fn adjust_for_alignment(&mut self, layout_parent_style: &ComputedValues) {
375         use crate::computed_values::align_items::T as AlignItems;
376         use crate::computed_values::align_self::T as AlignSelf;
378         if self.style.get_position().clone_align_self() == AlignSelf::Auto &&
379             !self.style.is_absolutely_positioned()
380         {
381             let self_align = match layout_parent_style.get_position().clone_align_items() {
382                 AlignItems::Stretch => AlignSelf::Stretch,
383                 AlignItems::Baseline => AlignSelf::Baseline,
384                 AlignItems::FlexStart => AlignSelf::FlexStart,
385                 AlignItems::FlexEnd => AlignSelf::FlexEnd,
386                 AlignItems::Center => AlignSelf::Center,
387             };
388             self.style.mutate_position().set_align_self(self_align);
389         }
390     }
392     /// The initial value of border-*-width may be changed at computed value
393     /// time.
394     ///
395     /// This is moved to properties.rs for convenience.
396     fn adjust_for_border_width(&mut self) {
397         properties::adjust_border_width(self.style);
398     }
400     /// The initial value of outline-width may be changed at computed value time.
401     fn adjust_for_outline(&mut self) {
402         if self
403             .style
404             .get_outline()
405             .clone_outline_style()
406             .none_or_hidden() &&
407             self.style.get_outline().outline_has_nonzero_width()
408         {
409             self.style
410                 .mutate_outline()
411                 .set_outline_width(crate::Zero::zero());
412         }
413     }
415     /// CSS overflow-x and overflow-y require some fixup as well in some cases.
416     /// https://drafts.csswg.org/css-overflow-3/#overflow-properties
417     /// "Computed value: as specified, except with `visible`/`clip` computing to
418     /// `auto`/`hidden` (respectively) if one of `overflow-x` or `overflow-y` is
419     /// neither `visible` nor `clip`."
420     fn adjust_for_overflow(&mut self) {
421         let overflow_x = self.style.get_box().clone_overflow_x();
422         let overflow_y = self.style.get_box().clone_overflow_y();
423         if overflow_x == overflow_y {
424             return; // optimization for the common case
425         }
427         if overflow_x.is_scrollable() != overflow_y.is_scrollable() {
428             let box_style = self.style.mutate_box();
429             box_style.set_overflow_x(overflow_x.to_scrollable());
430             box_style.set_overflow_y(overflow_y.to_scrollable());
431         }
432     }
434     /// Handles the relevant sections in:
435     ///
436     /// https://drafts.csswg.org/css-display/#unbox-html
437     ///
438     /// And forbidding display: contents in pseudo-elements, at least for now.
439     #[cfg(feature = "gecko")]
440     fn adjust_for_prohibited_display_contents<E>(&mut self, element: Option<E>)
441     where
442         E: TElement,
443     {
444         if self.style.get_box().clone_display() != Display::Contents {
445             return;
446         }
448         // FIXME(emilio): ::before and ::after should support display: contents,
449         // see bug 1418138.
450         if self.style.pseudo.is_some() {
451             self.style.mutate_box().set_display(Display::Inline);
452             return;
453         }
455         let element = match element {
456             Some(e) => e,
457             None => return,
458         };
460         if is_effective_display_none_for_display_contents(element) {
461             self.style.mutate_box().set_display(Display::None);
462         }
463     }
465     /// <textarea>'s editor root needs to inherit the overflow value from its
466     /// parent, but we need to make sure it's still scrollable.
467     #[cfg(feature = "gecko")]
468     fn adjust_for_text_control_editing_root(&mut self) {
469         use crate::selector_parser::PseudoElement;
471         if self.style.pseudo != Some(&PseudoElement::MozTextControlEditingRoot) {
472             return;
473         }
475         let box_style = self.style.get_box();
476         let overflow_x = box_style.clone_overflow_x();
477         let overflow_y = box_style.clone_overflow_y();
479         // If at least one is scrollable we'll adjust the other one in
480         // adjust_for_overflow if needed.
481         if overflow_x.is_scrollable() || overflow_y.is_scrollable() {
482             return;
483         }
485         let box_style = self.style.mutate_box();
486         box_style.set_overflow_x(Overflow::Auto);
487         box_style.set_overflow_y(Overflow::Auto);
488     }
490     /// If a <fieldset> has grid/flex display type, we need to inherit
491     /// this type into its ::-moz-fieldset-content anonymous box.
492     ///
493     /// NOTE(emilio): We don't need to handle the display change for this case
494     /// in matching.rs because anonymous box restyling works separately to the
495     /// normal cascading process.
496     #[cfg(feature = "gecko")]
497     fn adjust_for_fieldset_content(&mut self, layout_parent_style: &ComputedValues) {
498         use crate::selector_parser::PseudoElement;
500         if self.style.pseudo != Some(&PseudoElement::FieldsetContent) {
501             return;
502         }
504         debug_assert_eq!(self.style.get_box().clone_display(), Display::Block);
505         // TODO We actually want style from parent rather than layout
506         // parent, so that this fixup doesn't happen incorrectly when
507         // when <fieldset> has "display: contents".
508         let parent_display = layout_parent_style.get_box().clone_display();
509         let new_display = match parent_display {
510             Display::Flex | Display::InlineFlex => Some(Display::Flex),
511             Display::Grid | Display::InlineGrid => Some(Display::Grid),
512             _ => None,
513         };
514         if let Some(new_display) = new_display {
515             self.style.mutate_box().set_display(new_display);
516         }
517     }
519     /// -moz-center, -moz-left and -moz-right are used for HTML's alignment.
520     ///
521     /// This is covering the <div align="right"><table>...</table></div> case.
522     ///
523     /// In this case, we don't want to inherit the text alignment into the
524     /// table.
525     #[cfg(feature = "gecko")]
526     fn adjust_for_table_text_align(&mut self) {
527         use crate::properties::longhands::text_align::computed_value::T as TextAlign;
528         if self.style.get_box().clone_display() != Display::Table {
529             return;
530         }
532         match self.style.get_inherited_text().clone_text_align() {
533             TextAlign::MozLeft | TextAlign::MozCenter | TextAlign::MozRight => {},
534             _ => return,
535         }
537         self.style
538             .mutate_inherited_text()
539             .set_text_align(TextAlign::Start)
540     }
542     /// Computes the used text decoration for Servo.
543     ///
544     /// FIXME(emilio): This is a layout tree concept, should move away from
545     /// style, since otherwise we're going to have the same subtle bugs WebKit
546     /// and Blink have with this very same thing.
547     #[cfg(feature = "servo")]
548     fn adjust_for_text_decorations_in_effect(&mut self) {
549         use crate::values::computed::text::TextDecorationsInEffect;
551         let decorations_in_effect = TextDecorationsInEffect::from_style(&self.style);
552         if self.style.get_inherited_text().text_decorations_in_effect != decorations_in_effect {
553             self.style
554                 .mutate_inherited_text()
555                 .text_decorations_in_effect = decorations_in_effect;
556         }
557     }
559     #[cfg(feature = "gecko")]
560     fn should_suppress_linebreak<E>(
561         &self,
562         layout_parent_style: &ComputedValues,
563         element: Option<E>,
564     ) -> bool
565     where
566         E: TElement,
567     {
568         // Line break suppression should only be propagated to in-flow children.
569         if self.style.is_floating() || self.style.is_absolutely_positioned() {
570             return false;
571         }
572         let parent_display = layout_parent_style.get_box().clone_display();
573         if layout_parent_style
574             .flags
575             .contains(ComputedValueFlags::SHOULD_SUPPRESS_LINEBREAK)
576         {
577             // Line break suppression is propagated to any children of
578             // line participants.
579             if parent_display.is_line_participant() {
580                 return true;
581             }
582         }
583         match self.style.get_box().clone_display() {
584             // Ruby base and text are always non-breakable.
585             Display::RubyBase | Display::RubyText => true,
586             // Ruby base container and text container are breakable.
587             // Non-HTML elements may not form ruby base / text container because
588             // they may not respect ruby-internal display values, so we can't
589             // make them escaped from line break suppression.
590             // Note that, when certain HTML tags, e.g. form controls, have ruby
591             // level container display type, they could also escape from the
592             // line break suppression flag while they shouldn't. However, it is
593             // generally fine as far as they can't break the line inside them.
594             Display::RubyBaseContainer | Display::RubyTextContainer
595                 if element.map_or(true, |e| e.is_html_element()) =>
596             {
597                 false
598             },
599             // Anything else is non-breakable if and only if its layout parent
600             // has a ruby display type, because any of the ruby boxes can be
601             // anonymous.
602             _ => parent_display.is_ruby_type(),
603         }
604     }
606     /// Do ruby-related style adjustments, which include:
607     /// * propagate the line break suppression flag,
608     /// * inlinify block descendants,
609     /// * suppress border and padding for ruby level containers,
610     /// * correct unicode-bidi.
611     #[cfg(feature = "gecko")]
612     fn adjust_for_ruby<E>(&mut self, layout_parent_style: &ComputedValues, element: Option<E>)
613     where
614         E: TElement,
615     {
616         use crate::properties::longhands::unicode_bidi::computed_value::T as UnicodeBidi;
618         let self_display = self.style.get_box().clone_display();
619         // Check whether line break should be suppressed for this element.
620         if self.should_suppress_linebreak(layout_parent_style, element) {
621             self.style
622                 .add_flags(ComputedValueFlags::SHOULD_SUPPRESS_LINEBREAK);
623             // Inlinify the display type if allowed.
624             if !self.skip_item_display_fixup(element) {
625                 let inline_display = self_display.inlinify();
626                 if self_display != inline_display {
627                     self.style
628                         .mutate_box()
629                         .set_adjusted_display(inline_display, false);
630                 }
631             }
632         }
633         // Suppress border and padding for ruby level containers.
634         // This is actually not part of the spec. It is currently unspecified
635         // how border and padding should be handled for ruby level container,
636         // and suppressing them here make it easier for layout to handle.
637         if self_display.is_ruby_level_container() {
638             self.style.reset_border_struct();
639             self.style.reset_padding_struct();
640         }
642         // Force bidi isolation on all internal ruby boxes and ruby container
643         // per spec https://drafts.csswg.org/css-ruby-1/#bidi
644         if self_display.is_ruby_type() {
645             let new_value = match self.style.get_text().clone_unicode_bidi() {
646                 UnicodeBidi::Normal | UnicodeBidi::Embed => Some(UnicodeBidi::Isolate),
647                 UnicodeBidi::BidiOverride => Some(UnicodeBidi::IsolateOverride),
648                 _ => None,
649             };
650             if let Some(new_value) = new_value {
651                 self.style.mutate_text().set_unicode_bidi(new_value);
652             }
653         }
654     }
656     /// Computes the RELEVANT_LINK_VISITED flag based on the parent style and on
657     /// whether we're a relevant link.
658     ///
659     /// NOTE(emilio): We don't do this for text styles, which is... dubious, but
660     /// Gecko doesn't seem to do it either. It's extremely easy to do if needed
661     /// though.
662     ///
663     /// FIXME(emilio): This isn't technically a style adjustment thingie, could
664     /// it move somewhere else?
665     fn adjust_for_visited<E>(&mut self, element: Option<E>)
666     where
667         E: TElement,
668     {
669         if !self.style.has_visited_style() {
670             return;
671         }
673         let is_link_element = self.style.pseudo.is_none() && element.map_or(false, |e| e.is_link());
675         if !is_link_element {
676             return;
677         }
679         if element.unwrap().is_visited_link() {
680             self.style
681                 .add_flags(ComputedValueFlags::IS_RELEVANT_LINK_VISITED);
682         } else {
683             // Need to remove to handle unvisited link inside visited.
684             self.style
685                 .remove_flags(ComputedValueFlags::IS_RELEVANT_LINK_VISITED);
686         }
687     }
689     /// Resolves "justify-items: legacy" based on the inherited style if needed
690     /// to comply with:
691     ///
692     /// <https://drafts.csswg.org/css-align/#valdef-justify-items-legacy>
693     #[cfg(feature = "gecko")]
694     fn adjust_for_justify_items(&mut self) {
695         use crate::values::specified::align;
696         let justify_items = self.style.get_position().clone_justify_items();
697         if justify_items.specified.0 != align::AlignFlags::LEGACY {
698             return;
699         }
701         let parent_justify_items = self.style.get_parent_position().clone_justify_items();
703         if !parent_justify_items
704             .computed
705             .0
706             .contains(align::AlignFlags::LEGACY)
707         {
708             return;
709         }
711         if parent_justify_items.computed == justify_items.computed {
712             return;
713         }
715         self.style
716             .mutate_position()
717             .set_computed_justify_items(parent_justify_items.computed);
718     }
720     /// If '-webkit-appearance' is 'menulist' on a <select> element then
721     /// the computed value of 'line-height' is 'normal'.
722     ///
723     /// https://github.com/w3c/csswg-drafts/issues/3257
724     #[cfg(feature = "gecko")]
725     fn adjust_for_appearance<E>(&mut self, element: Option<E>)
726     where
727         E: TElement,
728     {
729         use crate::properties::longhands::appearance::computed_value::T as Appearance;
730         use crate::properties::longhands::line_height::computed_value::T as LineHeight;
732         let box_ = self.style.get_box();
733         let appearance = match box_.clone_appearance() {
734             Appearance::Auto => box_.clone__moz_default_appearance(),
735             a => a,
736         };
738         if appearance == Appearance::Menulist {
739             if self.style.get_inherited_text().clone_line_height() == LineHeight::normal() {
740                 return;
741             }
742             if self.style.pseudo.is_some() {
743                 return;
744             }
745             let is_html_select_element = element.map_or(false, |e| {
746                 e.is_html_element() && e.local_name() == &*atom!("select")
747             });
748             if !is_html_select_element {
749                 return;
750             }
751             self.style
752                 .mutate_inherited_text()
753                 .set_line_height(LineHeight::normal());
754         }
755     }
757     /// A legacy ::marker (i.e. no 'content') without an author-specified 'font-family'
758     /// and 'list-style-type:disc|circle|square|disclosure-closed|disclosure-open'
759     /// is assigned 'font-family:-moz-bullet-font'. (This is for <ul><li> etc.)
760     /// We don't want synthesized italic/bold for this font, so turn that off too.
761     /// Likewise for 'letter/word-spacing' -- unless the author specified it then reset
762     /// them to their initial value because traditionally we never added such spacing
763     /// between a legacy bullet and the list item's content, so we keep that behavior
764     /// for web-compat reasons.
765     /// We intentionally don't check 'list-style-image' below since we want it to use
766     /// the same font as its fallback ('list-style-type') in case it fails to load.
767     #[cfg(feature = "gecko")]
768     fn adjust_for_marker_pseudo(&mut self) {
769         use crate::values::computed::counters::Content;
770         use crate::values::computed::font::{FontFamily, FontSynthesis};
771         use crate::values::computed::text::{LetterSpacing, WordSpacing};
773         let is_legacy_marker = self.style.pseudo.map_or(false, |p| p.is_marker()) &&
774             self.style.get_list().clone_list_style_type().is_bullet() &&
775             self.style.get_counters().clone_content() == Content::Normal;
776         if !is_legacy_marker {
777             return;
778         }
779         if !self
780             .style
781             .flags
782             .get()
783             .contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_FAMILY)
784         {
785             self.style
786                 .mutate_font()
787                 .set_font_family(FontFamily::moz_bullet().clone());
789             // FIXME(mats): We can remove this if support for font-synthesis is added to @font-face rules.
790             // Then we can add it to the @font-face rule in html.css instead.
791             // https://github.com/w3c/csswg-drafts/issues/6081
792             if !self
793                 .style
794                 .flags
795                 .get()
796                 .contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS)
797             {
798                 self.style
799                     .mutate_font()
800                     .set_font_synthesis(FontSynthesis::none());
801             }
802         }
803         if !self
804             .style
805             .flags
806             .get()
807             .contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_LETTER_SPACING)
808         {
809             self.style
810                 .mutate_inherited_text()
811                 .set_letter_spacing(LetterSpacing::normal());
812         }
813         if !self
814             .style
815             .flags
816             .get()
817             .contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_WORD_SPACING)
818         {
819             self.style
820                 .mutate_inherited_text()
821                 .set_word_spacing(WordSpacing::normal());
822         }
823     }
825     /// Adjusts the style to account for various fixups that don't fit naturally
826     /// into the cascade.
827     ///
828     /// When comparing to Gecko, this is similar to the work done by
829     /// `ComputedStyle::ApplyStyleFixups`, plus some parts of
830     /// `nsStyleSet::GetContext`.
831     pub fn adjust<E>(&mut self, layout_parent_style: &ComputedValues, element: Option<E>)
832     where
833         E: TElement,
834     {
835         if cfg!(debug_assertions) {
836             if element.map_or(false, |e| e.is_pseudo_element()) {
837                 // It'd be nice to assert `self.style.pseudo == Some(&pseudo)`,
838                 // but we do resolve ::-moz-list pseudos on ::before / ::after
839                 // content, sigh.
840                 debug_assert!(self.style.pseudo.is_some(), "Someone really messed up");
841             }
842         }
843         // FIXME(emilio): The apply_declarations callsite in Servo's
844         // animation, and the font stuff for Gecko
845         // (Stylist::compute_for_declarations) should pass an element to
846         // cascade(), then we can make this assertion hold everywhere.
847         // debug_assert!(
848         //     element.is_some() || self.style.pseudo.is_some(),
849         //     "Should always have an element around for non-pseudo styles"
850         // );
852         self.adjust_for_visited(element);
853         #[cfg(feature = "gecko")]
854         {
855             self.adjust_for_prohibited_display_contents(element);
856             self.adjust_for_fieldset_content(layout_parent_style);
857             // NOTE: It's important that this happens before
858             // adjust_for_overflow.
859             self.adjust_for_text_control_editing_root();
860         }
861         self.adjust_for_top_layer();
862         self.blockify_if_necessary(layout_parent_style, element);
863         self.adjust_for_position();
864         self.adjust_for_overflow();
865         #[cfg(feature = "gecko")]
866         {
867             self.adjust_for_table_text_align();
868             self.adjust_for_mathvariant();
869             self.adjust_for_justify_items();
870         }
871         #[cfg(feature = "servo")]
872         {
873             self.adjust_for_alignment(layout_parent_style);
874         }
875         self.adjust_for_border_width();
876         self.adjust_for_outline();
877         self.adjust_for_writing_mode(layout_parent_style);
878         #[cfg(feature = "gecko")]
879         {
880             self.adjust_for_ruby(layout_parent_style, element);
881         }
882         #[cfg(feature = "servo")]
883         {
884             self.adjust_for_text_decorations_in_effect();
885         }
886         #[cfg(feature = "gecko")]
887         {
888             self.adjust_for_appearance(element);
889             self.adjust_for_marker_pseudo();
890         }
891         self.set_bits();
892     }