diff --git a/components/style/matching.rs b/components/style/matching.rs index fceea1150ce..50ece880044 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -301,7 +301,6 @@ trait PrivateMatchMethods: TElement { new_values: &Arc, pseudo: Option<&PseudoElement>) -> ChildCascadeRequirement { - use properties::computed_value_flags::*; // Don't accumulate damage if we're in a restyle for reconstruction. if shared_context.traversal_flags.for_reconstruct() { return ChildCascadeRequirement::MustCascadeChildren; @@ -327,10 +326,8 @@ trait PrivateMatchMethods: TElement { match difference.change { StyleChange::Unchanged => { // We need to cascade the children in order to ensure the - // correct propagation of text-decoration-line, which is a reset - // property. - if old_values.flags.contains(HAS_TEXT_DECORATION_LINES) != - new_values.flags.contains(HAS_TEXT_DECORATION_LINES) { + // correct propagation of computed value flags. + if old_values.flags != new_values.flags { return ChildCascadeRequirement::MustCascadeChildren; } ChildCascadeRequirement::CanSkipCascade diff --git a/components/style/properties/computed_value_flags.rs b/components/style/properties/computed_value_flags.rs index a9e0eac520f..50ad887417a 100644 --- a/components/style/properties/computed_value_flags.rs +++ b/components/style/properties/computed_value_flags.rs @@ -18,5 +18,15 @@ bitflags! { /// text-decoration-line is a reset property, but gets propagated in the /// frame/box tree. const HAS_TEXT_DECORATION_LINES = 1 << 0, + + /// Whether line break inside should be suppressed. + /// + /// If this flag is set, the line should not be broken inside, + /// which means inlines act as if nowrap is set,
element is + /// suppressed, and blocks are inlinized. + /// + /// This bit is propagated to all children of line participants. + /// It is currently used by ruby to make its content unbreakable. + const SHOULD_SUPPRESS_LINEBREAK = 1 << 1, } } diff --git a/components/style/properties/longhand/box.mako.rs b/components/style/properties/longhand/box.mako.rs index 02122a2fc70..47d45d824cd 100644 --- a/components/style/properties/longhand/box.mako.rs +++ b/components/style/properties/longhand/box.mako.rs @@ -53,6 +53,34 @@ ) } + /// Returns whether an element with this display type is a line + /// participant, which means it may lay its children on the same + /// line as itself. + pub fn is_line_participant(&self) -> bool { + matches!(*self, + T::inline + % if product == "gecko": + | T::contents + | T::ruby + | T::ruby_base_container + % endif + ) + } + + /// Returns whether this "display" value is one of the types for + /// ruby. + #[cfg(feature = "gecko")] + pub fn is_ruby_type(&self) -> bool { + matches!(*self, T::ruby | T::ruby_base | T::ruby_text | + T::ruby_base_container | T::ruby_text_container) + } + + /// Returns whether this "display" value is a ruby level container. + #[cfg(feature = "gecko")] + pub fn is_ruby_level_container(&self) -> bool { + matches!(*self, T::ruby_base_container | T::ruby_text_container) + } + /// Convert this display into an equivalent block display. /// /// Also used for style adjustments. @@ -82,6 +110,25 @@ // Everything else becomes block. _ => T::block, } + + } + + /// Convert this display into an inline-outside display. + /// + /// Ideally it should implement spec: https://drafts.csswg.org/css-display/#inlinify + /// but the spec isn't stable enough, so we copy what Gecko does for now. + #[cfg(feature = "gecko")] + pub fn inlinify(&self) -> Self { + match *self { + T::block | T::flow_root => T::inline_block, + T::table => T::inline_table, + T::flex => T::inline_flex, + T::grid => T::inline_grid, + T::_moz_box => T::_moz_inline_box, + T::_moz_stack => T::_moz_inline_stack, + T::_webkit_box => T::_webkit_inline_box, + other => other, + } } } } diff --git a/components/style/properties/longhand/text.mako.rs b/components/style/properties/longhand/text.mako.rs index a7b30aafa5e..3be23f1681e 100644 --- a/components/style/properties/longhand/text.mako.rs +++ b/components/style/properties/longhand/text.mako.rs @@ -135,6 +135,7 @@ ${helpers.single_keyword("unicode-bidi", "normal embed isolate bidi-override isolate-override plaintext", animation_value_type="discrete", + need_clone="True", spec="https://drafts.csswg.org/css-writing-modes/#propdef-unicode-bidi")} <%helpers:longhand name="text-decoration-line" diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index e6fe8f8a125..9d710c1dcb3 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -2486,6 +2486,12 @@ impl<'a> StyleBuilder<'a> { -> Option<<&mut style_structs::${style_struct.name}> { self.${style_struct.ident}.get_if_mutated() } + + /// Reset the current `${style_struct.name}` style to its default value. + pub fn reset_${style_struct.name_lower}(&mut self, default: &'a ComputedValues) { + self.${style_struct.ident} = + StyleStructRef::Borrowed(default.${style_struct.name_lower}_arc()); + } % endfor /// Returns whether this computed style represents a floated object. @@ -2597,8 +2603,8 @@ bitflags! { /// present, non-inherited styles are reset to their initial values. const INHERIT_ALL = 0x01, - /// Whether to skip any root element and flex/grid item display style - /// fixup. + /// Whether to skip any display style fixup for root element, flex/grid + /// item, and ruby descendants. const SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP = 0x02, /// Whether to only cascade properties that are visited dependent. @@ -2985,7 +2991,8 @@ pub fn apply_declarations<'a, F, I>(device: &Device, { StyleAdjuster::new(&mut style) - .adjust(context.layout_parent_style, flags); + .adjust(context.layout_parent_style, + context.device.default_computed_values(), flags); } % if product == "gecko": diff --git a/components/style/style_adjuster.rs b/components/style/style_adjuster.rs index 3c7c85443e6..5b60231b2c4 100644 --- a/components/style/style_adjuster.rs +++ b/components/style/style_adjuster.rs @@ -12,6 +12,8 @@ use properties::longhands::display::computed_value::T as display; use properties::longhands::float::computed_value::T as float; use properties::longhands::overflow_x::computed_value::T as overflow; use properties::longhands::position::computed_value::T as position; +#[cfg(feature = "gecko")] +use properties::longhands::unicode_bidi::computed_value::T as unicode_bidi; /// An unsized struct that implements all the adjustment methods. @@ -321,6 +323,83 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { } } + #[cfg(feature = "gecko")] + fn should_suppress_linebreak(&self, layout_parent_style: &ComputedValues) -> bool { + use properties::computed_value_flags::SHOULD_SUPPRESS_LINEBREAK; + // Line break suppression should only be propagated to in-flow children. + if self.style.floated() || self.style.out_of_flow_positioned() { + return false; + } + let parent_display = layout_parent_style.get_box().clone_display(); + if layout_parent_style.flags.contains(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::ruby_base | display::ruby_text => true, + // Ruby base container and text container are breakable. + // 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 since they themselves are non-breakable. + display::ruby_base_container | display::ruby_text_container => 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(&mut self, + layout_parent_style: &ComputedValues, + default_computed_values: &'b ComputedValues, + flags: CascadeFlags) { + use properties::SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP; + use properties::computed_value_flags::SHOULD_SUPPRESS_LINEBREAK; + 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) { + self.style.flags.insert(SHOULD_SUPPRESS_LINEBREAK); + // Inlinify the display type if allowed. + if !flags.contains(SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP) { + let inline_display = self_display.inlinify(); + if self_display != inline_display { + self.style.mutate_box().set_display(inline_display); + } + } + } + // 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(default_computed_values); + self.style.reset_padding(default_computed_values); + } + // 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() { + unicode_bidi::normal | unicode_bidi::embed => Some(unicode_bidi::isolate), + unicode_bidi::bidi_override => Some(unicode_bidi::isolate_override), + _ => None, + }; + if let Some(new_value) = new_value { + self.style.mutate_text().set_unicode_bidi(new_value); + } + } + } + /// Adjusts the style to account for various fixups that don't fit naturally /// into the cascade. /// @@ -328,6 +407,7 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { /// `nsStyleContext::ApplyStyleFixups`. pub fn adjust(&mut self, layout_parent_style: &ComputedValues, + _default_computed_values: &'b ComputedValues, flags: CascadeFlags) { #[cfg(feature = "gecko")] { @@ -351,5 +431,10 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { self.adjust_for_outline(); self.adjust_for_writing_mode(layout_parent_style); self.adjust_for_text_decoration_lines(layout_parent_style); + #[cfg(feature = "gecko")] + { + self.adjust_for_ruby(layout_parent_style, + _default_computed_values, flags); + } } } diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index d87eada4a2f..18c396d724e 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -1727,6 +1727,9 @@ pub extern "C" fn Servo_ComputedValues_GetStyleBits(values: ServoComputedValuesB if flags.contains(HAS_TEXT_DECORATION_LINES) { result |= structs::NS_STYLE_HAS_TEXT_DECORATION_LINES as u64; } + if flags.contains(SHOULD_SUPPRESS_LINEBREAK) { + result |= structs::NS_STYLE_SUPPRESS_LINEBREAK as u64; + } result }