Add style adjustments for ruby. r=heycam draft
authorXidorn Quan <me@upsuper.org>
Wed, 05 Jul 2017 14:02:15 +1000
changeset 605032 20296a52990cfa2861a664f6b7663c45817384a4
parent 605031 990a778b58b62b0f8fc438a1898638d6f5850bbe
child 605033 634c5b55040d85cac3284704ae95548ebbe3ebcc
push id67276
push userxquan@mozilla.com
push dateThu, 06 Jul 2017 22:38:30 +0000
reviewersheycam
milestone56.0a1
Add style adjustments for ruby. r=heycam MozReview-Commit-ID: 2bE8kXW2us2
servo/components/style/matching.rs
servo/components/style/properties/computed_value_flags.rs
servo/components/style/properties/longhand/box.mako.rs
servo/components/style/properties/longhand/text.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/style_adjuster.rs
servo/ports/geckolib/glue.rs
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -807,18 +807,16 @@ trait PrivateMatchMethods: TElement {
     #[cfg(feature = "gecko")]
     fn accumulate_damage_for(&self,
                              shared_context: &SharedStyleContext,
                              restyle: &mut RestyleData,
                              old_values: &ComputedValues,
                              new_values: &Arc<ComputedValues>,
                              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;
         }
 
         // If an ancestor is already getting reconstructed by Gecko's top-down
         // frame constructor, no need to apply damage.  Similarly if we already
         // have an explicitly stored ReconstructFrame hint.
@@ -834,20 +832,18 @@ trait PrivateMatchMethods: TElement {
 
         if !skip_applying_damage {
             restyle.damage |= difference.damage;
         }
 
         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
             },
             StyleChange::Changed => ChildCascadeRequirement::MustCascadeChildren,
         }
     }
 
--- a/servo/components/style/properties/computed_value_flags.rs
+++ b/servo/components/style/properties/computed_value_flags.rs
@@ -13,10 +13,20 @@ bitflags! {
     /// we might want to add a function to handle this.
     pub flags ComputedValueFlags: u8 {
         /// Whether the style or any of the ancestors has a text-decoration-line
         /// property that should get propagated to descendants.
         ///
         /// 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, <br> 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,
     }
 }
--- a/servo/components/style/properties/longhand/box.mako.rs
+++ b/servo/components/style/properties/longhand/box.mako.rs
@@ -48,16 +48,44 @@
                          | T::inline_flex
                          % if product == "gecko":
                          | T::grid
                          | T::inline_grid
                          % endif
                 )
             }
 
+            /// 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.
             pub fn equivalent_block_display(&self, _is_root_element: bool) -> Self {
                 match *self {
                     // Values that have a corresponding block-outside version.
                     T::inline_table => T::table,
                     T::inline_flex => T::flex,
@@ -77,16 +105,35 @@
                     T::none | T::block | T::flex | T::list_item | T::table => *self,
                     % if product == "gecko":
                     T::contents | T::flow_root | T::grid | T::_webkit_box => *self,
                     % endif
 
                     // 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,
+                }
             }
         }
     }
 
     #[allow(non_camel_case_types)]
     #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
     pub enum SpecifiedValue {
--- a/servo/components/style/properties/longhand/text.mako.rs
+++ b/servo/components/style/properties/longhand/text.mako.rs
@@ -141,16 +141,17 @@
             Ok(())
         }
     }
 </%helpers:longhand>
 
 ${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"
                    custom_cascade="${product == 'servo'}"
                    need_clone=True
                    animation_value_type="discrete"
                    spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-line">
     use std::fmt;
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -2411,16 +2411,22 @@ impl<'a> StyleBuilder<'a> {
         }
 
         /// Gets a mutable view of the current `${style_struct.name}` style,
         /// only if it's been mutated before.
         pub fn get_${style_struct.name_lower}_if_mutated(&mut self)
                                                          -> 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.
     pub fn floated(&self) -> bool {
         self.get_box().clone_float() != longhands::float::computed_value::T::none
     }
 
     /// Returns whether this computed style represents an out of flow-positioned
@@ -2523,18 +2529,18 @@ static CASCADE_PROPERTY: [CascadePropert
 
 bitflags! {
     /// A set of flags to tweak the behavior of the `cascade` function.
     pub flags CascadeFlags: u8 {
         /// Whether to inherit all styles from the parent. If this flag is not
         /// 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.
         const VISITED_DEPENDENT_ONLY = 0x04,
 
         /// Whether the given element we're styling is the document element,
         /// that is, matches :root.
         ///
@@ -2910,17 +2916,18 @@ pub fn apply_declarations<'a, F, I>(devi
             }
         % endif
     % endfor
 
     let mut style = context.style;
 
     {
         StyleAdjuster::new(&mut style)
-            .adjust(context.layout_parent_style, flags);
+            .adjust(context.layout_parent_style,
+                    context.device.default_computed_values(), flags);
     }
 
     % if product == "gecko":
         if let Some(ref mut bg) = style.get_background_if_mutated() {
             bg.fill_arrays();
         }
 
         if let Some(ref mut svg) = style.get_svg_if_mutated() {
--- a/servo/components/style/style_adjuster.rs
+++ b/servo/components/style/style_adjuster.rs
@@ -7,16 +7,17 @@
 
 use app_units::Au;
 use properties::{self, CascadeFlags, ComputedValues};
 use properties::{IS_ROOT_ELEMENT, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, StyleBuilder};
 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;
+use properties::longhands::unicode_bidi::computed_value::T as unicode_bidi;
 
 
 /// An unsized struct that implements all the adjustment methods.
 pub struct StyleAdjuster<'a, 'b: 'a> {
     style: &'a mut StyleBuilder<'b>,
 }
 
 impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
@@ -316,23 +317,101 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
     fn adjust_for_text_decoration_lines(&mut self, layout_parent_style: &ComputedValues) {
         use properties::computed_value_flags::HAS_TEXT_DECORATION_LINES;
         if layout_parent_style.flags.contains(HAS_TEXT_DECORATION_LINES) ||
            !self.style.get_text().clone_text_decoration_line().is_empty() {
             self.style.flags.insert(HAS_TEXT_DECORATION_LINES);
         }
     }
 
+    #[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.
     ///
     /// When comparing to Gecko, this is similar to the work done by
     /// `nsStyleContext::ApplyStyleFixups`.
     pub fn adjust(&mut self,
                   layout_parent_style: &ComputedValues,
+                  default_computed_values: &'b ComputedValues,
                   flags: CascadeFlags) {
         #[cfg(feature = "gecko")]
         {
             self.adjust_for_prohibited_display_contents(flags);
         }
         self.adjust_for_top_layer();
         self.blockify_if_necessary(layout_parent_style, flags);
         self.adjust_for_position();
@@ -346,10 +425,15 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
         #[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);
         self.adjust_for_text_decoration_lines(layout_parent_style);
+        #[cfg(feature = "gecko")]
+        {
+            self.adjust_for_ruby(layout_parent_style,
+                                 default_computed_values, flags);
+        }
     }
 }
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -1689,16 +1689,19 @@ pub extern "C" fn Servo_ComputedValues_G
 #[no_mangle]
 pub extern "C" fn Servo_ComputedValues_GetStyleBits(values: ServoComputedValuesBorrowed) -> u64 {
     use style::properties::computed_value_flags::*;
     let flags = ComputedValues::as_arc(&values).flags;
     let mut result = 0;
     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
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ComputedValues_SpecifiesAnimationsOrTransitions(values: ServoComputedValuesBorrowed)
                                                                         -> bool {
     let values = ComputedValues::as_arc(&values);
     let b = values.get_box();