forked from mirrors/gecko-dev
		
	 3c206e563a
			
		
	
	
		3c206e563a
		
	
	
	
	
		
			
			This is a hack, sorta, similar to Chromium's: https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/layout/layout_object.cc;l=356;drc=312b74e385e6aba98ab31fd911238c0dc16b396c except at computed-value rather than used-value time, because it's both simpler to reason about and prevents lying in the computed style. This fixes the relevant test-case, and matches closer what Chromium does, by not creating anonymous flex items for all elements inside the line-clamp context. The behavior change is covered by the test changes. I had to also fix a couple pre-existing bugs that were caught by tests, now that the line-clamped block is the -webkit-box-styled element rather than an anonymous flex item (and thus now had padding). Depends on D155180 Differential Revision: https://phabricator.services.mozilla.com/D155181
		
			
				
	
	
		
			919 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			919 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| //! A struct to encapsulate all the style fixups and flags propagations
 | |
| //! a computed style needs in order for it to adhere to the CSS spec.
 | |
| 
 | |
| use crate::computed_value_flags::ComputedValueFlags;
 | |
| use crate::dom::TElement;
 | |
| use crate::properties::longhands::contain::SpecifiedValue;
 | |
| use crate::properties::longhands::display::computed_value::T as Display;
 | |
| use crate::properties::longhands::float::computed_value::T as Float;
 | |
| use crate::properties::longhands::overflow_x::computed_value::T as Overflow;
 | |
| use crate::properties::longhands::position::computed_value::T as Position;
 | |
| use crate::properties::{self, ComputedValues, StyleBuilder};
 | |
| 
 | |
| 
 | |
| /// A struct that implements all the adjustment methods.
 | |
| ///
 | |
| /// NOTE(emilio): If new adjustments are introduced that depend on reset
 | |
| /// properties of the parent, you may need tweaking the
 | |
| /// `ChildCascadeRequirement` code in `matching.rs`.
 | |
| ///
 | |
| /// NOTE(emilio): Also, if new adjustments are introduced that break the
 | |
| /// following invariant:
 | |
| ///
 | |
| ///   Given same tag name, namespace, rules and parent style, two elements would
 | |
| ///   end up with exactly the same style.
 | |
| ///
 | |
| /// Then you need to adjust the lookup_by_rules conditions in the sharing cache.
 | |
| pub struct StyleAdjuster<'a, 'b: 'a> {
 | |
|     style: &'a mut StyleBuilder<'b>,
 | |
| }
 | |
| 
 | |
| #[cfg(feature = "gecko")]
 | |
| fn is_topmost_svg_svg_element<E>(e: E) -> bool
 | |
| where
 | |
|     E: TElement,
 | |
