Bug 1359603 - Port text-combine-upright writing mode fixup to Servo. r=heycam draft
authorJ. Ryan Stinnett <jryans@gmail.com>
Mon, 24 Apr 2017 16:04:15 -0500
changeset 568931 73fa2a10106b7cdfb8dc3d8072934f34abaef4e2
parent 568930 3f40c6ad0fdd4d6928d108d6644e3c6a64f860db
child 568932 2553f996afefe4276aef86c61ec99e6b7fd62242
push id56024
push userbmo:jryans@gmail.com
push dateWed, 26 Apr 2017 19:05:54 +0000
reviewersheycam
bugs1359603
milestone55.0a1
Bug 1359603 - Port text-combine-upright writing mode fixup to Servo. r=heycam Ports the Gecko fixup for text-combine-upright writing mode to Servo. In addition, this passes the current pseudo element (if any) down to the cascade for use during the fixup process. MozReview-Commit-ID: BkHd4AvSsOt
servo/components/style/animation.rs
servo/components/style/matching.rs
servo/components/style/properties/longhand/inherited_text.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/style_adjuster.rs
servo/components/style/stylist.rs
servo/ports/geckolib/glue.rs
--- a/servo/components/style/animation.rs
+++ b/servo/components/style/animation.rs
@@ -436,16 +436,17 @@ fn compute_style_for_animation_step(cont
 
             let iter = || {
                 guard.declarations().iter().rev().map(|&(ref decl, _importance)| decl)
             };
 
             let computed =
                 properties::apply_declarations(&context.stylist.device,
                                                /* is_root = */ false,
+                                               /* pseudo = */ None,
                                                iter,
                                                previous_style,
                                                previous_style,
                                                /* cascade_info = */ None,
                                                &*context.error_reporter,
                                                font_metrics_provider,
                                                CascadeFlags::empty());
             computed
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -381,28 +381,28 @@ trait PrivateMatchMethods: TElement {
         }
     }
 
     fn cascade_with_rules(&self,
                           shared_context: &SharedStyleContext,
                           font_metrics_provider: &FontMetricsProvider,
                           rule_node: &StrongRuleNode,
                           primary_style: &ComputedStyle,
-                          is_pseudo: bool)
+                          pseudo: Option<&PseudoElement>)
                           -> Arc<ComputedValues> {
         let mut cascade_info = CascadeInfo::new();
         let mut cascade_flags = CascadeFlags::empty();
         if self.skip_root_and_item_based_display_fixup() {
             cascade_flags.insert(SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP)
         }
 
         // Grab the inherited values.
         let parent_el;
         let parent_data;
-        let style_to_inherit_from = if !is_pseudo {
+        let style_to_inherit_from = if pseudo.is_none() {
             parent_el = self.parent_element();
             parent_data = parent_el.as_ref().and_then(|e| e.borrow_data());
             let parent_values = parent_data.as_ref().map(|d| {
                 // Sometimes Gecko eagerly styles things without processing
                 // pending restyles first. In general we'd like to avoid this,
                 // but there can be good reasons (for example, needing to
                 // construct a frame for some small piece of newly-added
                 // content in order to do something specific with that frame,
@@ -429,54 +429,56 @@ trait PrivateMatchMethods: TElement {
         let style_to_inherit_from = style_to_inherit_from.map(|x| &**x);
         let layout_parent_style = layout_parent_style.map(|x| &**x);
 
         // Propagate the "can be fragmented" bit. It would be nice to
         // encapsulate this better.
         //
         // Note that this is not needed for pseudos since we already do that
         // when we resolve the non-pseudo style.
-        if !is_pseudo {
+        if pseudo.is_none() {
             if let Some(ref p) = layout_parent_style {
                 let can_be_fragmented =
                     p.is_multicol() ||
                     layout_parent_el.as_ref().unwrap().as_node().can_be_fragmented();
                 unsafe { self.as_node().set_can_be_fragmented(can_be_fragmented); }
             }
         }
 
         // Invoke the cascade algorithm.
         let values =
             Arc::new(cascade(&shared_context.stylist.device,
                              rule_node,
+                             pseudo,
                              &shared_context.guards,
                              style_to_inherit_from,
                              layout_parent_style,
                              Some(&mut cascade_info),
                              &*shared_context.error_reporter,
                              font_metrics_provider,
                              cascade_flags));
 
         cascade_info.finish(&self.as_node());
         values
     }
 
     fn cascade_internal(&self,
                         context: &StyleContext<Self>,
                         primary_style: &ComputedStyle,
+                        pseudo: Option<&PseudoElement>,
                         pseudo_style: Option<&ComputedStyle>)
                         -> Arc<ComputedValues> {
         // Grab the rule node.
         let rule_node = &pseudo_style.unwrap_or(primary_style).rules;
 
         self.cascade_with_rules(context.shared,
                                 &context.thread_local.font_metrics_provider,
                                 rule_node,
                                 primary_style,
-                                pseudo_style.is_some())
+                                pseudo)
     }
 
     /// Computes values and damage for the primary or pseudo style of an element,
     /// setting them on the ElementData.
     fn cascade_primary_or_pseudo(&self,
                                  context: &mut StyleContext<Self>,
                                  data: &mut ElementData,
                                  pseudo: Option<&PseudoElement>,
@@ -497,16 +499,17 @@ trait PrivateMatchMethods: TElement {
         let mut old_values = match pseudo_style {
             Some(ref mut s) => s.values.take(),
             None => primary_style.values.take(),
         };
 
         // Compute the new values.
         let mut new_values = self.cascade_internal(context,
                                                    primary_style,
+                                                   pseudo,
                                                    pseudo_style.as_ref().map(|s| &**s));
 
         // Handle animations.
         if animate && !context.shared.traversal_flags.for_animation_only() {
             self.process_animations(context,
                                     &mut old_values,
                                     &mut new_values,
                                     primary_style,
@@ -529,33 +532,34 @@ trait PrivateMatchMethods: TElement {
     }
 
     /// get_after_change_style removes the transition rules from the ComputedValues.
     /// If there is no transition rule in the ComputedValues, it returns None.
     #[cfg(feature = "gecko")]
     fn get_after_change_style(&self,
                               context: &mut StyleContext<Self>,
                               primary_style: &ComputedStyle,
+                              pseudo: Option<&PseudoElement>,
                               pseudo_style: Option<&ComputedStyle>)
                               -> Option<Arc<ComputedValues>> {
         let relevant_style = pseudo_style.unwrap_or(primary_style);
         let rule_node = &relevant_style.rules;
         let without_transition_rules =
             context.shared.stylist.rule_tree.remove_transition_rule_if_applicable(rule_node);
         if without_transition_rules == *rule_node {
             // We don't have transition rule in this case, so return None to let the caller
             // use the original ComputedValues.
             return None;
         }
 
         Some(self.cascade_with_rules(context.shared,
                                      &context.thread_local.font_metrics_provider,
                                      &without_transition_rules,
                                      primary_style,
-                                     pseudo_style.is_some()))
+                                     pseudo))
     }
 
     #[cfg(feature = "gecko")]
     fn needs_animations_update(&self,
                                old_values: &Option<Arc<ComputedValues>>,
                                new_values: &Arc<ComputedValues>,
                                pseudo: Option<&PseudoElement>) -> bool {
         let ref new_box_style = new_values.get_box();
@@ -595,17 +599,17 @@ trait PrivateMatchMethods: TElement {
         if self.needs_animations_update(old_values, new_values, pseudo) {
             tasks.insert(CSS_ANIMATIONS);
         }
 
         let before_change_style = if self.might_need_transitions_update(&old_values.as_ref(),
                                                                         new_values,
                                                                         pseudo) {
             let after_change_style = if self.has_css_transitions(pseudo) {
-                self.get_after_change_style(context, primary_style, pseudo_style)
+                self.get_after_change_style(context, primary_style, pseudo, pseudo_style)
             } else {
                 None
             };
 
             // In order to avoid creating a SequentialTask for transitions which may not be updated,
             // we check it per property to make sure Gecko side will really update transition.
             let needs_transitions_update = {
                 // We borrow new_values here, so need to add a scope to make sure we release it
@@ -1320,30 +1324,31 @@ pub trait MatchMethods : TElement {
         }
     }
 
     /// Returns computed values without animation and transition rules.
     fn get_base_style(&self,
                       shared_context: &SharedStyleContext,
                       font_metrics_provider: &FontMetricsProvider,
                       primary_style: &ComputedStyle,
+                      pseudo: Option<&PseudoElement>,
                       pseudo_style: Option<&ComputedStyle>)
                       -> Arc<ComputedValues> {
         let relevant_style = pseudo_style.unwrap_or(primary_style);
         let rule_node = &relevant_style.rules;
         let without_animation_rules =
             shared_context.stylist.rule_tree.remove_animation_and_transition_rules(rule_node);
         if without_animation_rules == *rule_node {
             // Note that unwrapping here is fine, because the style is
             // only incomplete during the styling process.
             return relevant_style.values.as_ref().unwrap().clone();
         }
 
         self.cascade_with_rules(shared_context,
                                 font_metrics_provider,
                                 &without_animation_rules,
                                 primary_style,
-                                pseudo_style.is_some())
+                                pseudo)
     }
 
 }
 
 impl<E: TElement> MatchMethods for E {}
--- a/servo/components/style/properties/longhand/inherited_text.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_text.mako.rs
@@ -1252,17 +1252,17 @@
 ${helpers.single_keyword("ruby-position", "over under",
                          products="gecko", animation_value_type="none",
                          spec="https://drafts.csswg.org/css-ruby/#ruby-position-property")}
 
 // CSS Writing Modes Module Level 3
 // https://drafts.csswg.org/css-writing-modes-3/
 
 ${helpers.single_keyword("text-combine-upright", "none all",
-                         products="gecko", animation_value_type="none",
+                         products="gecko", animation_value_type="none", need_clone=True,
                          spec="https://drafts.csswg.org/css-writing-modes-3/#text-combine-upright")}
 
 // SVG 1.1: Section 11 - Painting: Filling, Stroking and Marker Symbols
 ${helpers.single_keyword("text-rendering",
                          "auto optimizespeed optimizelegibility geometricprecision",
                          animation_value_type="none",
                          spec="https://www.w3.org/TR/SVG11/painting.html#TextRenderingProperty")}
 
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -24,16 +24,17 @@ use computed_values;
 use font_metrics::FontMetricsProvider;
 #[cfg(feature = "gecko")] use gecko_bindings::bindings;
 #[cfg(feature = "gecko")] use gecko_bindings::structs::{self, nsCSSPropertyID};
 #[cfg(feature = "servo")] use logical_geometry::{LogicalMargin, PhysicalSide};
 use logical_geometry::WritingMode;
 use media_queries::Device;
 use parser::{LengthParsingMode, Parse, ParserContext};
 use properties::animated_properties::TransitionProperty;
+use selector_parser::PseudoElement;
 #[cfg(feature = "servo")] use servo_config::prefs::PREFS;
 use shared_lock::StylesheetGuards;
 use style_traits::ToCss;
 use stylesheets::{CssRuleType, Origin, UrlExtraData};
 #[cfg(feature = "servo")] use values::Either;
 use values::{HasViewportPercentage, computed};
 use cascade_info::CascadeInfo;
 use rule_tree::StrongRuleNode;
@@ -2103,16 +2104,17 @@ bitflags! {
 ///
 ///   * `parent_style`: The parent style, if applicable; if `None`, this is the root node.
 ///
 /// Returns the computed values.
 ///   * `flags`: Various flags.
 ///
 pub fn cascade(device: &Device,
                rule_node: &StrongRuleNode,
+               pseudo: Option<<&PseudoElement>,
                guards: &StylesheetGuards,
                parent_style: Option<<&ComputedValues>,
                layout_parent_style: Option<<&ComputedValues>,
                cascade_info: Option<<&mut CascadeInfo>,
                error_reporter: &ParseErrorReporter,
                font_metrics_provider: &FontMetricsProvider,
                flags: CascadeFlags)
                -> ComputedValues {
@@ -2148,30 +2150,32 @@ pub fn cascade(device: &Device,
                     } else {
                         None
                     }
                 })
         })
     };
     apply_declarations(device,
                        is_root_element,
+                       pseudo,
                        iter_declarations,
                        inherited_style,
                        layout_parent_style,
                        cascade_info,
                        error_reporter,
                        font_metrics_provider,
                        flags)
 }
 
 /// NOTE: This function expects the declaration with more priority to appear
 /// first.
 #[allow(unused_mut)] // conditionally compiled code for "position"
 pub fn apply_declarations<'a, F, I>(device: &Device,
                                     is_root_element: bool,
+                                    pseudo: Option<<&PseudoElement>,
                                     iter_declarations: F,
                                     inherited_style: &ComputedValues,
                                     layout_parent_style: &ComputedValues,
                                     mut cascade_info: Option<<&mut CascadeInfo>,
                                     error_reporter: &ParseErrorReporter,
                                     font_metrics_provider: &FontMetricsProvider,
                                     flags: CascadeFlags)
                                     -> ComputedValues
@@ -2368,17 +2372,17 @@ pub fn apply_declarations<'a, F, I>(devi
                                                  &mut cascade_info,
                                                  error_reporter);
             }
         % endif
     % endfor
 
     let mut style = context.style;
 
-    StyleAdjuster::new(&mut style, is_root_element)
+    StyleAdjuster::new(&mut style, is_root_element, pseudo)
         .adjust(context.layout_parent_style,
                 flags.contains(SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP));
 
     % if product == "gecko":
         // FIXME(emilio): This is effectively creating a new nsStyleBackground
         // and nsStyleSVG per element. We should only do this when necessary
         // using the `seen` bitfield!
         style.mutate_background().fill_arrays();
--- a/servo/components/style/style_adjuster.rs
+++ b/servo/components/style/style_adjuster.rs
@@ -6,30 +6,33 @@
 //! for it to adhere to the CSS spec.
 
 use app_units::Au;
 use properties::{self, ComputedValues};
 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 selector_parser::PseudoElement;
 
 /// An unsized struct that implements all the adjustment methods.
+#[allow(dead_code)] // `pseudo` field is currently unused by Servo
 pub struct StyleAdjuster<'a> {
     style: &'a mut ComputedValues,
     is_root_element: bool,
+    pseudo: Option<&'a PseudoElement>,
 }
 
 impl<'a> StyleAdjuster<'a> {
     /// Trivially constructs a new StyleAdjuster.
-    pub fn new(style: &'a mut ComputedValues, is_root_element: bool) -> Self {
+    pub fn new(style: &'a mut ComputedValues, is_root_element: bool, pseudo: Option<&'a PseudoElement>) -> Self {
         StyleAdjuster {
             style: style,
             is_root_element: is_root_element,
+            pseudo: pseudo,
         }
     }
 
     /// 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.
     ///
@@ -80,16 +83,36 @@ impl<'a> StyleAdjuster<'a> {
         let blockified_display =
             display.equivalent_block_display(self.is_root_element);
         if display != blockified_display {
             self.style.mutate_box().set_adjusted_display(blockified_display,
                                                          is_item_or_root);
         }
     }
 
+    /// Change writing mode of text frame for text-combine-upright.
+    /// It is safe to look at the parent's style because we are looking at
+    /// inherited properties, and ::-moz-text never matches any rules.
+    #[cfg(feature = "gecko")]
+    fn adjust_for_text_combine_upright(&mut self,
+                                       layout_parent_style: &ComputedValues) {
+        if let Some(p) = self.pseudo {
+            if *p.as_atom() == atom!(":-moz-text") {
+                use computed_values::text_combine_upright::T as text_combine_upright;
+                use computed_values::writing_mode::T as writing_mode;
+                let parent_writing_mode = layout_parent_style.get_inheritedbox().clone_writing_mode();
+                let parent_text_combine_upright = layout_parent_style.get_inheritedtext().clone_text_combine_upright();
+                if parent_writing_mode != writing_mode::horizontal_tb &&
+                   parent_text_combine_upright == text_combine_upright::all {
+                    self.style.mutate_inheritedbox().set_writing_mode(writing_mode::horizontal_tb);
+                }
+            }
+        }
+    }
+
     /// 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]
     ///
@@ -218,20 +241,24 @@ impl<'a> StyleAdjuster<'a> {
         if overflow_x != original_overflow_x ||
            overflow_y != original_overflow_y {
             let mut box_style = self.style.mutate_box();
             box_style.set_overflow_x(overflow_x);
             box_style.set_overflow_y(overflow_y);
         }
     }
 
-    /// Adjusts the style to account for display fixups.
+    /// Adjusts the style to account for various fixups the don't fit naturally into the cascade.
     pub fn adjust(mut self,
                   layout_parent_style: &ComputedValues,
                   skip_root_and_element_display_fixup: bool) {
+        #[cfg(feature = "gecko")]
+        {
+            self.adjust_for_text_combine_upright(layout_parent_style);
+        }
         self.adjust_for_top_layer();
         self.blockify_if_necessary(layout_parent_style,
                                    skip_root_and_element_display_fixup);
         self.adjust_for_writing_mode(layout_parent_style);
         self.adjust_for_position();
 
         self.adjust_for_overflow();
         #[cfg(feature = "gecko")]
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -406,16 +406,17 @@ impl Stylist {
         //
         // In practice, I don't think any anonymous content can be a direct
         // descendant of a display: contents element where display: contents is
         // the actual used value, and the computed value of it would need
         // blockification.
         let computed =
             properties::cascade(&self.device,
                                 &rule_node,
+                                Some(pseudo),
                                 guards,
                                 parent.map(|p| &**p),
                                 parent.map(|p| &**p),
                                 None,
                                 &RustLogReporter,
                                 font_metrics,
                                 cascade_flags);
         ComputedStyle::new(rule_node, Arc::new(computed))
@@ -524,16 +525,17 @@ impl Stylist {
 
         // Read the comment on `precomputed_values_for_pseudo` to see why it's
         // difficult to assert that display: contents nodes never arrive here
         // (tl;dr: It doesn't apply for replaced elements and such, but the
         // computed value is still "contents").
         let computed =
             properties::cascade(&self.device,
                                 &rule_node,
+                                Some(pseudo),
                                 guards,
                                 Some(&**parent),
                                 Some(&**parent),
                                 None,
                                 &RustLogReporter,
                                 font_metrics,
                                 CascadeFlags::empty());
 
@@ -854,16 +856,17 @@ impl Stylist {
                                                           CascadeLevel::StyleAttributeNormal)
         ];
         let rule_node =
             self.rule_tree.insert_ordered_rules(v.into_iter().map(|a| (a.source, a.level)));
 
         let metrics = get_metrics_provider_for_product();
         Arc::new(properties::cascade(&self.device,
                                      &rule_node,
+                                     None,
                                      guards,
                                      Some(parent_style),
                                      Some(parent_style),
                                      None,
                                      &RustLogReporter,
                                      &metrics,
                                      CascadeFlags::empty()))
     }
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -459,17 +459,17 @@ pub extern "C" fn Servo_StyleSet_GetBase
             let style = pseudos.get(p);
             debug_assert!(style.is_some());
             style
         }
         None => None,
     };
 
     let provider = get_metrics_provider_for_product();
-    element.get_base_style(shared_context, &provider, &styles.primary, pseudo_style)
+    element.get_base_style(shared_context, &provider, &styles.primary, pseudo.as_ref(), pseudo_style)
            .into_strong()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ComputedValues_ExtractAnimationValue(computed_values: ServoComputedValuesBorrowed,
                                                              property_id: nsCSSPropertyID)
                                                              -> RawServoAnimationValueStrong
 {