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