Add style adjustments for ruby. r=heycam
draft
Add style adjustments for ruby. r=heycam
MozReview-Commit-ID: 2bE8kXW2us2
--- 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();