| {
 | |
|     debug_assert!(e.is_svg_element());
 | |
|     if e.local_name() != &*atom!("svg") {
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     let parent = match e.traversal_parent() {
 | |
|         Some(n) => n,
 | |
|         None => return true,
 | |
|     };
 | |
| 
 | |
|     if !parent.is_svg_element() {
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     parent.local_name() == &*atom!("foreignObject")
 | |
| }
 | |
| 
 | |
| // https://drafts.csswg.org/css-display/#unbox
 | |
| #[cfg(feature = "gecko")]
 | |
| fn is_effective_display_none_for_display_contents<E>(element: E) -> bool
 | |
| where
 | |
|     E: TElement,
 | |
| {
 | |
|     use crate::Atom;
 | |
| 
 | |
|     const SPECIAL_HTML_ELEMENTS: [Atom; 16] = [
 | |
|         atom!("br"),
 | |
|         atom!("wbr"),
 | |
|         atom!("meter"),
 | |
|         atom!("progress"),
 | |
|         atom!("canvas"),
 | |
|         atom!("embed"),
 | |
|         atom!("object"),
 | |
|         atom!("audio"),
 | |
|         atom!("iframe"),
 | |
|         atom!("img"),
 | |
|         atom!("video"),
 | |
|         atom!("frame"),
 | |
|         atom!("frameset"),
 | |
|         atom!("input"),
 | |
|         atom!("textarea"),
 | |
|         atom!("select"),
 | |
|     ];
 | |
| 
 | |
|     // https://drafts.csswg.org/css-display/#unbox-svg
 | |
|     //
 | |
|     // There's a note about "Unknown elements", but there's not a good way to
 | |
|     // know what that means, or to get that information from here, and no other
 | |
|     // UA implements this either.
 | |
|     const SPECIAL_SVG_ELEMENTS: [Atom; 6] = [
 | |
|         atom!("svg"),
 | |
|         atom!("a"),
 | |
|         atom!("g"),
 | |
|         atom!("use"),
 | |
|         atom!("tspan"),
 | |
|         atom!("textPath"),
 | |
|     ];
 | |
| 
 | |
|     // https://drafts.csswg.org/css-display/#unbox-html
 | |
|     if element.is_html_element() {
 | |
|         let local_name = element.local_name();
 | |
|         return SPECIAL_HTML_ELEMENTS
 | |
|             .iter()
 | |
|             .any(|name| &**name == local_name);
 | |
|     }
 | |
| 
 | |
|     // https://drafts.csswg.org/css-display/#unbox-svg
 | |
|     if element.is_svg_element() {
 | |
|         if is_topmost_svg_svg_element(element) {
 | |
|             return true;
 | |
|         }
 | |
|         let local_name = element.local_name();
 | |
|         return !SPECIAL_SVG_ELEMENTS
 | |
|             .iter()
 | |
|             .any(|name| &**name == local_name);
 | |
|     }
 | |
| 
 | |
|     // https://drafts.csswg.org/css-display/#unbox-mathml
 | |
|     //
 | |
|     // We always treat XUL as display: none. We don't use display:
 | |
|     // contents in XUL anyway, so should be fine to be consistent with
 | |
|     // MathML unless there's a use case for it.
 | |
|     if element.is_mathml_element() || element.is_xul_element() {
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     false
 | |
| }
 | |
| 
 | |
| impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
 | |
|     /// Trivially constructs a new StyleAdjuster.
 | |
|     #[inline]
 | |
|     pub fn new(style: &'a mut StyleBuilder<'b>) -> Self {
 | |
|         StyleAdjuster { style }
 | |
|     }
 | |
| 
 | |
|     /// <https://fullscreen.spec.whatwg.org/#new-stacking-layer>
 | |
|     ///
 | |
|     ///    Any position value other than 'absolute' and 'fixed' are
 | |
|     ///    computed to 'absolute' if the element is in a top layer.
 | |
|     ///
 | |
|     fn adjust_for_top_layer(&mut self) {
 | |
|         if !self.style.in_top_layer() {
 | |
|             return;
 | |
|         }
 | |
|         if !self.style.is_absolutely_positioned() {
 | |
|             self.style.mutate_box().set_position(Position::Absolute);
 | |
|         }
 | |
|         if self.style.get_box().clone_display().is_contents() {
 | |
|             self.style.mutate_box().set_display(Display::Block);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// -webkit-box with line-clamp and vertical orientation gets turned into
 | |
|     /// flow-root at computed-value time.
 | |
|     ///
 | |
|     /// This makes the element not be a flex container, with all that it
 | |
|     /// implies, but it should be safe. It matches blink, see
 | |
|     /// https://bugzilla.mozilla.org/show_bug.cgi?id=1786147#c10
 | |
|     fn adjust_for_webkit_line_clamp(&mut self) {
 | |
|         use crate::properties::longhands::_moz_box_orient::computed_value::T as BoxOrient;
 | |
|         use crate::values::specified::box_::{DisplayOutside, DisplayInside};
 | |
|         let box_style= self.style.get_box();
 | |
|         if box_style.clone__webkit_line_clamp().is_none() {
 | |
|             return;
 | |
|         }
 | |
|         let disp = box_style.clone_display();
 | |
|         if disp.inside() != DisplayInside::WebkitBox {
 | |
|             return;
 | |
|         }
 | |
|         if self.style.get_xul().clone__moz_box_orient() != BoxOrient::Vertical {
 | |
|             return;
 | |
|         }
 | |
|         let new_display = if disp.outside() == DisplayOutside::Block {
 | |
|             Display::FlowRoot
 | |
|         } else {
 | |
|             debug_assert_eq!(disp.outside(), DisplayOutside::Inline);
 | |
|             Display::InlineBlock
 | |
|         };
 | |
|         self.style.mutate_box().set_adjusted_display(new_display, false);
 | |
|     }
 | |
| 
 | |
|     /// CSS 2.1 section 9.7:
 | |
|     ///
 | |
|     ///    If 'position' has the value 'absolute' or 'fixed', [...] the computed
 | |
|     ///    value of 'float' is 'none'.
 | |
|     ///
 | |
|     fn adjust_for_position(&mut self) {
 | |
|         if self.style.is_absolutely_positioned() && self.style.is_floating() {
 | |
|             self.style.mutate_box().set_float(Float::None);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// Whether we should skip any item-based display property blockification on
 | |
|     /// this element.
 | |
|     fn skip_item_display_fixup<E>(&self, element: Option<E>) -> bool
 | |
|     where
 | |
|         E: TElement,
 | |
|     {
 | |
|         if let Some(pseudo) = self.style.pseudo {
 | |
|             return pseudo.skip_item_display_fixup();
 | |
|         }
 | |
| 
 | |
|         element.map_or(false, |e| e.skip_item_display_fixup())
 | |
|     }
 | |
| 
 | |
|     /// Apply the blockification rules based on the table in CSS 2.2 section 9.7.
 | |
|     /// <https://drafts.csswg.org/css2/visuren.html#dis-pos-flo>
 | |
|     /// A ::marker pseudo-element with 'list-style-position:outside' needs to
 | |
|     /// have its 'display' blockified, unless the ::marker is for an inline
 | |
|     /// list-item (for which 'list-style-position:outside' behaves as 'inside').
 | |
|     /// https://drafts.csswg.org/css-lists-3/#list-style-position-property
 | |
|     fn blockify_if_necessary<E>(&mut self, layout_parent_style: &ComputedValues, element: Option<E>)
 | |
|     where
 | |
|         E: TElement,
 | |
|     {
 | |
|         let mut blockify = false;
 | |
|         macro_rules! blockify_if {
 | |
|             ($if_what:expr) => {
 | |
|                 if !blockify {
 | |
|                     blockify = $if_what;
 | |
|                 }
 | |
|             };
 | |
|         }
 | |
| 
 | |
|         blockify_if!(self.style.is_root_element);
 | |
|         if !self.skip_item_display_fixup(element) {
 | |
|             let parent_display = layout_parent_style.get_box().clone_display();
 | |
|             blockify_if!(parent_display.is_item_container());
 | |
|         }
 | |
| 
 | |
|         let is_item_or_root = blockify;
 | |
| 
 | |
|         blockify_if!(self.style.is_floating());
 | |
|         blockify_if!(self.style.is_absolutely_positioned());
 | |
| 
 | |
|         if !blockify {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         let display = self.style.get_box().clone_display();
 | |
|         let blockified_display = display.equivalent_block_display(self.style.is_root_element);
 | |
|         if display != blockified_display {
 | |
|             self.style
 | |
|                 .mutate_box()
 | |
|                 .set_adjusted_display(blockified_display, is_item_or_root);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// Compute a few common flags for both text and element's style.
 | |
|     fn set_bits(&mut self) {
 | |
|         let display = self.style.get_box().clone_display();
 | |
| 
 | |
|         if !display.is_contents() {
 | |
|             if !self
 | |
|                 .style
 | |
|                 .get_text()
 | |
|                 .clone_text_decoration_line()
 | |
|                 .is_empty()
 | |
|             {
 | |
|                 self.style
 | |
|                     .add_flags(ComputedValueFlags::HAS_TEXT_DECORATION_LINES);
 | |
|             }
 | |
| 
 | |
|             if self.style.get_effects().clone_opacity() == 0. {
 | |
|                 self.style
 | |
|                     .add_flags(ComputedValueFlags::IS_IN_OPACITY_ZERO_SUBTREE);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if self.style.is_pseudo_element() {
 | |
|             self.style
 | |
|                 .add_flags(ComputedValueFlags::IS_IN_PSEUDO_ELEMENT_SUBTREE);
 | |
|         }
 | |
| 
 | |
|         if self.style.is_root_element {
 | |
|             self.style
 | |
|                 .add_flags(ComputedValueFlags::IS_ROOT_ELEMENT_STYLE);
 | |
|         }
 | |
| 
 | |
|         if self.style
 | |
|             .get_box()
 | |
|             .clone_contain()
 | |
|             .contains(SpecifiedValue::STYLE)
 | |
|         {
 | |
|             self.style
 | |
|                 .add_flags(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_CONTAIN_STYLE);
 | |
|         }
 | |
| 
 | |
|         #[cfg(feature = "servo-layout-2013")]
 | |
|         {
 | |
|             if self.style.get_parent_column().is_multicol() {
 | |
|                 self.style.add_flags(ComputedValueFlags::CAN_BE_FRAGMENTED);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// Adjust the style for text style.
 | |
|     ///
 | |
|     /// The adjustments here are a subset of the adjustments generally, because
 | |
|     /// text only inherits properties.
 | |
|     ///
 | |
|     /// Note that this, for Gecko, comes through Servo_ComputedValues_Inherit.
 | |
|     #[cfg(feature = "gecko")]
 | |
|     pub fn adjust_for_text(&mut self) {
 | |
|         debug_assert!(!self.style.is_root_element);
 | |
|         self.adjust_for_text_combine_upright();
 | |
|         self.adjust_for_text_in_ruby();
 | |
|         self.set_bits();
 | |
|     }
 | |
| 
 | |
|     /// Change writing mode of the text frame for text-combine-upright.
 | |
|     ///
 | |
|     /// It is safe to look at our own style because we are looking at inherited
 | |
|     /// properties, and text is just plain inheritance.
 | |
|     ///
 | |
|     /// TODO(emilio): we should (Gecko too) revise these adjustments in presence
 | |
|     /// of display: contents.
 | |
|     ///
 | |
|     /// FIXME(emilio): How does this play with logical properties? Doesn't
 | |
|     /// mutating writing-mode change the potential physical sides chosen?
 | |
|     #[cfg(feature = "gecko")]
 | |
|     fn adjust_for_text_combine_upright(&mut self) {
 | |
|         use crate::computed_values::text_combine_upright::T as TextCombineUpright;
 | |
|         use crate::computed_values::writing_mode::T as WritingMode;
 | |
|         use crate::logical_geometry;
 | |
| 
 | |
|         let writing_mode = self.style.get_inherited_box().clone_writing_mode();
 | |
|         let text_combine_upright = self.style.get_inherited_text().clone_text_combine_upright();
 | |
| 
 | |
|         if matches!(
 | |
|             writing_mode,
 | |
|             WritingMode::VerticalRl | WritingMode::VerticalLr
 | |
|         ) && text_combine_upright == TextCombineUpright::All
 | |
|         {
 | |
|             self.style.add_flags(ComputedValueFlags::IS_TEXT_COMBINED);
 | |
|             self.style
 | |
|                 .mutate_inherited_box()
 | |
|                 .set_writing_mode(WritingMode::HorizontalTb);
 | |
|             self.style.writing_mode =
 | |
|                 logical_geometry::WritingMode::new(self.style.get_inherited_box());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// Unconditionally propagates the line break suppression flag to text, and
 | |
|     /// additionally it applies it if it is in any ruby box.
 | |
|     ///
 | |
|     /// This is necessary because its parent may not itself have the flag set
 | |
|     /// (e.g. ruby or ruby containers), thus we may not inherit the flag from
 | |
|     /// them.
 | |
|     #[cfg(feature = "gecko")]
 | |
|     fn adjust_for_text_in_ruby(&mut self) {
 | |
|         let parent_display = self.style.get_parent_box().clone_display();
 | |
|         if parent_display.is_ruby_type() ||
 | |
|             self.style
 | |
|                 .get_parent_flags()
 | |
|                 .contains(ComputedValueFlags::SHOULD_SUPPRESS_LINEBREAK)
 | |
|         {
 | |
|             self.style
 | |
|                 .add_flags(ComputedValueFlags::SHOULD_SUPPRESS_LINEBREAK);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// <https://drafts.csswg.org/css-writing-modes-3/#block-flow:>
 | |
|     ///
 | |
|     ///    If a box has a different writing-mode value than its containing
 | |
|     ///    block:
 | |
|     ///
 | |
|     ///        - If the box has a specified display of inline, its display
 | |
|     ///          computes to inline-block. [CSS21]
 | |
|     ///
 | |
|     /// This matches the adjustment that Gecko does, not exactly following
 | |
|     /// the spec. See also:
 | |
|     ///
 | |
|     /// <https://lists.w3.org/Archives/Public/www-style/2017Mar/0045.html>
 | |
|     /// <https://github.com/servo/servo/issues/15754>
 | |
|     fn adjust_for_writing_mode(&mut self, layout_parent_style: &ComputedValues) {
 | |
|         let our_writing_mode = self.style.get_inherited_box().clone_writing_mode();
 | |
|         let parent_writing_mode = layout_parent_style.get_inherited_box().clone_writing_mode();
 | |
| 
 | |
|         if our_writing_mode != parent_writing_mode &&
 | |
|             self.style.get_box().clone_display() == Display::Inline
 | |
|         {
 | |
|             // TODO(emilio): Figure out if we can just set the adjusted display
 | |
|             // on Gecko too and unify this code path.
 | |
|             if cfg!(feature = "servo") {
 | |
|                 self.style
 | |
|                     .mutate_box()
 | |
|                     .set_adjusted_display(Display::InlineBlock, false);
 | |
|             } else {
 | |
|                 self.style.mutate_box().set_display(Display::InlineBlock);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// This implements an out-of-date spec. The new spec moves the handling of
 | |
|     /// this to layout, which Gecko implements but Servo doesn't.
 | |
|     ///
 | |
|     /// See https://github.com/servo/servo/issues/15229
 | |
|     #[cfg(feature = "servo")]
 | |
|     fn adjust_for_alignment(&mut self, layout_parent_style: &ComputedValues) {
 | |
|         use crate::computed_values::align_items::T as AlignItems;
 | |
|         use crate::computed_values::align_self::T as AlignSelf;
 | |
| 
 | |
|         if self.style.get_position().clone_align_self() == AlignSelf::Auto &&
 | |
|             !self.style.is_absolutely_positioned()
 | |
|         {
 | |
|             let self_align = match layout_parent_style.get_position().clone_align_items() {
 | |
|                 AlignItems::Stretch => AlignSelf::Stretch,
 | |
|                 AlignItems::Baseline => AlignSelf::Baseline,
 | |
|                 AlignItems::FlexStart => AlignSelf::FlexStart,
 | |
|                 AlignItems::FlexEnd => AlignSelf::FlexEnd,
 | |
|                 AlignItems::Center => AlignSelf::Center,
 | |
|             };
 | |
|             self.style.mutate_position().set_align_self(self_align);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// The initial value of border-*-width may be changed at computed value
 | |
|     /// time.
 | |
|     ///
 | |
|     /// This is moved to properties.rs for convenience.
 | |
|     fn adjust_for_border_width(&mut self) {
 | |
|         properties::adjust_border_width(self.style);
 | |
|     }
 | |
| 
 | |
|     /// The initial value of outline-width may be changed at computed value time.
 | |
|     fn adjust_for_outline(&mut self) {
 | |
|         if self
 | |
|             .style
 | |
|             .get_outline()
 | |
|             .clone_outline_style()
 | |
|             .none_or_hidden() &&
 | |
|             self.style.get_outline().outline_has_nonzero_width()
 | |
|         {
 | |
|             self.style
 | |
|                 .mutate_outline()
 | |
|                 .set_outline_width(crate::Zero::zero());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// CSS overflow-x and overflow-y require some fixup as well in some cases.
 | |
|     /// https://drafts.csswg.org/css-overflow-3/#overflow-properties
 | |
|     /// "Computed value: as specified, except with `visible`/`clip` computing to
 | |
|     /// `auto`/`hidden` (respectively) if one of `overflow-x` or `overflow-y` is
 | |
|     /// neither `visible` nor `clip`."
 | |
|     fn adjust_for_overflow(&mut self) {
 | |
|         let overflow_x = self.style.get_box().clone_overflow_x();
 | |
|         let overflow_y = self.style.get_box().clone_overflow_y();
 | |
|         if overflow_x == overflow_y {
 | |
|             return; // optimization for the common case
 | |
|         }
 | |
| 
 | |
|         if overflow_x.is_scrollable() != overflow_y.is_scrollable() {
 | |
|             let box_style = self.style.mutate_box();
 | |
|             box_style.set_overflow_x(overflow_x.to_scrollable());
 | |
|             box_style.set_overflow_y(overflow_y.to_scrollable());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// Handles the relevant sections in:
 | |
|     ///
 | |
|     /// https://drafts.csswg.org/css-display/#unbox-html
 | |
|     ///
 | |
|     /// And forbidding display: contents in pseudo-elements, at least for now.
 | |
|     #[cfg(feature = "gecko")]
 | |
|     fn adjust_for_prohibited_display_contents<E>(&mut self, element: Option<E>)
 | |
|     where
 | |
|         E: TElement,
 | |
|     {
 | |
|         if self.style.get_box().clone_display() != Display::Contents {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // FIXME(emilio): ::before and ::after should support display: contents,
 | |
|         // see bug 1418138.
 | |
|         if self.style.pseudo.is_some() {
 | |
|             self.style.mutate_box().set_display(Display::Inline);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         let element = match element {
 | |
|             Some(e) => e,
 | |
|             None => return,
 | |
|         };
 | |
| 
 | |
|         if is_effective_display_none_for_display_contents(element) {
 | |
|             self.style.mutate_box().set_display(Display::None);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// <textarea>'s editor root needs to inherit the overflow value from its
 | |
|     /// parent, but we need to make sure it's still scrollable.
 | |
|     #[cfg(feature = "gecko")]
 | |
|     fn adjust_for_text_control_editing_root(&mut self) {
 | |
|         use crate::selector_parser::PseudoElement;
 | |
| 
 | |
|         if self.style.pseudo != Some(&PseudoElement::MozTextControlEditingRoot) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         let box_style = self.style.get_box();
 | |
|         let overflow_x = box_style.clone_overflow_x();
 | |
|         let overflow_y = box_style.clone_overflow_y();
 | |
| 
 | |
|         // If at least one is scrollable we'll adjust the other one in
 | |
|         // adjust_for_overflow if needed.
 | |
|         if overflow_x.is_scrollable() || overflow_y.is_scrollable() {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         let box_style = self.style.mutate_box();
 | |
|         box_style.set_overflow_x(Overflow::Auto);
 | |
|         box_style.set_overflow_y(Overflow::Auto);
 | |
|     }
 | |
| 
 | |
|     /// If a <fieldset> has grid/flex display type, we need to inherit
 | |
|     /// this type into its ::-moz-fieldset-content anonymous box.
 | |
|     ///
 | |
|     /// NOTE(emilio): We don't need to handle the display change for this case
 | |
|     /// in matching.rs because anonymous box restyling works separately to the
 | |
|     /// normal cascading process.
 | |
|     #[cfg(feature = "gecko")]
 | |
|     fn adjust_for_fieldset_content(&mut self, layout_parent_style: &ComputedValues) {
 | |
|         use crate::selector_parser::PseudoElement;
 | |
| 
 | |
|         if self.style.pseudo != Some(&PseudoElement::FieldsetContent) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         debug_assert_eq!(self.style.get_box().clone_display(), Display::Block);
 | |
|         // TODO We actually want style from parent rather than layout
 | |
|         // parent, so that this fixup doesn't happen incorrectly when
 | |
|         // when <fieldset> has "display: contents".
 | |
|         let parent_display = layout_parent_style.get_box().clone_display();
 | |
|         let new_display = match parent_display {
 | |
|             Display::Flex | Display::InlineFlex => Some(Display::Flex),
 | |
|             Display::Grid | Display::InlineGrid => Some(Display::Grid),
 | |
|             _ => None,
 | |
|         };
 | |
|         if let Some(new_display) = new_display {
 | |
|             self.style.mutate_box().set_display(new_display);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// -moz-center, -moz-left and -moz-right are used for HTML's alignment.
 | |
|     ///
 | |
|     /// This is covering the <div align="right"><table>...</table></div> case.
 | |
|     ///
 | |
|     /// In this case, we don't want to inherit the text alignment into the
 | |
|     /// table.
 | |
|     #[cfg(feature = "gecko")]
 | |
|     fn adjust_for_table_text_align(&mut self) {
 | |
|         use crate::properties::longhands::text_align::computed_value::T as TextAlign;
 | |
|         if self.style.get_box().clone_display() != Display::Table {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         match self.style.get_inherited_text().clone_text_align() {
 | |
|             TextAlign::MozLeft | TextAlign::MozCenter | TextAlign::MozRight => {},
 | |
|             _ => return,
 | |
|         }
 | |
| 
 | |
|         self.style
 | |
|             .mutate_inherited_text()
 | |
|             .set_text_align(TextAlign::Start)
 | |
|     }
 | |
| 
 | |
|     /// Computes the used text decoration for Servo.
 | |
|     ///
 | |
|     /// FIXME(emilio): This is a layout tree concept, should move away from
 | |
|     /// style, since otherwise we're going to have the same subtle bugs WebKit
 | |
|     /// and Blink have with this very same thing.
 | |
|     #[cfg(feature = "servo")]
 | |
|     fn adjust_for_text_decorations_in_effect(&mut self) {
 | |
|         use crate::values::computed::text::TextDecorationsInEffect;
 | |
| 
 | |
|         let decorations_in_effect = TextDecorationsInEffect::from_style(&self.style);
 | |
|         if self.style.get_inherited_text().text_decorations_in_effect != decorations_in_effect {
 | |
|             self.style
 | |
|                 .mutate_inherited_text()
 | |
|                 .text_decorations_in_effect = decorations_in_effect;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     #[cfg(feature = "gecko")]
 | |
|     fn should_suppress_linebreak<E>(
 | |
|         &self,
 | |
|         layout_parent_style: &ComputedValues,
 | |
|         element: Option<E>,
 | |
|     ) -> bool
 | |
|     where
 | |
|         E: TElement,
 | |
|     {
 | |
|         // Line break suppression should only be propagated to in-flow children.
 | |
|         if self.style.is_floating() || self.style.is_absolutely_positioned() {
 | |
|             return false;
 | |
|         }
 | |
|         let parent_display = layout_parent_style.get_box().clone_display();
 | |
|         if layout_parent_style
 | |
|             .flags
 | |
|             .contains(ComputedValueFlags::SHOULD_SUPPRESS_LINEBREAK)
 | |
|         {
 | |
|             // Line break suppression is propagated to any children of
 | |
|             // line participants.
 | |
|             if parent_display.is_line_participant() {
 | |
|                 return true;
 | |
|             }
 | |
|         }
 | |
|         match self.style.get_box().clone_display() {
 | |
|             // Ruby base and text are always non-breakable.
 | |
|             Display::RubyBase | Display::RubyText => true,
 | |
|             // Ruby base container and text container are breakable.
 | |
|             // Non-HTML elements may not form ruby base / text container because
 | |
|             // they may not respect ruby-internal display values, so we can't
 | |
|             // make them escaped from line break suppression.
 | |
|             // Note that, when certain HTML tags, e.g. form controls, have ruby
 | |
|             // level container display type, they could also escape from the
 | |
|             // line break suppression flag while they shouldn't. However, it is
 | |
|             // generally fine as far as they can't break the line inside them.
 | |
|             Display::RubyBaseContainer | Display::RubyTextContainer
 | |
|                 if element.map_or(true, |e| e.is_html_element()) =>
 | |
|             {
 | |
|                 false
 | |
|             },
 | |
|             // Anything else is non-breakable if and only if its layout parent
 | |
|             // has a ruby display type, because any of the ruby boxes can be
 | |
|             // anonymous.
 | |
|             _ => parent_display.is_ruby_type(),
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// Do ruby-related style adjustments, which include:
 | |
|     /// * propagate the line break suppression flag,
 | |
|     /// * inlinify block descendants,
 | |
|     /// * suppress border and padding for ruby level containers,
 | |
|     /// * correct unicode-bidi.
 | |
|     #[cfg(feature = "gecko")]
 | |
|     fn adjust_for_ruby<E>(&mut self, layout_parent_style: &ComputedValues, element: Option<E>)
 | |
|     where
 | |
|         E: TElement,
 | |
|     {
 | |
|         use crate::properties::longhands::unicode_bidi::computed_value::T as UnicodeBidi;
 | |
| 
 | |
|         let self_display = self.style.get_box().clone_display();
 | |
|         // Check whether line break should be suppressed for this element.
 | |
|         if self.should_suppress_linebreak(layout_parent_style, element) {
 | |
|             self.style
 | |
|                 .add_flags(ComputedValueFlags::SHOULD_SUPPRESS_LINEBREAK);
 | |
|             // Inlinify the display type if allowed.
 | |
|             if !self.skip_item_display_fixup(element) {
 | |
|                 let inline_display = self_display.inlinify();
 | |
|                 if self_display != inline_display {
 | |
|                     self.style
 | |
|                         .mutate_box()
 | |
|                         .set_adjusted_display(inline_display, false);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         // Suppress border and padding for ruby level containers.
 | |
|         // This is actually not part of the spec. It is currently unspecified
 | |
|         // how border and padding should be handled for ruby level container,
 | |
|         // and suppressing them here make it easier for layout to handle.
 | |
|         if self_display.is_ruby_level_container() {
 | |
|             self.style.reset_border_struct();
 | |
|             self.style.reset_padding_struct();
 | |
|         }
 | |
| 
 | |
|         // Force bidi isolation on all internal ruby boxes and ruby container
 | |
|         // per spec https://drafts.csswg.org/css-ruby-1/#bidi
 | |
|         if self_display.is_ruby_type() {
 | |
|             let new_value = match self.style.get_text().clone_unicode_bidi() {
 | |
|                 UnicodeBidi::Normal | UnicodeBidi::Embed => Some(UnicodeBidi::Isolate),
 | |
|                 UnicodeBidi::BidiOverride => Some(UnicodeBidi::IsolateOverride),
 | |
|                 _ => None,
 | |
|             };
 | |
|             if let Some(new_value) = new_value {
 | |
|                 self.style.mutate_text().set_unicode_bidi(new_value);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// Computes the RELEVANT_LINK_VISITED flag based on the parent style and on
 | |
|     /// whether we're a relevant link.
 | |
|     ///
 | |
|     /// NOTE(emilio): We don't do this for text styles, which is... dubious, but
 | |
|     /// Gecko doesn't seem to do it either. It's extremely easy to do if needed
 | |
|     /// though.
 | |
|     ///
 | |
|     /// FIXME(emilio): This isn't technically a style adjustment thingie, could
 | |
|     /// it move somewhere else?
 | |
|     fn adjust_for_visited<E>(&mut self, element: Option<E>)
 | |
|     where
 | |
|         E: TElement,
 | |
|     {
 | |
|         if !self.style.has_visited_style() {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         let is_link_element = self.style.pseudo.is_none() && element.map_or(false, |e| e.is_link());
 | |
| 
 | |
|         if !is_link_element {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if element.unwrap().is_visited_link() {
 | |
|             self.style
 | |
|                 .add_flags(ComputedValueFlags::IS_RELEVANT_LINK_VISITED);
 | |
|         } else {
 | |
|             // Need to remove to handle unvisited link inside visited.
 | |
|             self.style
 | |
|                 .remove_flags(ComputedValueFlags::IS_RELEVANT_LINK_VISITED);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// Resolves "justify-items: legacy" based on the inherited style if needed
 | |
|     /// to comply with:
 | |
|     ///
 | |
|     /// <https://drafts.csswg.org/css-align/#valdef-justify-items-legacy>
 | |
|     #[cfg(feature = "gecko")]
 | |
|     fn adjust_for_justify_items(&mut self) {
 | |
|         use crate::values::specified::align;
 | |
|         let justify_items = self.style.get_position().clone_justify_items();
 | |
|         if justify_items.specified.0 != align::AlignFlags::LEGACY {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         let parent_justify_items = self.style.get_parent_position().clone_justify_items();
 | |
| 
 | |
|         if !parent_justify_items
 | |
|             .computed
 | |
|             .0
 | |
|             .contains(align::AlignFlags::LEGACY)
 | |
|         {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if parent_justify_items.computed == justify_items.computed {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         self.style
 | |
|             .mutate_position()
 | |
|             .set_computed_justify_items(parent_justify_items.computed);
 | |
|     }
 | |
| 
 | |
|     /// If '-webkit-appearance' is 'menulist' on a <select> element then
 | |
|     /// the computed value of 'line-height' is 'normal'.
 | |
|     ///
 | |
|     /// https://github.com/w3c/csswg-drafts/issues/3257
 | |
|     #[cfg(feature = "gecko")]
 | |
|     fn adjust_for_appearance<E>(&mut self, element: Option<E>)
 | |
|     where
 | |
|         E: TElement,
 | |
|     {
 | |
|         use crate::properties::longhands::appearance::computed_value::T as Appearance;
 | |
|         use crate::properties::longhands::line_height::computed_value::T as LineHeight;
 | |
| 
 | |
|         let box_ = self.style.get_box();
 | |
|         let appearance = match box_.clone_appearance() {
 | |
|             Appearance::Auto => box_.clone__moz_default_appearance(),
 | |
|             a => a,
 | |
|         };
 | |
| 
 | |
|         if appearance == Appearance::Menulist {
 | |
|             if self.style.get_inherited_text().clone_line_height() == LineHeight::normal() {
 | |
|                 return;
 | |
|             }
 | |
|             if self.style.pseudo.is_some() {
 | |
|                 return;
 | |
|             }
 | |
|             let is_html_select_element = element.map_or(false, |e| {
 | |
|                 e.is_html_element() && e.local_name() == &*atom!("select")
 | |
|             });
 | |
|             if !is_html_select_element {
 | |
|                 return;
 | |
|             }
 | |
|             self.style
 | |
|                 .mutate_inherited_text()
 | |
|                 .set_line_height(LineHeight::normal());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// A legacy ::marker (i.e. no 'content') without an author-specified 'font-family'
 | |
|     /// and 'list-style-type:disc|circle|square|disclosure-closed|disclosure-open'
 | |
|     /// is assigned 'font-family:-moz-bullet-font'. (This is for <ul><li> etc.)
 | |
|     /// We don't want synthesized italic/bold for this font, so turn that off too.
 | |
|     /// Likewise for 'letter/word-spacing' -- unless the author specified it then reset
 | |
|     /// them to their initial value because traditionally we never added such spacing
 | |
|     /// between a legacy bullet and the list item's content, so we keep that behavior
 | |
|     /// for web-compat reasons.
 | |
|     /// We intentionally don't check 'list-style-image' below since we want it to use
 | |
|     /// the same font as its fallback ('list-style-type') in case it fails to load.
 | |
|     #[cfg(feature = "gecko")]
 | |
|     fn adjust_for_marker_pseudo(&mut self) {
 | |
|         use crate::values::computed::counters::Content;
 | |
|         use crate::values::computed::font::{FontFamily, FontSynthesis};
 | |
|         use crate::values::computed::text::{LetterSpacing, WordSpacing};
 | |
| 
 | |
|         let is_legacy_marker = self.style.pseudo.map_or(false, |p| p.is_marker()) &&
 | |
|             self.style.get_list().clone_list_style_type().is_bullet() &&
 | |
|             self.style.get_counters().clone_content() == Content::Normal;
 | |
|         if !is_legacy_marker {
 | |
|             return;
 | |
|         }
 | |
|         if !self
 | |
|             .style
 | |
|             .flags
 | |
|             .get()
 | |
|             .contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_FAMILY)
 | |
|         {
 | |
|             self.style
 | |
|                 .mutate_font()
 | |
|                 .set_font_family(FontFamily::moz_bullet().clone());
 | |
| 
 | |
|             // FIXME(mats): We can remove this if support for font-synthesis is added to @font-face rules.
 | |
|             // Then we can add it to the @font-face rule in html.css instead.
 | |
|             // https://github.com/w3c/csswg-drafts/issues/6081
 | |
|             if !self
 | |
|                 .style
 | |
|                 .flags
 | |
|                 .get()
 | |
|                 .contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS)
 | |
|             {
 | |
|                 self.style
 | |
|                     .mutate_font()
 | |
|                     .set_font_synthesis(FontSynthesis::none());
 | |
|             }
 | |
|         }
 | |
|         if !self
 | |
|             .style
 | |
|             .flags
 | |
|             .get()
 | |
|             .contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_LETTER_SPACING)
 | |
|         {
 | |
|             self.style
 | |
|                 .mutate_inherited_text()
 | |
|                 .set_letter_spacing(LetterSpacing::normal());
 | |
|         }
 | |
|         if !self
 | |
|             .style
 | |
|             .flags
 | |
|             .get()
 | |
|             .contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_WORD_SPACING)
 | |
|         {
 | |
|             self.style
 | |
|                 .mutate_inherited_text()
 | |
|                 .set_word_spacing(WordSpacing::normal());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// Adjusts the style to account for various fixups that don't fit naturally
 | |
|     /// into the cascade.
 | |
|     ///
 | |
|     /// When comparing to Gecko, this is similar to the work done by
 | |
|     /// `ComputedStyle::ApplyStyleFixups`, plus some parts of
 | |
|     /// `nsStyleSet::GetContext`.
 | |
|     pub fn adjust<E>(&mut self, layout_parent_style: &ComputedValues, element: Option<E>)
 | |
|     where
 | |
|         E: TElement,
 | |
|     {
 | |
|         if cfg!(debug_assertions) {
 | |
|             if element.map_or(false, |e| e.is_pseudo_element()) {
 | |
|                 // It'd be nice to assert `self.style.pseudo == Some(&pseudo)`,
 | |
|                 // but we do resolve ::-moz-list pseudos on ::before / ::after
 | |
|                 // content, sigh.
 | |
|                 debug_assert!(self.style.pseudo.is_some(), "Someone really messed up");
 | |
|             }
 | |
|         }
 | |
|         // FIXME(emilio): The apply_declarations callsite in Servo's
 | |
|         // animation, and the font stuff for Gecko
 | |
|         // (Stylist::compute_for_declarations) should pass an element to
 | |
|         // cascade(), then we can make this assertion hold everywhere.
 | |
|         // debug_assert!(
 | |
|         //     element.is_some() || self.style.pseudo.is_some(),
 | |
|         //     "Should always have an element around for non-pseudo styles"
 | |
|         // );
 | |
| 
 | |
|         self.adjust_for_visited(element);
 | |
|         #[cfg(feature = "gecko")]
 | |
|         {
 | |
|             self.adjust_for_prohibited_display_contents(element);
 | |
|             self.adjust_for_fieldset_content(layout_parent_style);
 | |
|             // NOTE: It's important that this happens before
 | |
|             // adjust_for_overflow.
 | |
|             self.adjust_for_text_control_editing_root();
 | |
|         }
 | |
|         self.adjust_for_top_layer();
 | |
|         self.blockify_if_necessary(layout_parent_style, element);
 | |
|         self.adjust_for_webkit_line_clamp();
 | |
|         self.adjust_for_position();
 | |
|         self.adjust_for_overflow();
 | |
|         #[cfg(feature = "gecko")]
 | |
|         {
 | |
|             self.adjust_for_table_text_align();
 | |
|             self.adjust_for_justify_items();
 | |
|         }
 | |
|         #[cfg(feature = "servo")]
 | |
|         {
 | |
|             self.adjust_for_alignment(layout_parent_style);
 | |
|         }
 | |
|         self.adjust_for_border_width();
 | |
|         self.adjust_for_outline();
 | |
|         self.adjust_for_writing_mode(layout_parent_style);
 | |
|         #[cfg(feature = "gecko")]
 | |
|         {
 | |
|             self.adjust_for_ruby(layout_parent_style, element);
 | |
|         }
 | |
|         #[cfg(feature = "servo")]
 | |
|         {
 | |
|             self.adjust_for_text_decorations_in_effect();
 | |
|         }
 | |
|         #[cfg(feature = "gecko")]
 | |
|         {
 | |
|             self.adjust_for_appearance(element);
 | |
|             self.adjust_for_marker_pseudo();
 | |
|         }
 | |
|         self.set_bits();
 | |
|     }
 | |
| }
 |