diff options
93 files changed, 2715 insertions, 1753 deletions
diff --git a/Cargo.lock b/Cargo.lock index 4c49f2fdf0a..8d7bea14ee6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1532,7 +1532,7 @@ name = "fallible" version = "0.0.1" dependencies = [ "hashglobe", - "smallvec 1.0.0", + "smallvec 1.2.0", ] [[package]] @@ -1751,7 +1751,6 @@ dependencies = [ "log", "malloc_size_of", "net_traits", - "ordered-float", "range", "serde", "servo-fontconfig", @@ -3313,7 +3312,7 @@ dependencies = [ "serde_bytes", "servo_arc", "smallbitvec", - "smallvec 1.0.0", + "smallvec 1.2.0", "string_cache", "thin-slice", "time", @@ -4912,7 +4911,7 @@ dependencies = [ "phf_codegen", "precomputed-hash", "servo_arc", - "smallvec 1.0.0", + "smallvec 1.2.0", "thin-slice", "to_shmem", "to_shmem_derive", @@ -5507,9 +5506,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.0.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ecf3b85f68e8abaa7555aa5abdb1153079387e60b718283d732f03897fcfc86" +checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" [[package]] name = "smithay-client-toolkit" @@ -5659,7 +5658,6 @@ dependencies = [ "num-integer", "num-traits", "num_cpus", - "ordered-float", "owning_ref", "parking_lot", "precomputed-hash", @@ -5672,7 +5670,7 @@ dependencies = [ "servo_config", "servo_url", "smallbitvec", - "smallvec 1.0.0", + "smallvec 1.2.0", "string_cache", "style_derive", "style_traits", @@ -6017,7 +6015,7 @@ dependencies = [ "cssparser", "servo_arc", "smallbitvec", - "smallvec 1.0.0", + "smallvec 1.2.0", "string_cache", "thin-slice", ] @@ -6784,7 +6782,7 @@ dependencies = [ "rendy-descriptor", "rendy-memory", "serde", - "smallvec 1.0.0", + "smallvec 1.2.0", "vec_map", ] diff --git a/components/gfx/Cargo.toml b/components/gfx/Cargo.toml index bb47f664446..08e0e4d6cc7 100644 --- a/components/gfx/Cargo.toml +++ b/components/gfx/Cargo.toml @@ -27,7 +27,6 @@ libc = "0.2" log = "0.4" malloc_size_of = { path = "../malloc_size_of" } net_traits = {path = "../net_traits"} -ordered-float = "1.0" range = {path = "../range"} serde = "1.0" servo_arc = {path = "../servo_arc"} diff --git a/components/gfx/font.rs b/components/gfx/font.rs index 439784ba680..2de1a598cda 100644 --- a/components/gfx/font.rs +++ b/components/gfx/font.rs @@ -13,7 +13,6 @@ use crate::text::shaping::ShaperMethods; use crate::text::Shaper; use app_units::Au; use euclid::default::{Point2D, Rect, Size2D}; -use ordered_float::NotNan; use servo_atoms::Atom; use smallvec::SmallVec; use std::borrow::ToOwned; @@ -38,6 +37,7 @@ macro_rules! ot_tag { pub const GPOS: u32 = ot_tag!('G', 'P', 'O', 'S'); pub const GSUB: u32 = ot_tag!('G', 'S', 'U', 'B'); pub const KERN: u32 = ot_tag!('k', 'e', 'r', 'n'); +pub const LAST_RESORT_GLYPH_ADVANCE: FractionalPixel = 10.0; static TEXT_SHAPING_PERFORMANCE_COUNTER: AtomicUsize = AtomicUsize::new(0); @@ -197,7 +197,7 @@ pub struct ShapingOptions { /// NB: You will probably want to set the `IGNORE_LIGATURES_SHAPING_FLAG` if this is non-null. pub letter_spacing: Option<Au>, /// Spacing to add between each word. Corresponds to the CSS 2.1 `word-spacing` property. - pub word_spacing: (Au, NotNan<f32>), + pub word_spacing: Au, /// The Unicode script property of the characters in this run. pub script: Script, /// Various flags. @@ -278,8 +278,7 @@ impl Font { let mut advance = Au::from_f64_px(self.glyph_h_advance(glyph_id)); if character == ' ' { // https://drafts.csswg.org/css-text-3/#word-spacing-property - let (length, percent) = options.word_spacing; - advance = (advance + length) + Au((advance.0 as f32 * percent.into_inner()) as i32); + advance += options.word_spacing; } if let Some(letter_spacing) = options.letter_spacing { advance += letter_spacing; @@ -343,7 +342,7 @@ impl Font { .or_insert_with(|| { match self.handle.glyph_h_advance(glyph) { Some(adv) => adv, - None => 10f64 as FractionalPixel, // FIXME: Need fallback strategy + None => LAST_RESORT_GLYPH_ADVANCE as FractionalPixel, // FIXME: Need fallback strategy } }) } @@ -516,8 +515,8 @@ impl RunMetrics { RunMetrics { advance_width: advance, bounding_box: bounds, - ascent: ascent, - descent: descent, + ascent, + descent, } } } diff --git a/components/gfx/text/shaping/harfbuzz.rs b/components/gfx/text/shaping/harfbuzz.rs index 9599dd61a12..47cf337011b 100644 --- a/components/gfx/text/shaping/harfbuzz.rs +++ b/components/gfx/text/shaping/harfbuzz.rs @@ -434,9 +434,7 @@ impl Shaper { // We elect to only space the two required code points. if character == ' ' || character == '\u{a0}' { // https://drafts.csswg.org/css-text-3/#word-spacing-property - let (length, percent) = options.word_spacing; - advance = - (advance + length) + Au::new((advance.0 as f32 * percent.into_inner()) as i32); + advance += options.word_spacing; } advance diff --git a/components/layout/construct.rs b/components/layout/construct.rs index 5c9ff1256dd..aabb2e69b67 100644 --- a/components/layout/construct.rs +++ b/components/layout/construct.rs @@ -840,7 +840,10 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode> match text_content { TextContent::Text(string) => { - let info = Box::new(UnscannedTextFragmentInfo::new(string, node.selection())); + let info = Box::new(UnscannedTextFragmentInfo::new( + string.into(), + node.selection(), + )); let specific_fragment_info = SpecificFragmentInfo::UnscannedText(info); fragments .fragments @@ -857,7 +860,8 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode> for content_item in content_items.into_iter() { let specific_fragment_info = match content_item { ContentItem::String(string) => { - let info = Box::new(UnscannedTextFragmentInfo::new(string, None)); + let info = + Box::new(UnscannedTextFragmentInfo::new(string.into(), None)); SpecificFragmentInfo::UnscannedText(info) }, content_item => { diff --git a/components/layout/display_list/conversions.rs b/components/layout/display_list/conversions.rs index 2759c5f65ac..ecf20be3869 100644 --- a/components/layout/display_list/conversions.rs +++ b/components/layout/display_list/conversions.rs @@ -95,7 +95,7 @@ impl ToLayout for TransformStyle { type Type = wr::TransformStyle; fn to_layout(&self) -> Self::Type { match *self { - TransformStyle::Auto | TransformStyle::Flat => wr::TransformStyle::Flat, + TransformStyle::Flat => wr::TransformStyle::Flat, TransformStyle::Preserve3d => wr::TransformStyle::Preserve3D, } } diff --git a/components/layout/table.rs b/components/layout/table.rs index 190f6693256..80f5ac43032 100644 --- a/components/layout/table.rs +++ b/components/layout/table.rs @@ -309,7 +309,7 @@ impl Flow for TableFlow { percentage: match *specified_inline_size { Size::Auto => 0.0, Size::LengthPercentage(ref lp) => { - lp.0.as_percentage().map_or(0.0, |p| p.0) + lp.0.to_percentage().map_or(0.0, |p| p.0) }, }, preferred: Au(0), diff --git a/components/layout/table_row.rs b/components/layout/table_row.rs index 58fb40bde3a..0d959b7456c 100644 --- a/components/layout/table_row.rs +++ b/components/layout/table_row.rs @@ -437,7 +437,7 @@ impl Flow for TableRowFlow { .unwrap_or(child_base.intrinsic_inline_sizes.minimum_inline_size), percentage: match child_specified_inline_size { Size::Auto => 0.0, - Size::LengthPercentage(ref lp) => lp.0.as_percentage().map_or(0.0, |p| p.0), + Size::LengthPercentage(ref lp) => lp.0.to_percentage().map_or(0.0, |p| p.0), }, preferred: child_base.intrinsic_inline_sizes.preferred_inline_size, constrained: match child_specified_inline_size { diff --git a/components/layout/text.rs b/components/layout/text.rs index ef9515b0726..1d9fae80297 100644 --- a/components/layout/text.rs +++ b/components/layout/text.rs @@ -10,7 +10,7 @@ use crate::fragment::{ScannedTextFragmentInfo, SpecificFragmentInfo, UnscannedTe use crate::inline::{InlineFragmentNodeFlags, InlineFragments}; use crate::linked_list::split_off_head; use app_units::Au; -use gfx::font::{FontMetrics, FontRef, RunMetrics, ShapingFlags, ShapingOptions}; +use gfx::font::{self, FontMetrics, FontRef, RunMetrics, ShapingFlags, ShapingOptions}; use gfx::text::glyph::ByteIndex; use gfx::text::text_run::TextRun; use gfx::text::util::{self, CompressionMode}; @@ -195,7 +195,24 @@ impl TextRunScanner { }; text_transform = inherited_text_style.text_transform; letter_spacing = inherited_text_style.letter_spacing; - word_spacing = inherited_text_style.word_spacing.to_hash_key(); + word_spacing = inherited_text_style + .word_spacing + .to_length() + .map(|l| l.into()) + .unwrap_or_else(|| { + let space_width = font_group + .borrow_mut() + .find_by_codepoint(&mut font_context, ' ') + .and_then(|font| { + let font = font.borrow(); + font.glyph_index(' ') + .map(|glyph_id| font.glyph_h_advance(glyph_id)) + }) + .unwrap_or(font::LAST_RESORT_GLYPH_ADVANCE); + inherited_text_style + .word_spacing + .to_used_value(Au::from_f64_px(space_width)) + }); text_rendering = inherited_text_style.text_rendering; word_break = inherited_text_style.word_break; } diff --git a/components/layout_2020/flow/inline.rs b/components/layout_2020/flow/inline.rs index ba9453a5b80..58dfb6581db 100644 --- a/components/layout_2020/flow/inline.rs +++ b/components/layout_2020/flow/inline.rs @@ -159,8 +159,12 @@ impl InlineFormattingContext { } fn add_lengthpercentage(&mut self, lp: LengthPercentage) { - self.add_length(lp.length_component()); - self.current_line_percentages += lp.percentage_component(); + if let Some(l) = lp.to_length() { + self.add_length(l); + } + if let Some(p) = lp.to_percentage() { + self.current_line_percentages += p; + } } fn add_length(&mut self, l: Length) { @@ -601,13 +605,6 @@ impl TextRun { flags.insert(ShapingFlags::KEEP_ALL_FLAG); } - let shaping_options = gfx::font::ShapingOptions { - letter_spacing, - word_spacing: inherited_text_style.word_spacing.to_hash_key(), - script: unicode_script::Script::Common, - flags, - }; - crate::context::with_thread_local_font_context(layout_context, |font_context| { let font_group = font_context.font_group(font_style); let font = font_group @@ -616,6 +613,25 @@ impl TextRun { .expect("could not find font"); let mut font = font.borrow_mut(); + let word_spacing = &inherited_text_style.word_spacing; + let word_spacing = word_spacing + .to_length() + .map(|l| l.into()) + .unwrap_or_else(|| { + let space_width = font + .glyph_index(' ') + .map(|glyph_id| font.glyph_h_advance(glyph_id)) + .unwrap_or(gfx::font::LAST_RESORT_GLYPH_ADVANCE); + word_spacing.to_used_value(Au::from_f64_px(space_width)) + }); + + let shaping_options = gfx::font::ShapingOptions { + letter_spacing, + word_spacing, + script: unicode_script::Script::Common, + flags, + }; + let (runs, break_at_start) = gfx::text::text_run::TextRun::break_and_shape( &mut font, &self.text, diff --git a/components/layout_2020/sizing.rs b/components/layout_2020/sizing.rs index dc7513a7859..f5dac929b84 100644 --- a/components/layout_2020/sizing.rs +++ b/components/layout_2020/sizing.rs @@ -117,12 +117,12 @@ impl BoxContentSizes { .auto_is(Length::zero); let max_inline_size = match style.max_box_size().inline { MaxSize::None => None, - MaxSize::LengthPercentage(ref lp) => lp.as_length(), + MaxSize::LengthPercentage(ref lp) => lp.to_length(), }; let clamp = |l: Length| l.clamp_between_extremums(min_inline_size, max_inline_size); // Percentages for 'width' are treated as 'auto' - let inline_size = inline_size.map(|lp| lp.as_length()); + let inline_size = inline_size.map(|lp| lp.to_length()); // The (inner) min/max-content are only used for 'auto' let mut outer = match inline_size.non_auto().flatten() { None => { @@ -148,8 +148,12 @@ impl BoxContentSizes { let margin = style.margin(); pbm_lengths += border.inline_sum(); let mut add = |x: LengthPercentage| { - pbm_lengths += x.length_component(); - pbm_percentages += x.percentage_component(); + if let Some(l) = x.to_length() { + pbm_lengths += l; + } + if let Some(p) = x.to_percentage() { + pbm_percentages += p; + } }; add(padding.inline_start); add(padding.inline_end); diff --git a/components/layout_2020/style_ext.rs b/components/layout_2020/style_ext.rs index f6fb8d4000a..30b384f3884 100644 --- a/components/layout_2020/style_ext.rs +++ b/components/layout_2020/style_ext.rs @@ -59,7 +59,7 @@ impl ComputedValuesExt for ComputedValues { } else { &position.height }; - matches!(size, Size::LengthPercentage(lp) if lp.0.as_length().is_some()) + matches!(size, Size::LengthPercentage(lp) if lp.0.to_length().is_some()) } fn inline_box_offsets_are_both_non_auto(&self) -> bool { diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs index 62673268ad9..68935869aa3 100644 --- a/components/malloc_size_of/lib.rs +++ b/components/malloc_size_of/lib.rs @@ -46,6 +46,7 @@ //! Note: WebRender has a reduced fork of this crate, so that we can avoid //! publishing this crate on crates.io. +#[cfg(feature = "servo")] extern crate accountable_refcell; extern crate app_units; #[cfg(feature = "servo")] diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index 6adb71e89d7..c3fcb196527 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -55,7 +55,6 @@ num_cpus = {version = "1.1.0"} num-integer = "0.1" num-traits = "0.2" num-derive = "0.2" -ordered-float = "1.0" owning_ref = "0.4" parking_lot = "0.9" precomputed-hash = "0.1.1" diff --git a/components/style/counter_style/mod.rs b/components/style/counter_style/mod.rs index 25cdc3f8737..d131f350393 100644 --- a/components/style/counter_style/mod.rs +++ b/components/style/counter_style/mod.rs @@ -409,6 +409,7 @@ impl ToCss for System { /// <https://drafts.csswg.org/css-counter-styles/#typedef-symbol> #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToShmem)] +#[repr(u8)] pub enum Symbol { /// <string> String(crate::OwnedStr), @@ -554,6 +555,7 @@ impl Parse for Fallback { /// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-symbols> #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToShmem)] +#[repr(C)] pub struct Symbols(#[css(iterable)] pub crate::OwnedSlice<Symbol>); impl Parse for Symbols { diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index 3717e1f0614..76a72276a44 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -7,11 +7,14 @@ //! [custom]: https://drafts.csswg.org/css-variables/ use crate::hash::map::Entry; +use crate::media_queries::Device; use crate::properties::{CSSWideKeyword, CustomDeclaration, CustomDeclarationValue}; use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet, PrecomputedHasher}; use crate::stylesheets::{Origin, PerOrigin}; use crate::Atom; -use cssparser::{Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType}; +use cssparser::{ + CowRcStr, Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType, +}; use indexmap::IndexMap; use selectors::parser::SelectorParseErrorKind; use servo_arc::Arc; @@ -29,50 +32,50 @@ use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; #[derive(Debug, MallocSizeOf)] pub struct CssEnvironment; +type EnvironmentEvaluator = fn(device: &Device) -> VariableValue; + struct EnvironmentVariable { name: Atom, - value: VariableValue, + evaluator: EnvironmentEvaluator, } macro_rules! make_variable { - ($name:expr, $value:expr) => {{ + ($name:expr, $evaluator:expr) => {{ EnvironmentVariable { name: $name, - value: { - // TODO(emilio): We could make this be more efficient (though a - // bit less convenient). - let mut input = ParserInput::new($value); - let mut input = Parser::new(&mut input); - - let (first_token_type, css, last_token_type) = - parse_self_contained_declaration_value(&mut input, None).unwrap(); - - VariableValue { - css: css.into_owned(), - first_token_type, - last_token_type, - references: Default::default(), - references_environment: false, - } - }, + evaluator: $evaluator, } }}; } -lazy_static! { - static ref ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [ - make_variable!(atom!("safe-area-inset-top"), "0px"), - make_variable!(atom!("safe-area-inset-bottom"), "0px"), - make_variable!(atom!("safe-area-inset-left"), "0px"), - make_variable!(atom!("safe-area-inset-right"), "0px"), - ]; +fn get_safearea_inset_top(device: &Device) -> VariableValue { + VariableValue::pixel(device.safe_area_insets().top) +} + +fn get_safearea_inset_bottom(device: &Device) -> VariableValue { + VariableValue::pixel(device.safe_area_insets().bottom) +} + +fn get_safearea_inset_left(device: &Device) -> VariableValue { + VariableValue::pixel(device.safe_area_insets().left) +} + +fn get_safearea_inset_right(device: &Device) -> VariableValue { + VariableValue::pixel(device.safe_area_insets().right) } +static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [ + make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top), + make_variable!(atom!("safe-area-inset-bottom"), get_safearea_inset_bottom), + make_variable!(atom!("safe-area-inset-left"), get_safearea_inset_left), + make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right), +]; + impl CssEnvironment { #[inline] - fn get(&self, name: &Atom) -> Option<&VariableValue> { + fn get(&self, name: &Atom, device: &Device) -> Option<VariableValue> { let var = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name)?; - Some(&var.value) + Some((var.evaluator)(device)) } } @@ -252,6 +255,28 @@ impl VariableValue { references_environment: references.references_environment, })) } + + /// Create VariableValue from css pixel value + pub fn pixel(number: f32) -> Self { + // FIXME (https://github.com/servo/rust-cssparser/issues/266): + // No way to get TokenSerializationType::Dimension without creating + // Token object. + let token = Token::Dimension { + has_sign: false, + value: number, + int_value: None, + unit: CowRcStr::from("px"), + }; + let token_type = token.serialization_type(); + + VariableValue { + css: token.to_css_string(), + first_token_type: token_type, + last_token_type: token_type, + references: Default::default(), + references_environment: false, + } + } } /// Parse the value of a non-custom property that contains `var()` references. @@ -497,22 +522,19 @@ pub struct CustomPropertiesBuilder<'a> { may_have_cycles: bool, custom_properties: Option<CustomPropertiesMap>, inherited: Option<&'a Arc<CustomPropertiesMap>>, - environment: &'a CssEnvironment, + device: &'a Device, } impl<'a> CustomPropertiesBuilder<'a> { /// Create a new builder, inheriting from a given custom properties map. - pub fn new( - inherited: Option<&'a Arc<CustomPropertiesMap>>, - environment: &'a CssEnvironment, - ) -> Self { + pub fn new(inherited: Option<&'a Arc<CustomPropertiesMap>>, device: &'a Device) -> Self { Self { seen: PrecomputedHashSet::default(), reverted: Default::default(), may_have_cycles: false, custom_properties: None, inherited, - environment, + device, } } @@ -553,8 +575,7 @@ impl<'a> CustomPropertiesBuilder<'a> { // environment variable here, perform substitution here instead // of forcing a full traversal in `substitute_all` afterwards. let value = if !has_references && unparsed_value.references_environment { - let result = - substitute_references_in_value(unparsed_value, &map, &self.environment); + let result = substitute_references_in_value(unparsed_value, &map, &self.device); match result { Ok(new_value) => Arc::new(new_value), Err(..) => { @@ -632,7 +653,7 @@ impl<'a> CustomPropertiesBuilder<'a> { None => return self.inherited.cloned(), }; if self.may_have_cycles { - substitute_all(&mut map, self.environment); + substitute_all(&mut map, self.device); } Some(Arc::new(map)) } @@ -641,7 +662,7 @@ impl<'a> CustomPropertiesBuilder<'a> { /// Resolve all custom properties to either substituted or invalid. /// /// It does cycle dependencies removal at the same time as substitution. -fn substitute_all(custom_properties_map: &mut CustomPropertiesMap, environment: &CssEnvironment) { +fn substitute_all(custom_properties_map: &mut CustomPropertiesMap, device: &Device) { // The cycle dependencies removal in this function is a variant // of Tarjan's algorithm. It is mostly based on the pseudo-code // listed in @@ -677,8 +698,8 @@ fn substitute_all(custom_properties_map: &mut CustomPropertiesMap, environment: /// all unfinished strong connected components. stack: SmallVec<[usize; 5]>, map: &'a mut CustomPropertiesMap, - /// The environment to substitute `env()` variables. - environment: &'a CssEnvironment, + /// to resolve the environment to substitute `env()` variables. + device: &'a Device, } /// This function combines the traversal for cycle removal and value @@ -813,7 +834,7 @@ fn substitute_all(custom_properties_map: &mut CustomPropertiesMap, environment: // Now we have shown that this variable is not in a loop, and // all of its dependencies should have been resolved. We can // start substitution now. - let result = substitute_references_in_value(&value, &context.map, &context.environment); + let result = substitute_references_in_value(&value, &context.map, &context.device); match result { Ok(computed_value) => { @@ -838,7 +859,7 @@ fn substitute_all(custom_properties_map: &mut CustomPropertiesMap, environment: stack: SmallVec::new(), var_info: SmallVec::new(), map: custom_properties_map, - environment, + device, }; traverse(name, &mut context); } @@ -848,7 +869,7 @@ fn substitute_all(custom_properties_map: &mut CustomPropertiesMap, environment: fn substitute_references_in_value<'i>( value: &'i VariableValue, custom_properties: &CustomPropertiesMap, - environment: &CssEnvironment, + device: &Device, ) -> Result<ComputedValue, ParseError<'i>> { debug_assert!(!value.references.is_empty() || value.references_environment); @@ -862,7 +883,7 @@ fn substitute_references_in_value<'i>( &mut position, &mut computed_value, custom_properties, - environment, + device, )?; computed_value.push_from(&input, position, last_token_type)?; @@ -884,7 +905,7 @@ fn substitute_block<'i>( position: &mut (SourcePosition, TokenSerializationType), partial_computed_value: &mut ComputedValue, custom_properties: &CustomPropertiesMap, - env: &CssEnvironment, + device: &Device, ) -> Result<TokenSerializationType, ParseError<'i>> { let mut last_token_type = TokenSerializationType::nothing(); let mut set_position_at_next_iteration = false; @@ -928,8 +949,14 @@ fn substitute_block<'i>( } }; + let env_value; let value = if is_env { - env.get(&name) + if let Some(v) = device.environment().get(&name, device) { + env_value = v; + Some(&env_value) + } else { + None + } } else { custom_properties.get(&name).map(|v| &**v) }; @@ -956,7 +983,7 @@ fn substitute_block<'i>( &mut position, partial_computed_value, custom_properties, - env, + device, )?; partial_computed_value.push_from(input, position, last_token_type)?; } @@ -974,7 +1001,7 @@ fn substitute_block<'i>( position, partial_computed_value, custom_properties, - env, + device, ) })?; // It's the same type for CloseCurlyBracket and CloseSquareBracket. @@ -1000,7 +1027,7 @@ pub fn substitute<'i>( input: &'i str, first_token_type: TokenSerializationType, computed_values_map: Option<&Arc<CustomPropertiesMap>>, - env: &CssEnvironment, + device: &Device, ) -> Result<String, ParseError<'i>> { let mut substituted = ComputedValue::empty(); let mut input = ParserInput::new(input); @@ -1016,7 +1043,7 @@ pub fn substitute<'i>( &mut position, &mut substituted, &custom_properties, - env, + device, )?; substituted.push_from(&input, position, last_token_type)?; Ok(substituted.css) diff --git a/components/style/gecko/conversions.rs b/components/style/gecko/conversions.rs index 9bc9573bf91..3deaeacadc8 100644 --- a/components/style/gecko/conversions.rs +++ b/components/style/gecko/conversions.rs @@ -15,7 +15,6 @@ use crate::gecko_bindings::structs::{self, Matrix4x4Components}; use crate::gecko_bindings::structs::{nsStyleImage, nsresult}; use crate::stylesheets::RulesMutateError; use crate::values::computed::transform::Matrix3D; -use crate::values::computed::url::ComputedImageUrl; use crate::values::computed::{Gradient, Image, TextAlign}; use crate::values::generics::image::GenericImage; use crate::values::generics::rect::Rect; @@ -63,7 +62,7 @@ impl nsStyleImage { match self.mType { nsStyleImageType::eStyleImageType_Null => None, nsStyleImageType::eStyleImageType_Image => { - let url = self.get_image_url(); + let url = self.__bindgen_anon_1.mImage.as_ref().clone(); if self.mCropRect.mPtr.is_null() { Some(GenericImage::Url(url)) } else { @@ -88,13 +87,6 @@ impl nsStyleImage { }, } } - - unsafe fn get_image_url(&self) -> ComputedImageUrl { - let image_request = bindings::Gecko_GetImageRequest(self) - .as_ref() - .expect("Null image request?"); - ComputedImageUrl::from_image_request(image_request) - } } pub mod basic_shape { diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index 3f82b832bce..25ce9f5b3a1 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -16,7 +16,7 @@ use crate::values::{CustomIdent, KeyframesName}; use app_units::{Au, AU_PER_PX}; use cssparser::RGBA; use euclid::default::Size2D; -use euclid::Scale; +use euclid::{Scale, SideOffsets2D}; use servo_arc::Arc; use std::fmt; use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering}; @@ -281,6 +281,11 @@ impl Device { convert_nscolor_to_rgba(self.pref_sheet_prefs().mDefaultBackgroundColor) } + /// Returns the default foreground color. + pub fn default_color(&self) -> RGBA { + convert_nscolor_to_rgba(self.pref_sheet_prefs().mDefaultColor) + } + /// Returns the current effective text zoom. #[inline] fn effective_text_zoom(&self) -> f32 { @@ -302,4 +307,9 @@ impl Device { pub fn unzoom_text(&self, size: Au) -> Au { size.scale_by(1. / self.effective_text_zoom()) } + + /// Returns safe area insets + pub fn safe_area_insets(&self) -> SideOffsets2D<f32, CSSPixel> { + SideOffsets2D::zero() + } } diff --git a/components/style/gecko/url.rs b/components/style/gecko/url.rs index 3a53f639afe..27f3c60884c 100644 --- a/components/style/gecko/url.rs +++ b/components/style/gecko/url.rs @@ -6,8 +6,6 @@ use crate::gecko_bindings::bindings; use crate::gecko_bindings::structs; -use crate::gecko_bindings::structs::nsStyleImageRequest; -use crate::gecko_bindings::sugar::refptr::RefPtr; use crate::parser::{Parse, ParserContext}; use crate::stylesheets::{CorsMode, UrlExtraData}; use crate::values::computed::{Context, ToComputedValue}; @@ -150,31 +148,52 @@ struct LoadDataKey(*const LoadDataSource); unsafe impl Sync for LoadDataKey {} unsafe impl Send for LoadDataKey {} -/// The load data for a given URL. This is mutable from C++, for now at least. +bitflags! { + /// Various bits of mutable state that are kept for image loads. + #[repr(C)] + pub struct LoadDataFlags: u8 { + /// Whether we tried to resolve the uri at least once. + const TRIED_TO_RESOLVE_URI = 1 << 0; + /// Whether we tried to resolve the image at least once. + const TRIED_TO_RESOLVE_IMAGE = 1 << 1; + } +} + +/// This is usable and movable from multiple threads just fine, as long as it's +/// not cloned (it is not clonable), and the methods that mutate it run only on +/// the main thread (when all the other threads we care about are paused). +unsafe impl Sync for LoadData {} +unsafe impl Send for LoadData {} + +/// The load data for a given URL. This is mutable from C++, and shouldn't be +/// accessed from rust for anything. #[repr(C)] #[derive(Debug)] pub struct LoadData { - resolved: RefPtr<structs::nsIURI>, - load_id: u64, - tried_to_resolve: bool, + /// A strong reference to the imgRequestProxy, if any, that should be + /// released on drop. + /// + /// These are raw pointers because they are not safe to reference-count off + /// the main thread. + resolved_image: *mut structs::imgRequestProxy, + /// A strong reference to the resolved URI of this image. + resolved_uri: *mut structs::nsIURI, + /// A few flags that are set when resolving the image or such. + flags: LoadDataFlags, } impl Drop for LoadData { fn drop(&mut self) { - if self.load_id != 0 { - unsafe { - bindings::Gecko_LoadData_DeregisterLoad(self); - } - } + unsafe { bindings::Gecko_LoadData_Drop(self) } } } impl Default for LoadData { fn default() -> Self { Self { - resolved: RefPtr::null(), - load_id: 0, - tried_to_resolve: false, + resolved_image: std::ptr::null_mut(), + resolved_uri: std::ptr::null_mut(), + flags: LoadDataFlags::empty(), } } } @@ -342,13 +361,6 @@ impl ToCss for ComputedUrl { #[repr(transparent)] pub struct ComputedImageUrl(pub ComputedUrl); -impl ComputedImageUrl { - /// Convert from nsStyleImageRequest to ComputedImageUrl. - pub unsafe fn from_image_request(image_request: &nsStyleImageRequest) -> Self { - image_request.mImageURL.clone() - } -} - impl ToCss for ComputedImageUrl { fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where diff --git a/components/style/gecko/values.rs b/components/style/gecko/values.rs index b31485345b6..a755556e850 100644 --- a/components/style/gecko/values.rs +++ b/components/style/gecko/values.rs @@ -7,13 +7,13 @@ //! Different kind of helpers to interact with Gecko values. use crate::counter_style::{Symbol, Symbols}; +use crate::gecko_bindings::bindings; use crate::gecko_bindings::structs::CounterStylePtr; use crate::values::generics::CounterStyle; use crate::values::Either; use crate::Atom; use app_units::Au; use cssparser::RGBA; -use nsstring::{nsACString, nsCStr}; use std::cmp::max; /// Convert a given RGBA value to `nscolor`. @@ -51,43 +51,13 @@ pub fn round_border_to_device_pixels(width: Au, au_per_device_px: Au) -> Au { impl CounterStyle { /// Convert this counter style to a Gecko CounterStylePtr. - pub fn to_gecko_value(self, gecko_value: &mut CounterStylePtr) { - use crate::gecko_bindings::bindings::Gecko_SetCounterStyleToName as set_name; - use crate::gecko_bindings::bindings::Gecko_SetCounterStyleToSymbols as set_symbols; - match self { - CounterStyle::Name(name) => unsafe { - debug_assert_ne!(name.0, atom!("none")); - set_name(gecko_value, name.0.into_addrefed()); - }, - CounterStyle::Symbols(symbols_type, symbols) => { - let symbols: Vec<_> = symbols - .0 - .iter() - .map(|symbol| match *symbol { - Symbol::String(ref s) => nsCStr::from(&**s), - Symbol::Ident(_) => unreachable!("Should not have identifier in symbols()"), - }) - .collect(); - let symbols: Vec<_> = symbols - .iter() - .map(|symbol| symbol as &nsACString as *const _) - .collect(); - unsafe { - set_symbols( - gecko_value, - symbols_type.to_gecko_keyword(), - symbols.as_ptr(), - symbols.len() as u32, - ) - }; - }, - } + #[inline] + pub fn to_gecko_value(&self, gecko_value: &mut CounterStylePtr) { + unsafe { bindings::Gecko_CounterStyle_ToPtr(self, gecko_value) } } /// Convert Gecko CounterStylePtr to CounterStyle or String. pub fn from_gecko_value(gecko_value: &CounterStylePtr) -> Either<Self, String> { - use crate::gecko_bindings::bindings; - use crate::values::generics::SymbolsType; use crate::values::CustomIdent; let name = unsafe { bindings::Gecko_CounterStyle_GetName(gecko_value) }; @@ -103,7 +73,7 @@ impl CounterStyle { debug_assert_eq!(symbols.len(), 1); Either::Second(symbols[0].to_string()) } else { - let symbol_type = SymbolsType::from_gecko_keyword(anonymous.mSystem as u32); + let symbol_type = anonymous.mSymbolsType; let symbols = symbols .iter() .map(|gecko_symbol| Symbol::String(gecko_symbol.to_string().into())) diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index e4b2d7f0be1..ba1c724d58f 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -956,9 +956,8 @@ impl FontMetricsProvider for GeckoFontMetricsProvider { let (wm, font) = match base_size { FontBaseSize::CurrentStyle => (style.writing_mode, style.get_font()), - // These are only used for font-size computation, and the first is - // really dubious... - FontBaseSize::InheritedStyleButStripEmUnits | FontBaseSize::InheritedStyle => { + // This is only used for font-size computation. + FontBaseSize::InheritedStyle => { (*style.inherited_writing_mode(), style.get_parent_font()) }, }; @@ -1869,22 +1868,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { #[inline] fn pseudo_element_originating_element(&self) -> Option<Self> { debug_assert!(self.is_pseudo_element()); - let parent = self.closest_anon_subtree_root_parent()?; - - // FIXME(emilio): Special-case for <input type="number">s - // pseudo-elements, which are nested NAC. Probably nsNumberControlFrame - // should instead inherit from nsTextControlFrame, and then this could - // go away. - if let Some(PseudoElement::MozNumberText) = parent.implemented_pseudo_element() { - debug_assert_eq!( - self.implemented_pseudo_element().unwrap(), - PseudoElement::Placeholder, - "You added a new pseudo, do you really want this?" - ); - return parent.closest_anon_subtree_root_parent(); - } - - Some(parent) + self.closest_anon_subtree_root_parent() } #[inline] diff --git a/components/style/gecko_bindings/sugar/refptr.rs b/components/style/gecko_bindings/sugar/refptr.rs index e27b4a28c40..a141aef4e8b 100644 --- a/components/style/gecko_bindings/sugar/refptr.rs +++ b/components/style/gecko_bindings/sugar/refptr.rs @@ -304,11 +304,6 @@ impl_threadsafe_refcount!( bindings::Gecko_ReleaseURLExtraDataArbitraryThread ); impl_threadsafe_refcount!( - structs::nsIReferrerInfo, - bindings::Gecko_AddRefnsIReferrerInfoArbitraryThread, - bindings::Gecko_ReleasensIReferrerInfoArbitraryThread -); -impl_threadsafe_refcount!( structs::nsIURI, bindings::Gecko_AddRefnsIURIArbitraryThread, bindings::Gecko_ReleasensIURIArbitraryThread diff --git a/components/style/gecko_string_cache/namespace.rs b/components/style/gecko_string_cache/namespace.rs index 33883e66941..2dba484e002 100644 --- a/components/style/gecko_string_cache/namespace.rs +++ b/components/style/gecko_string_cache/namespace.rs @@ -25,6 +25,7 @@ macro_rules! ns { /// A Gecko namespace is just a wrapped atom. #[derive(Clone, Debug, Default, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)] +#[repr(transparent)] pub struct Namespace(pub Atom); impl PrecomputedHash for Namespace { diff --git a/components/style/lib.rs b/components/style/lib.rs index 354950f9c15..6c4b1d5a2a9 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -72,7 +72,6 @@ extern crate num_cpus; extern crate num_derive; extern crate num_integer; extern crate num_traits; -extern crate ordered_float; extern crate owning_ref; extern crate parking_lot; extern crate precomputed_hash; @@ -120,6 +119,8 @@ pub mod attr; pub mod author_styles; pub mod bezier; pub mod bloom; +#[path = "properties/computed_value_flags.rs"] +pub mod computed_value_flags; pub mod context; pub mod counter_style; pub mod custom_properties; @@ -173,10 +174,12 @@ pub mod values; pub use crate::gecko_string_cache as string_cache; #[cfg(feature = "gecko")] pub use crate::gecko_string_cache::Atom; +/// The namespace prefix type for Gecko, which is just an atom. #[cfg(feature = "gecko")] -pub use crate::gecko_string_cache::Atom as Prefix; +pub type Prefix = crate::gecko_string_cache::Atom; +/// The local name of an element for Gecko, which is just an atom. #[cfg(feature = "gecko")] -pub use crate::gecko_string_cache::Atom as LocalName; +pub type LocalName = crate::gecko_string_cache::Atom; #[cfg(feature = "gecko")] pub use crate::gecko_string_cache::Namespace; diff --git a/components/style/matching.rs b/components/style/matching.rs index 7ec94d35346..06e74810cfd 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -7,6 +7,7 @@ #![allow(unsafe_code)] #![deny(missing_docs)] +use crate::computed_value_flags::ComputedValueFlags; use crate::context::{ElementCascadeInputs, QuirksMode, SelectorFlagsMap}; use crate::context::{SharedStyleContext, StyleContext}; use crate::data::ElementData; @@ -705,7 +706,10 @@ pub trait MatchMethods: TElement { let new_primary_style = data.styles.primary.as_ref().unwrap(); let mut cascade_requirement = ChildCascadeRequirement::CanSkipCascade; - if self.is_root() && !self.is_in_native_anonymous_subtree() { + if new_primary_style + .flags + .contains(ComputedValueFlags::IS_ROOT_ELEMENT_STYLE) + { let device = context.shared.stylist.device(); let new_font_size = new_primary_style.get_font().clone_font_size(); diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs index dd87916cf7d..50029da8479 100644 --- a/components/style/properties/cascade.rs +++ b/components/style/properties/cascade.rs @@ -25,7 +25,7 @@ use smallvec::SmallVec; use std::borrow::Cow; use std::cell::RefCell; use crate::style_adjuster::StyleAdjuster; -use crate::values::computed; +use crate::values::{computed, specified}; /// We split the cascade in two phases: 'early' properties, and 'late' /// properties. @@ -250,7 +250,7 @@ where let custom_properties = { let mut builder = CustomPropertiesBuilder::new( inherited_style.custom_properties(), - device.environment(), + device, ); for (declaration, origin) in iter_declarations() { @@ -263,8 +263,11 @@ where builder.build() }; + + let is_root_element = + pseudo.is_none() && element.map_or(false, |e| e.is_root()); + let mut context = computed::Context { - is_root_element: pseudo.is_none() && element.map_or(false, |e| e.is_root()), // We'd really like to own the rules here to avoid refcount traffic, but // animation's usage of `apply_declarations` make this tricky. See bug // 1375525. @@ -275,6 +278,7 @@ where pseudo, Some(rules.clone()), custom_properties, + is_root_element, ), cached_system_font: None, in_media_query: false, @@ -333,27 +337,37 @@ where context.builder.build() } -fn should_ignore_declaration_when_ignoring_document_colors( - device: &Device, +/// How should a declaration behave when ignoring document colors? +enum DeclarationApplication { + /// We should apply the declaration. + Apply, + /// We should ignore the declaration. + Ignore, + /// We should apply the following declaration, only if any other declaration + /// hasn't set it before. + ApplyUnlessOverriden(PropertyDeclaration), +} + +fn application_when_ignoring_colors( + builder: &StyleBuilder, longhand_id: LonghandId, origin: Origin, - pseudo: Option<&PseudoElement>, - declaration: &mut Cow<PropertyDeclaration>, -) -> bool { + declaration: &PropertyDeclaration, +) -> DeclarationApplication { if !longhand_id.ignored_when_document_colors_disabled() { - return false; + return DeclarationApplication::Apply; } let is_ua_or_user_rule = matches!(origin, Origin::User | Origin::UserAgent); if is_ua_or_user_rule { - return false; + return DeclarationApplication::Apply; } // Don't override background-color on ::-moz-color-swatch. It is set as an // author style (via the style attribute), but it's pretty important for it // to show up for obvious reasons :) - if pseudo.map_or(false, |p| p.is_color_swatch()) && longhand_id == LonghandId::BackgroundColor { - return false; + if builder.pseudo.map_or(false, |p| p.is_color_swatch()) && longhand_id == LonghandId::BackgroundColor { + return DeclarationApplication::Apply; } // Treat background-color a bit differently. If the specified color is @@ -365,26 +379,41 @@ fn should_ignore_declaration_when_ignoring_document_colors( // a background image, if we're ignoring document colors). // Here we check backplate status to decide if ignoring background-image // is the right decision. - { - let background_color = match **declaration { - PropertyDeclaration::BackgroundColor(ref color) => color, - // In the future, if/when we remove the backplate pref, we can remove this - // special case along with the 'ignored_when_colors_disabled=True' mako line - // for the "background-image" property. - #[cfg(feature = "gecko")] - PropertyDeclaration::BackgroundImage(..) => return !static_prefs::pref!("browser.display.permit_backplate"), - _ => return true, - }; - - if background_color.is_transparent() { - return false; + match *declaration { + PropertyDeclaration::BackgroundColor(ref color) => { + if color.is_transparent() { + return DeclarationApplication::Apply; + } + let color = builder.device.default_background_color(); + DeclarationApplication::ApplyUnlessOverriden( + PropertyDeclaration::BackgroundColor(color.into()) + ) } + PropertyDeclaration::Color(ref color) => { + if color.0.is_transparent() { + return DeclarationApplication::Apply; + } + if builder.get_parent_inherited_text().clone_color().alpha != 0 { + return DeclarationApplication::Ignore; + } + let color = builder.device.default_color(); + DeclarationApplication::ApplyUnlessOverriden( + PropertyDeclaration::Color(specified::ColorPropertyValue(color.into())) + ) + }, + // In the future, if/when we remove the backplate pref, we can remove this + // special case along with the 'ignored_when_colors_disabled=True' mako line + // for the "background-image" property. + #[cfg(feature = "gecko")] + PropertyDeclaration::BackgroundImage(..) => { + if static_prefs::pref!("browser.display.permit_backplate") { + DeclarationApplication::Apply + } else { + DeclarationApplication::Ignore + } + }, + _ => DeclarationApplication::Ignore, } - - let color = device.default_background_color(); - *declaration.to_mut() = PropertyDeclaration::BackgroundColor(color.into()); - - false } struct Cascade<'a, 'b: 'a> { @@ -424,7 +453,7 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { declaration.id, self.context.builder.custom_properties.as_ref(), self.context.quirks_mode, - self.context.device().environment(), + self.context.device(), )) } @@ -461,6 +490,8 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { ); let ignore_colors = !self.context.builder.device.use_document_colors(); + let mut declarations_to_apply_unless_overriden = + SmallVec::<[PropertyDeclaration; 2]>::new(); for (declaration, origin) in declarations { let declaration_id = declaration.id(); @@ -502,21 +533,25 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { continue; } - let mut declaration = self.substitute_variables_if_needed(declaration); + let declaration = self.substitute_variables_if_needed(declaration); - // When document colors are disabled, skip properties that are - // marked as ignored in that mode, unless they come from a UA or - // user style sheet. + // When document colors are disabled, do special handling of + // properties that are marked as ignored in that mode. if ignore_colors { - let should_ignore = should_ignore_declaration_when_ignoring_document_colors( - self.context.builder.device, + let application = application_when_ignoring_colors( + &self.context.builder, longhand_id, origin, - self.context.builder.pseudo, - &mut declaration, + &declaration, ); - if should_ignore { - continue; + + match application { + DeclarationApplication::Ignore => continue, + DeclarationApplication::Apply => {}, + DeclarationApplication::ApplyUnlessOverriden(decl) => { + declarations_to_apply_unless_overriden.push(decl); + continue; + } } } @@ -554,6 +589,20 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { self.apply_declaration(longhand_id, &*declaration); } + if ignore_colors { + for declaration in declarations_to_apply_unless_overriden.iter() { + let longhand_id = match declaration.id() { + PropertyDeclarationId::Longhand(id) => id, + PropertyDeclarationId::Custom(..) => unreachable!(), + }; + debug_assert!(!longhand_id.is_logical()); + if self.seen.contains(longhand_id) { + continue; + } + self.apply_declaration(longhand_id, declaration); + } + } + if Phase::is_early() { self.fixup_font_stuff(); self.compute_writing_mode(); diff --git a/components/style/properties/computed_value_flags.rs b/components/style/properties/computed_value_flags.rs index a86f8a4da3a..4363f3f36e9 100644 --- a/components/style/properties/computed_value_flags.rs +++ b/components/style/properties/computed_value_flags.rs @@ -11,6 +11,7 @@ bitflags! { /// anonymous boxes, see StyleBuilder::for_inheritance and its callsites. /// If we ever want to add some flags that shouldn't inherit for them, /// we might want to add a function to handle this. + #[repr(C)] pub struct ComputedValueFlags: u16 { /// Whether the style or any of the ancestors has a text-decoration-line /// property that should get propagated to descendants. @@ -63,6 +64,9 @@ bitflags! { /// /// Only used in Servo. const CAN_BE_FRAGMENTED = 1 << 10; + + /// Whether this style is the style of the document element. + const IS_ROOT_ELEMENT_STYLE = 1 << 11; } } @@ -71,9 +75,9 @@ impl ComputedValueFlags { #[inline] fn inherited_flags() -> Self { ComputedValueFlags::IS_RELEVANT_LINK_VISITED | - ComputedValueFlags::CAN_BE_FRAGMENTED | - ComputedValueFlags::IS_IN_PSEUDO_ELEMENT_SUBTREE | - ComputedValueFlags::HAS_TEXT_DECORATION_LINES + ComputedValueFlags::CAN_BE_FRAGMENTED | + ComputedValueFlags::IS_IN_PSEUDO_ELEMENT_SUBTREE | + ComputedValueFlags::HAS_TEXT_DECORATION_LINES } /// Flags that may be propagated to descendants. @@ -97,22 +101,3 @@ impl ComputedValueFlags { self & Self::maybe_inherited_flags() } } - -/// Asserts that the relevant servo and Gecko representations match. -#[cfg(feature = "gecko")] -#[inline] -pub fn assert_match() { - use crate::gecko_bindings::structs; - macro_rules! assert_bit { - ($rust:ident, $cpp:ident) => { - debug_assert_eq!(ComputedValueFlags::$rust.bits, structs::$cpp); - } - } - - assert_bit!(HAS_TEXT_DECORATION_LINES, ComputedStyleBit_HasTextDecorationLines); - assert_bit!(IS_IN_PSEUDO_ELEMENT_SUBTREE, ComputedStyleBit_HasPseudoElementData); - assert_bit!(SHOULD_SUPPRESS_LINEBREAK, ComputedStyleBit_SuppressLineBreak); - assert_bit!(IS_TEXT_COMBINED, ComputedStyleBit_IsTextCombined); - assert_bit!(IS_RELEVANT_LINK_VISITED, ComputedStyleBit_RelevantLinkVisited); - assert_bit!(DEPENDS_ON_FONT_METRICS, ComputedStyleBit_DependsOnFontMetrics); -} diff --git a/components/style/properties/declaration_block.rs b/components/style/properties/declaration_block.rs index 90dec0a47d7..53b483887ce 100644 --- a/components/style/properties/declaration_block.rs +++ b/components/style/properties/declaration_block.rs @@ -8,7 +8,7 @@ use super::*; use crate::context::QuirksMode; -use crate::custom_properties::{CssEnvironment, CustomPropertiesBuilder}; +use crate::custom_properties::CustomPropertiesBuilder; use crate::error_reporting::{ContextualParseError, ParseErrorReporter}; use crate::parser::ParserContext; use crate::properties::animated_properties::{AnimationValue, AnimationValueMap}; @@ -778,6 +778,7 @@ impl PropertyDeclarationBlock { dest: &mut CssStringWriter, computed_values: Option<&ComputedValues>, custom_properties_block: Option<&PropertyDeclarationBlock>, + device: &Device, ) -> fmt::Result { if let Ok(shorthand) = property.as_shorthand() { return self.shorthand_to_css(shorthand, dest); @@ -790,19 +791,13 @@ impl PropertyDeclarationBlock { None => return Err(fmt::Error), }; - // TODO(emilio): When we implement any environment variable without - // hard-coding the values we're going to need to get something - // meaningful out of here... All this code path is so terribly hacky - // ;_;. - let env = CssEnvironment; - let custom_properties = if let Some(cv) = computed_values { // If there are extra custom properties for this declaration block, // factor them in too. if let Some(block) = custom_properties_block { // FIXME(emilio): This is not super-efficient here, and all this // feels like a hack anyway... - block.cascade_custom_properties(cv.custom_properties(), &env) + block.cascade_custom_properties(cv.custom_properties(), device) } else { cv.custom_properties().cloned() } @@ -825,7 +820,7 @@ impl PropertyDeclarationBlock { declaration.id, custom_properties.as_ref(), QuirksMode::NoQuirks, - &env, + device, ) .to_css(dest) }, @@ -873,7 +868,7 @@ impl PropertyDeclarationBlock { ) -> Option<Arc<crate::custom_properties::CustomPropertiesMap>> { self.cascade_custom_properties( context.style().custom_properties(), - context.device().environment(), + context.device(), ) } @@ -883,9 +878,9 @@ impl PropertyDeclarationBlock { fn cascade_custom_properties( &self, inherited_custom_properties: Option<&Arc<crate::custom_properties::CustomPropertiesMap>>, - environment: &CssEnvironment, + device: &Device, ) -> Option<Arc<crate::custom_properties::CustomPropertiesMap>> { - let mut builder = CustomPropertiesBuilder::new(inherited_custom_properties, environment); + let mut builder = CustomPropertiesBuilder::new(inherited_custom_properties, device); for declaration in self.normal_declaration_iter() { if let PropertyDeclaration::Custom(ref declaration) = *declaration { diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index 14262e7a074..6cfb4c859b4 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -12,6 +12,7 @@ use crate::Atom; use app_units::Au; +use crate::computed_value_flags::*; use crate::custom_properties::CustomPropertiesMap; use crate::gecko_bindings::bindings; % for style_struct in data.style_structs: @@ -24,28 +25,20 @@ use crate::gecko_bindings::bindings::Gecko_CopyCounterStyle; use crate::gecko_bindings::bindings::Gecko_CopyCursorArrayFrom; use crate::gecko_bindings::bindings::Gecko_CopyFontFamilyFrom; use crate::gecko_bindings::bindings::Gecko_CopyImageValueFrom; -use crate::gecko_bindings::bindings::Gecko_CopyListStyleImageFrom; use crate::gecko_bindings::bindings::Gecko_EnsureImageLayersLength; -use crate::gecko_bindings::bindings::Gecko_SetCursorArrayLength; -use crate::gecko_bindings::bindings::Gecko_SetCursorImageValue; use crate::gecko_bindings::bindings::Gecko_nsStyleFont_SetLang; use crate::gecko_bindings::bindings::Gecko_nsStyleFont_CopyLangFrom; -use crate::gecko_bindings::bindings::Gecko_SetListStyleImageNone; -use crate::gecko_bindings::bindings::Gecko_SetListStyleImageImageValue; use crate::gecko_bindings::bindings::Gecko_SetNullImageValue; use crate::gecko_bindings::structs; use crate::gecko_bindings::structs::nsCSSPropertyID; use crate::gecko_bindings::structs::mozilla::PseudoStyleType; -use crate::gecko_bindings::sugar::refptr::RefPtr; use crate::gecko::values::round_border_to_device_pixels; use crate::logical_geometry::WritingMode; use crate::media_queries::Device; -use crate::properties::computed_value_flags::*; use crate::properties::longhands; use crate::rule_tree::StrongRuleNode; use crate::selector_parser::PseudoElement; use servo_arc::{Arc, RawOffsetArc, UniqueArc}; -use std::marker::PhantomData; use std::mem::{forget, MaybeUninit}; use std::{cmp, ops, ptr}; use crate::values::{self, CustomIdent, Either, KeyframesName, None_}; @@ -55,8 +48,6 @@ use crate::values::computed::BorderStyle; use crate::values::computed::font::FontSize; use crate::values::generics::column::ColumnCount; use crate::values::generics::image::ImageLayer; -use crate::values::generics::transform::TransformStyle; -use crate::values::generics::url::UrlOrNone; pub mod style_structs { @@ -959,48 +950,16 @@ fn static_assert() { <% skip_position_longhands = " ".join(x.ident for x in SIDES) %> <%self:impl_trait style_struct_name="Position" - skip_longhands="${skip_position_longhands} - align-content justify-content align-self - justify-self align-items justify-items - grid-auto-flow"> + skip_longhands="${skip_position_longhands} grid-auto-flow"> % for side in SIDES: <% impl_split_style_coord(side.ident, "mOffset", side.index) %> % endfor - - % for kind in ["align", "justify"]: - ${impl_simple_type_with_conversion(kind + "_content")} - ${impl_simple_type_with_conversion(kind + "_self")} - % endfor - ${impl_simple_type_with_conversion("align_items")} - - pub fn set_justify_items(&mut self, v: longhands::justify_items::computed_value::T) { - self.gecko.mSpecifiedJustifyItems = v.specified.into(); - self.set_computed_justify_items(v.computed); - } - + ${impl_simple_type_with_conversion("grid_auto_flow")} pub fn set_computed_justify_items(&mut self, v: values::specified::JustifyItems) { debug_assert_ne!(v.0, crate::values::specified::align::AlignFlags::LEGACY); - self.gecko.mJustifyItems = v.into(); + self.gecko.mJustifyItems.computed = v; } - pub fn reset_justify_items(&mut self, reset_style: &Self) { - self.gecko.mJustifyItems = reset_style.gecko.mJustifyItems; - self.gecko.mSpecifiedJustifyItems = reset_style.gecko.mSpecifiedJustifyItems; - } - - pub fn copy_justify_items_from(&mut self, other: &Self) { - self.gecko.mJustifyItems = other.gecko.mJustifyItems; - self.gecko.mSpecifiedJustifyItems = other.gecko.mJustifyItems; - } - - pub fn clone_justify_items(&self) -> longhands::justify_items::computed_value::T { - longhands::justify_items::computed_value::T { - computed: self.gecko.mJustifyItems.into(), - specified: self.gecko.mSpecifiedJustifyItems.into(), - } - } - - ${impl_simple_type_with_conversion("grid_auto_flow")} </%self:impl_trait> <% skip_outline_longhands = " ".join("outline-style outline-width".split() + @@ -1507,7 +1466,7 @@ fn static_assert() { animation-iteration-count animation-timing-function clear transition-duration transition-delay transition-timing-function transition-property - transform-style shape-outside -webkit-line-clamp""" %> + shape-outside -webkit-line-clamp""" %> <%self:impl_trait style_struct_name="Box" skip_longhands="${skip_box_longhands}"> #[inline] pub fn set_display(&mut self, v: longhands::display::computed_value::T) { @@ -1664,25 +1623,6 @@ fn static_assert() { self.copy_transition_property_from(other) } - // Hand-written because the Mako helpers transform `Preserve3d` into `PRESERVE3D`. - pub fn set_transform_style(&mut self, v: TransformStyle) { - self.gecko.mTransformStyle = match v { - TransformStyle::Flat => structs::NS_STYLE_TRANSFORM_STYLE_FLAT as u8, - TransformStyle::Preserve3d => structs::NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D as u8, - }; - } - - // Hand-written because the Mako helpers transform `Preserve3d` into `PRESERVE3D`. - pub fn clone_transform_style(&self) -> TransformStyle { - match self.gecko.mTransformStyle as u32 { - structs::NS_STYLE_TRANSFORM_STYLE_FLAT => TransformStyle::Flat, - structs::NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D => TransformStyle::Preserve3d, - _ => panic!("illegal transform style"), - } - } - - ${impl_simple_copy('transform_style', 'mTransformStyle')} - ${impl_transition_count('property', 'Property')} pub fn animations_equals(&self, other: &Self) -> bool { @@ -2162,57 +2102,20 @@ fn static_assert() { <% impl_simple_image_array_property("blend_mode", "background", "mImage", "mBlendMode", "Background") %> </%self:impl_trait> -<%self:impl_trait style_struct_name="List" - skip_longhands="list-style-image list-style-type"> - - pub fn set_list_style_image(&mut self, image: longhands::list_style_image::computed_value::T) { - match image { - UrlOrNone::None => { - unsafe { - Gecko_SetListStyleImageNone(&mut *self.gecko); - } - } - UrlOrNone::Url(ref url) => { - unsafe { - Gecko_SetListStyleImageImageValue(&mut *self.gecko, url); - } - } - } - } - - pub fn copy_list_style_image_from(&mut self, other: &Self) { - unsafe { Gecko_CopyListStyleImageFrom(&mut *self.gecko, &*other.gecko); } - } - - pub fn reset_list_style_image(&mut self, other: &Self) { - self.copy_list_style_image_from(other) - } - - pub fn clone_list_style_image(&self) -> longhands::list_style_image::computed_value::T { - if self.gecko.mListStyleImage.mRawPtr.is_null() { - return UrlOrNone::None; - } - - unsafe { - let ref gecko_image_request = *self.gecko.mListStyleImage.mRawPtr; - UrlOrNone::Url(ComputedImageUrl::from_image_request(gecko_image_request)) - } - } - +<%self:impl_trait style_struct_name="List" skip_longhands="list-style-type"> pub fn set_list_style_type(&mut self, v: longhands::list_style_type::computed_value::T) { - use crate::gecko_bindings::bindings::Gecko_SetCounterStyleToName; - use crate::gecko_bindings::bindings::Gecko_SetCounterStyleToString; use nsstring::{nsACString, nsCStr}; use self::longhands::list_style_type::computed_value::T; match v { T::None => unsafe { - Gecko_SetCounterStyleToName(&mut self.gecko.mCounterStyle, - atom!("none").into_addrefed()); + bindings::Gecko_SetCounterStyleToNone(&mut self.gecko.mCounterStyle) } T::CounterStyle(s) => s.to_gecko_value(&mut self.gecko.mCounterStyle), T::String(s) => unsafe { - Gecko_SetCounterStyleToString(&mut self.gecko.mCounterStyle, - &nsCStr::from(&s) as &nsACString) + bindings::Gecko_SetCounterStyleToString( + &mut self.gecko.mCounterStyle, + &nsCStr::from(&s) as &nsACString, + ) } } } @@ -2488,14 +2391,11 @@ clip-path pub fn set_cursor(&mut self, v: longhands::cursor::computed_value::T) { self.gecko.mCursor = v.keyword; unsafe { - Gecko_SetCursorArrayLength(&mut *self.gecko, v.images.len()); + bindings::Gecko_SetCursorArrayCapacity(&mut *self.gecko, v.images.len()); } for i in 0..v.images.len() { unsafe { - Gecko_SetCursorImageValue( - &mut self.gecko.mCursorImages[i], - &v.images[i].url - ); + bindings::Gecko_AppendCursorImage(&mut *self.gecko, &v.images[i].url); } match v.images[i].hotspot { @@ -2528,10 +2428,7 @@ clip-path let keyword = self.gecko.mCursor; let images = self.gecko.mCursorImages.iter().map(|gecko_cursor_image| { - let url = unsafe { - let gecko_image_request = gecko_cursor_image.mImage.mRawPtr.as_ref().unwrap(); - ComputedImageUrl::from_image_request(&gecko_image_request) - }; + let url = gecko_cursor_image.mImage.clone(); let hotspot = if gecko_cursor_image.mHaveHotspot { @@ -2580,236 +2477,9 @@ clip-path ${impl_simple('column_rule_style', 'mColumnRuleStyle')} </%self:impl_trait> -<%self:impl_trait style_struct_name="Counters" skip_longhands="content"> +<%self:impl_trait style_struct_name="Counters"> pub fn ineffective_content_property(&self) -> bool { - self.gecko.mContents.is_empty() - } - - pub fn set_content(&mut self, v: longhands::content::computed_value::T) { - use crate::values::CustomIdent; - use crate::values::generics::counters::{Content, ContentItem}; - use crate::values::generics::CounterStyle; - use crate::gecko_bindings::structs::nsStyleContentData; - use crate::gecko_bindings::structs::nsStyleContentAttr; - use crate::gecko_bindings::structs::StyleContentType; - use crate::gecko_bindings::bindings::Gecko_ClearAndResizeStyleContents; - - // Converts a string as utf16, and returns an owned, zero-terminated raw buffer. - fn as_utf16_and_forget(s: &str) -> *mut u16 { - use std::mem; - let mut vec = s.encode_utf16().collect::<Vec<_>>(); - vec.push(0u16); - let ptr = vec.as_mut_ptr(); - mem::forget(vec); - ptr - } - - fn set_counter_function( - data: &mut nsStyleContentData, - content_type: StyleContentType, - name: CustomIdent, - sep: &str, - style: CounterStyle, - ) { - debug_assert!(content_type == StyleContentType::Counter || - content_type == StyleContentType::Counters); - let counter_func = unsafe { - bindings::Gecko_SetCounterFunction(data, content_type).as_mut().unwrap() - }; - counter_func.mIdent.set_move(unsafe { - RefPtr::from_addrefed(name.0.into_addrefed()) - }); - if content_type == StyleContentType::Counters { - counter_func.mSeparator.assign_str(sep); - } - style.to_gecko_value(&mut counter_func.mCounterStyle); - } - - match v { - Content::None | - Content::Normal => { - // Ensure destructors run, otherwise we could leak. - if !self.gecko.mContents.is_empty() { - unsafe { - Gecko_ClearAndResizeStyleContents(&mut *self.gecko, 0); - } - } - }, - Content::MozAltContent => { - unsafe { - Gecko_ClearAndResizeStyleContents(&mut *self.gecko, 1); - *self.gecko.mContents[0].mContent.mString.as_mut() = ptr::null_mut(); - } - self.gecko.mContents[0].mType = StyleContentType::AltContent; - }, - Content::Items(items) => { - unsafe { - Gecko_ClearAndResizeStyleContents(&mut *self.gecko, - items.len() as u32); - } - for (i, item) in items.into_vec().into_iter().enumerate() { - // NB: Gecko compares the mString value if type is not image - // or URI independently of whatever gets there. In the quote - // cases, they set it to null, so do the same here. - unsafe { - *self.gecko.mContents[i].mContent.mString.as_mut() = ptr::null_mut(); - } - match item { - ContentItem::String(ref value) => { - self.gecko.mContents[i].mType = StyleContentType::String; - unsafe { - // NB: we share allocators, so doing this is fine. - *self.gecko.mContents[i].mContent.mString.as_mut() = - as_utf16_and_forget(&value); - } - } - ContentItem::Attr(ref attr) => { - self.gecko.mContents[i].mType = StyleContentType::Attr; - unsafe { - // NB: we share allocators, so doing this is fine. - let maybe_ns = attr.namespace.clone(); - let attr_struct = Box::new(nsStyleContentAttr { - mName: structs::RefPtr { - mRawPtr: attr.attribute.clone().into_addrefed(), - _phantom_0: PhantomData, - }, - mNamespaceURL: structs::RefPtr { - mRawPtr: maybe_ns.map_or(ptr::null_mut(), |x| (x.1).0.into_addrefed()), - _phantom_0: PhantomData, - }, - }); - *self.gecko.mContents[i].mContent.mAttr.as_mut() = - Box::into_raw(attr_struct); - } - } - ContentItem::OpenQuote - => self.gecko.mContents[i].mType = StyleContentType::OpenQuote, - ContentItem::CloseQuote - => self.gecko.mContents[i].mType = StyleContentType::CloseQuote, - ContentItem::NoOpenQuote - => self.gecko.mContents[i].mType = StyleContentType::NoOpenQuote, - ContentItem::NoCloseQuote - => self.gecko.mContents[i].mType = StyleContentType::NoCloseQuote, - ContentItem::Counter(name, style) => { - set_counter_function( - &mut self.gecko.mContents[i], - StyleContentType::Counter, - name, - "", - style, - ); - } - ContentItem::Counters(name, sep, style) => { - set_counter_function( - &mut self.gecko.mContents[i], - StyleContentType::Counters, - name, - &sep, - style, - ); - } - ContentItem::Url(ref url) => { - unsafe { - bindings::Gecko_SetContentDataImageValue( - &mut self.gecko.mContents[i], - url, - ) - } - } - } - } - } - } - } - - pub fn copy_content_from(&mut self, other: &Self) { - use crate::gecko_bindings::bindings::Gecko_CopyStyleContentsFrom; - unsafe { - Gecko_CopyStyleContentsFrom(&mut *self.gecko, &*other.gecko) - } - } - - pub fn reset_content(&mut self, other: &Self) { - self.copy_content_from(other) - } - - pub fn clone_content(&self) -> longhands::content::computed_value::T { - use {Atom, Namespace}; - use crate::gecko::conversions::string_from_chars_pointer; - use crate::gecko_bindings::structs::StyleContentType; - use crate::values::generics::counters::{Content, ContentItem}; - use crate::values::{CustomIdent, Either}; - use crate::values::generics::CounterStyle; - use crate::values::specified::Attr; - - if self.gecko.mContents.is_empty() { - return Content::None; - } - - if self.gecko.mContents.len() == 1 && - self.gecko.mContents[0].mType == StyleContentType::AltContent { - return Content::MozAltContent; - } - - Content::Items( - self.gecko.mContents.iter().map(|gecko_content| { - match gecko_content.mType { - StyleContentType::OpenQuote => ContentItem::OpenQuote, - StyleContentType::CloseQuote => ContentItem::CloseQuote, - StyleContentType::NoOpenQuote => ContentItem::NoOpenQuote, - StyleContentType::NoCloseQuote => ContentItem::NoCloseQuote, - StyleContentType::String => { - let gecko_chars = unsafe { gecko_content.mContent.mString.as_ref() }; - let string = unsafe { string_from_chars_pointer(*gecko_chars) }; - ContentItem::String(string.into_boxed_str()) - }, - StyleContentType::Attr => { - let (namespace, attribute) = unsafe { - let s = &**gecko_content.mContent.mAttr.as_ref(); - let ns = if s.mNamespaceURL.mRawPtr.is_null() { - None - } else { - // FIXME(bholley): We don't have any way to get the prefix here. :-( - let prefix = atom!(""); - Some((prefix, Namespace(Atom::from_raw(s.mNamespaceURL.mRawPtr)))) - }; - (ns, Atom::from_raw(s.mName.mRawPtr)) - }; - ContentItem::Attr(Attr { namespace, attribute }) - }, - StyleContentType::Counter | StyleContentType::Counters => { - let gecko_function = - unsafe { &**gecko_content.mContent.mCounters.as_ref() }; - let ident = CustomIdent(unsafe { - Atom::from_raw(gecko_function.mIdent.mRawPtr) - }); - let style = - CounterStyle::from_gecko_value(&gecko_function.mCounterStyle); - let style = match style { - Either::First(counter_style) => counter_style, - Either::Second(_) => - unreachable!("counter function shouldn't have single string type"), - }; - if gecko_content.mType == StyleContentType::Counter { - ContentItem::Counter(ident, style) - } else { - let separator = gecko_function.mSeparator.to_string(); - ContentItem::Counters(ident, separator.into_boxed_str(), style) - } - }, - StyleContentType::Image => { - unsafe { - let gecko_image_request = - &**gecko_content.mContent.mImage.as_ref(); - ContentItem::Url( - ComputedImageUrl::from_image_request(gecko_image_request) - ) - } - }, - _ => panic!("Found unexpected value in style struct for content property"), - } - }).collect::<Vec<_>>().into_boxed_slice() - ) + !self.gecko.mContent.is_items() } </%self:impl_trait> diff --git a/components/style/properties/helpers.mako.rs b/components/style/properties/helpers.mako.rs index 4d3895440d5..7a25b2569ea 100644 --- a/components/style/properties/helpers.mako.rs +++ b/components/style/properties/helpers.mako.rs @@ -563,6 +563,7 @@ Copy, Debug, Eq, + FromPrimitive, Hash, MallocSizeOf, Parse, @@ -762,7 +763,7 @@ % endif pub mod computed_value { #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] - #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToResolvedValue)] + #[derive(Clone, Copy, Debug, Eq, FromPrimitive, MallocSizeOf, PartialEq, ToCss, ToResolvedValue)] % if not extra_specified: #[derive(Parse, SpecifiedValueInfo, ToComputedValue, ToShmem)] % endif diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index c81e186a129..84f843ec728 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -507,7 +507,7 @@ impl AnimationValue { declaration.id, custom_properties, context.quirks_mode, - context.device().environment(), + context.device(), ) }; return AnimationValue::from_declaration( diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs index fba2fac66dc..90c8dc539d8 100644 --- a/components/style/properties/longhands/box.mako.rs +++ b/components/style/properties/longhands/box.mako.rs @@ -47,6 +47,7 @@ ${helpers.single_keyword( values="static absolute relative fixed ${'sticky' if engine in ['gecko', 'servo-2013'] else ''}" engines="gecko servo-2013 servo-2020" animation_value_type="discrete" + gecko_enum_prefix="StylePositionProperty" flags="CREATES_STACKING_CONTEXT ABSPOS_CB" spec="https://drafts.csswg.org/css-position/#position-property" servo_restyle_damage="rebuild_and_reflow" @@ -452,6 +453,7 @@ ${helpers.single_keyword( engines="gecko", spec="https://drafts.csswg.org/cssom-view/#propdef-scroll-behavior", animation_value_type="discrete", + gecko_enum_prefix="StyleScrollBehavior", )} ${helpers.predefined_type( @@ -472,13 +474,15 @@ ${helpers.predefined_type( animation_value_type="discrete", )} -% for axis in ["x", "y"]: +% for (axis, logical) in ALL_AXES: ${helpers.predefined_type( "overscroll-behavior-" + axis, "OverscrollBehavior", "computed::OverscrollBehavior::Auto", engines="gecko", needs_context=False, + logical_group="overscroll-behavior", + logical=logical, gecko_pref="layout.css.overscroll-behavior.enabled", spec="https://wicg.github.io/overscroll-behavior/#overscroll-behavior-properties", animation_value_type="discrete", @@ -589,7 +593,7 @@ ${helpers.single_keyword( ${helpers.predefined_type( "transform-style", "TransformStyle", - "computed::TransformStyle::" + ("Flat" if engine == "gecko" else "Auto"), + "computed::TransformStyle::Flat", engines="gecko servo-2013 servo-2020", servo_2020_pref="layout.2020.unimplemented", spec="https://drafts.csswg.org/css-transforms-2/#transform-style-property", diff --git a/components/style/properties/longhands/column.mako.rs b/components/style/properties/longhands/column.mako.rs index 8b49679abf7..1974e82d137 100644 --- a/components/style/properties/longhands/column.mako.rs +++ b/components/style/properties/longhands/column.mako.rs @@ -13,7 +13,7 @@ ${helpers.predefined_type( engines="gecko servo-2013 servo-2020", servo_2020_pref="layout.2020.unimplemented", initial_specified_value="specified::length::NonNegativeLengthOrAuto::auto()", - extra_prefixes="moz", + extra_prefixes="moz:layout.css.prefixes.columns", animation_value_type="NonNegativeLengthOrAuto", servo_2013_pref="layout.columns.enabled", spec="https://drafts.csswg.org/css-multicol/#propdef-column-width", @@ -29,7 +29,7 @@ ${helpers.predefined_type( initial_specified_value="specified::ColumnCount::auto()", servo_2013_pref="layout.columns.enabled", animation_value_type="AnimatedColumnCount", - extra_prefixes="moz", + extra_prefixes="moz:layout.css.prefixes.columns", spec="https://drafts.csswg.org/css-multicol/#propdef-column-count", servo_restyle_damage="rebuild_and_reflow", )} @@ -38,7 +38,7 @@ ${helpers.single_keyword( "column-fill", "balance auto", engines="gecko", - extra_prefixes="moz", + extra_prefixes="moz:layout.css.prefixes.columns", animation_value_type="discrete", gecko_enum_prefix="StyleColumnFill", spec="https://drafts.csswg.org/css-multicol/#propdef-column-fill", @@ -53,7 +53,7 @@ ${helpers.predefined_type( computed_type="crate::values::computed::NonNegativeLength", spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-width", animation_value_type="NonNegativeLength", - extra_prefixes="moz", + extra_prefixes="moz:layout.css.prefixes.columns", )} // https://drafts.csswg.org/css-multicol-1/#crc @@ -64,22 +64,19 @@ ${helpers.predefined_type( engines="gecko", initial_specified_value="specified::Color::currentcolor()", animation_value_type="AnimatedColor", - extra_prefixes="moz", + extra_prefixes="moz:layout.css.prefixes.columns", ignored_when_colors_disabled=True, spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-color", )} -// FIXME: Remove enabled_in="ua" once column-span is enabled on nightly (bug 1423383). ${helpers.single_keyword( "column-span", "none all", engines="gecko", animation_value_type="discrete", gecko_enum_prefix="StyleColumnSpan", - gecko_pref="layout.css.column-span.enabled", - enabled_in="ua", spec="https://drafts.csswg.org/css-multicol/#propdef-column-span", - extra_prefixes="moz:layout.css.column-span.enabled", + extra_prefixes="moz:layout.css.prefixes.columns", )} ${helpers.predefined_type( @@ -89,7 +86,7 @@ ${helpers.predefined_type( engines="gecko", needs_context=False, initial_specified_value="specified::BorderStyle::None", - extra_prefixes="moz", + extra_prefixes="moz:layout.css.prefixes.columns", animation_value_type="discrete", spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-style", )} diff --git a/components/style/properties/longhands/inherited_box.mako.rs b/components/style/properties/longhands/inherited_box.mako.rs index 78c2e6023ee..e8f3a0a45d9 100644 --- a/components/style/properties/longhands/inherited_box.mako.rs +++ b/components/style/properties/longhands/inherited_box.mako.rs @@ -43,7 +43,7 @@ ${helpers.single_keyword( servo_2020_pref="layout.2020.unimplemented", animation_value_type="none", spec="https://drafts.csswg.org/css-writing-modes/#propdef-direction", - needs_conversion=True, + gecko_enum_prefix="StyleDirection", servo_restyle_damage="rebuild_and_reflow", )} @@ -78,6 +78,7 @@ ${helpers.single_keyword( extra_servo_2013_values="pixelated", extra_servo_2020_values="pixelated", gecko_aliases="-moz-crisp-edges=crisp-edges", + gecko_enum_prefix="StyleImageRendering", animation_value_type="discrete", spec="https://drafts.csswg.org/css-images/#propdef-image-rendering", )} diff --git a/components/style/properties/longhands/inherited_svg.mako.rs b/components/style/properties/longhands/inherited_svg.mako.rs index 1d75df08bd8..1839f90d6e6 100644 --- a/components/style/properties/longhands/inherited_svg.mako.rs +++ b/components/style/properties/longhands/inherited_svg.mako.rs @@ -17,6 +17,7 @@ ${helpers.single_keyword( engines="gecko", animation_value_type="discrete", spec="https://www.w3.org/TR/css-inline-3/#propdef-dominant-baseline", + gecko_enum_prefix="StyleDominantBaseline", )} ${helpers.single_keyword( @@ -118,6 +119,7 @@ ${helpers.single_keyword( engines="gecko", animation_value_type="discrete", spec="https://www.w3.org/TR/SVG11/painting.html#StrokeLinejoinProperty", + gecko_enum_prefix = "StyleStrokeLinejoin", )} ${helpers.predefined_type( diff --git a/components/style/properties/longhands/inherited_table.mako.rs b/components/style/properties/longhands/inherited_table.mako.rs index 06b42e802b6..488fe5d2659 100644 --- a/components/style/properties/longhands/inherited_table.mako.rs +++ b/components/style/properties/longhands/inherited_table.mako.rs @@ -20,7 +20,7 @@ ${helpers.single_keyword( "empty-cells", "show hide", engines="gecko servo-2013", - gecko_constant_prefix="NS_STYLE_TABLE_EMPTY_CELLS", + gecko_enum_prefix="StyleEmptyCells", animation_value_type="discrete", spec="https://drafts.csswg.org/css-tables/#propdef-empty-cells", servo_restyle_damage="rebuild_and_reflow", diff --git a/components/style/properties/longhands/inherited_text.mako.rs b/components/style/properties/longhands/inherited_text.mako.rs index 9784e2529e9..f01bedb177e 100644 --- a/components/style/properties/longhands/inherited_text.mako.rs +++ b/components/style/properties/longhands/inherited_text.mako.rs @@ -382,8 +382,8 @@ ${helpers.single_keyword( // text underline offset ${helpers.predefined_type( "text-underline-offset", - "TextDecorationLength", - "generics::text::GenericTextDecorationLength::Auto", + "LengthPercentageOrAuto", + "computed::LengthPercentageOrAuto::auto()", engines="gecko", animation_value_type="ComputedValue", gecko_pref="layout.css.text-underline-offset.enabled", diff --git a/components/style/properties/longhands/position.mako.rs b/components/style/properties/longhands/position.mako.rs index d974a0f5915..5e3cbf1324c 100644 --- a/components/style/properties/longhands/position.mako.rs +++ b/components/style/properties/longhands/position.mako.rs @@ -398,7 +398,7 @@ ${helpers.predefined_type( "computed::length::NonNegativeLengthPercentageOrNormal::normal()", engines="gecko servo-2013", alias="grid-column-gap" if engine == "gecko" else "", - extra_prefixes="moz", + extra_prefixes="moz:layout.css.prefixes.columns", servo_2013_pref="layout.columns.enabled", spec="https://drafts.csswg.org/css-align-3/#propdef-column-gap", animation_value_type="NonNegativeLengthPercentageOrNormal", diff --git a/components/style/properties/longhands/svg.mako.rs b/components/style/properties/longhands/svg.mako.rs index f25cbb8e019..724c38b2d1b 100644 --- a/components/style/properties/longhands/svg.mako.rs +++ b/components/style/properties/longhands/svg.mako.rs @@ -69,6 +69,7 @@ ${helpers.single_keyword( "mask-type", "luminance alpha", engines="gecko", + gecko_enum_prefix="StyleMaskType", animation_value_type="discrete", spec="https://drafts.fxtf.org/css-masking/#propdef-mask-type", )} diff --git a/components/style/properties/longhands/table.mako.rs b/components/style/properties/longhands/table.mako.rs index 0564942fc0a..104c28b8e04 100644 --- a/components/style/properties/longhands/table.mako.rs +++ b/components/style/properties/longhands/table.mako.rs @@ -12,6 +12,7 @@ ${helpers.single_keyword( engines="gecko servo-2013", gecko_ffi_name="mLayoutStrategy", animation_value_type="discrete", + gecko_enum_prefix="StyleTableLayout", spec="https://drafts.csswg.org/css-tables/#propdef-table-layout", servo_restyle_damage="reflow", )} diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 30a03f79290..d3e61867518 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -28,6 +28,7 @@ use crate::context::QuirksMode; #[cfg(feature = "servo")] use crate::computed_values; use crate::logical_geometry::WritingMode; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +use crate::computed_value_flags::*; use crate::media_queries::Device; use crate::parser::ParserContext; use crate::properties::longhands::system_font::SystemFont; @@ -45,7 +46,6 @@ use crate::values::computed::NonNegativeLength; use crate::values::serialize_atom_name; use crate::rule_tree::StrongRuleNode; use crate::Zero; -use self::computed_value_flags::*; use crate::str::{CssString, CssStringBorrow, CssStringWriter}; use std::cell::Cell; @@ -58,8 +58,6 @@ pub use self::cascade::*; import os.path %> -#[path="${repr(os.path.join(os.path.dirname(__file__), 'computed_value_flags.rs'))[1:-1]}"] -pub mod computed_value_flags; #[path="${repr(os.path.join(os.path.dirname(__file__), 'declaration_block.rs'))[1:-1]}"] pub mod declaration_block; #[path="${repr(os.path.join(os.path.dirname(__file__), 'cascade.rs'))[1:-1]}"] @@ -1584,7 +1582,7 @@ impl UnparsedValue { longhand_id: LonghandId, custom_properties: Option<<&Arc<crate::custom_properties::CustomPropertiesMap>>, quirks_mode: QuirksMode, - environment: &::custom_properties::CssEnvironment, + device: &Device, ) -> PropertyDeclaration { let invalid_at_computed_value_time = || { let keyword = if longhand_id.inherited() { @@ -1602,7 +1600,7 @@ impl UnparsedValue { &self.css, self.first_token_type, custom_properties, - environment, + device, ) { Ok(css) => css, Err(..) => return invalid_at_computed_value_time(), @@ -1803,8 +1801,11 @@ impl ToCss for PropertyId { } /// The counted unknown property list which is used for css use counters. +/// +/// FIXME: This should be just #[repr(u8)], but can't be because of ABI issues, +/// see https://bugs.llvm.org/show_bug.cgi?id=44228. #[derive(Clone, Copy, Debug, Eq, FromPrimitive, Hash, PartialEq)] -#[repr(u8)] +#[repr(u32)] pub enum CountedUnknownProperty { % for prop in data.counted_unknown_properties: /// ${prop.name} @@ -2894,9 +2895,18 @@ pub struct ComputedValues { /// We maintain this distinction in servo to reduce the amount of special /// casing. inner: ComputedValuesInner, + + /// The pseudo-element that we're using. + pseudo: Option<PseudoElement>, } impl ComputedValues { + /// Returns the pseudo-element that this style represents. + #[cfg(feature = "servo")] + pub fn pseudo(&self) -> Option<<&PseudoElement> { + self.pseudo.as_ref() + } + /// Returns whether this style's display value is equal to contents. pub fn is_display_contents(&self) -> bool { self.get_box().clone_display().is_contents() @@ -2999,7 +3009,7 @@ impl ComputedValues { impl ComputedValues { /// Create a new refcounted `ComputedValues` pub fn new( - _: Option<<&PseudoElement>, + pseudo: Option<<&PseudoElement>, custom_properties: Option<Arc<crate::custom_properties::CustomPropertiesMap>>, writing_mode: WritingMode, flags: ComputedValueFlags, @@ -3019,7 +3029,8 @@ impl ComputedValues { % for style_struct in data.active_style_structs(): ${style_struct.ident}, % endfor - } + }, + pseudo: pseudo.cloned(), }) } @@ -3433,6 +3444,9 @@ pub struct StyleBuilder<'a> { /// `StyleAdjuster` did any work. modified_reset: bool, + /// Whether this is the style for the root element. + pub is_root_element: bool, + /// The writing mode flags. /// /// TODO(emilio): Make private. @@ -3459,6 +3473,7 @@ impl<'a> StyleBuilder<'a> { pseudo: Option<<&'a PseudoElement>, rules: Option<StrongRuleNode>, custom_properties: Option<Arc<crate::custom_properties::CustomPropertiesMap>>, + is_root_element: bool, ) -> Self { debug_assert_eq!(parent_style.is_some(), parent_style_ignoring_first_line.is_some()); #[cfg(feature = "gecko")] @@ -3480,6 +3495,7 @@ impl<'a> StyleBuilder<'a> { pseudo, rules, modified_reset: false, + is_root_element, custom_properties, writing_mode: inherited_style.writing_mode, flags: Cell::new(flags), @@ -3518,6 +3534,7 @@ impl<'a> StyleBuilder<'a> { reset_style, pseudo: None, modified_reset: false, + is_root_element: false, rules: None, custom_properties: style_to_derive_from.custom_properties().cloned(), writing_mode: style_to_derive_from.writing_mode, @@ -3645,6 +3662,7 @@ impl<'a> StyleBuilder<'a> { pseudo, /* rules = */ None, parent.and_then(|p| p.custom_properties().cloned()), + /* is_root_element = */ false, ); ret.visited_style = visited_style; ret @@ -3827,9 +3845,9 @@ pub use self::lazy_static_module::INITIAL_SERVO_VALUES; #[allow(missing_docs)] mod lazy_static_module { use crate::logical_geometry::WritingMode; + use crate::computed_value_flags::ComputedValueFlags; use servo_arc::Arc; use super::{ComputedValues, ComputedValuesInner, longhands, style_structs}; - use super::computed_value_flags::ComputedValueFlags; lazy_static! { /// The initial values for all style structs as defined by the specification. @@ -3859,7 +3877,8 @@ mod lazy_static_module { rules: None, visited_style: None, flags: ComputedValueFlags::empty(), - } + }, + pseudo: None, }; } } diff --git a/components/style/properties/shorthands/column.mako.rs b/components/style/properties/shorthands/column.mako.rs index f9612fd6c0c..d5e385faa4c 100644 --- a/components/style/properties/shorthands/column.mako.rs +++ b/components/style/properties/shorthands/column.mako.rs @@ -9,7 +9,8 @@ sub_properties="column-width column-count" servo_2013_pref="layout.columns.enabled", derive_serialize="True" - extra_prefixes="moz" spec="https://drafts.csswg.org/css-multicol/#propdef-columns"> + extra_prefixes="moz:layout.css.prefixes.columns" + spec="https://drafts.csswg.org/css-multicol/#propdef-columns"> use crate::properties::longhands::{column_count, column_width}; pub fn parse_value<'i, 't>( @@ -59,7 +60,7 @@ <%helpers:shorthand name="column-rule" engines="gecko" - extra_prefixes="moz" + extra_prefixes="moz:layout.css.prefixes.columns" sub_properties="column-rule-width column-rule-style column-rule-color" derive_serialize="True" spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule" diff --git a/components/style/rule_tree/mod.rs b/components/style/rule_tree/mod.rs index 19e3accb851..113fa9cb9bb 100644 --- a/components/style/rule_tree/mod.rs +++ b/components/style/rule_tree/mod.rs @@ -136,6 +136,29 @@ impl StyleSource { let _ = write!(writer, " -> {:?}", self.read(guard).declarations()); } + // This is totally unsafe, should be removed when we figure out the cause of + // bug 1607553. + #[cfg(feature = "gecko")] + unsafe fn dump_unchecked<W: Write>(&self, writer: &mut W) { + if let Some(ref rule) = self.0.as_first() { + let rule = rule.read_unchecked(); + let _ = write!(writer, "{:?}", rule.selectors); + } + let _ = write!(writer, " -> {:?}", self.read_unchecked().declarations()); + } + + // This is totally unsafe, should be removed when we figure out the cause of + // bug 1607553. + #[inline] + #[cfg(feature = "gecko")] + unsafe fn read_unchecked(&self) -> &PropertyDeclarationBlock { + let block: &Locked<PropertyDeclarationBlock> = match self.0.borrow() { + ArcUnionBorrow::First(ref rule) => &rule.get().read_unchecked().block, + ArcUnionBorrow::Second(ref block) => block.get(), + }; + block.read_unchecked() + } + /// Read the style source guard, and obtain thus read access to the /// underlying property declaration block. #[inline] @@ -1437,7 +1460,6 @@ impl StrongRuleNode { use crate::gecko_bindings::structs::NS_AUTHOR_SPECIFIED_PADDING; use crate::properties::{CSSWideKeyword, LonghandId}; use crate::properties::{PropertyDeclaration, PropertyDeclarationId}; - use crate::values::specified::Color; use std::borrow::Cow; // Reset properties: @@ -1560,11 +1582,11 @@ impl StrongRuleNode { if is_author { if !author_colors_allowed { - // FIXME(emilio): this looks wrong, this should - // do: if color is not transparent, then return - // true, or something. if let PropertyDeclaration::BackgroundColor(ref color) = *declaration { - return *color == Color::transparent(); + if color.is_transparent() { + return true; + } + continue; } } return true; @@ -1696,6 +1718,7 @@ impl Clone for StrongRuleNode { } impl Drop for StrongRuleNode { + #[cfg_attr(feature = "servo", allow(unused_mut))] fn drop(&mut self) { let node = unsafe { &*self.ptr() }; @@ -1719,7 +1742,47 @@ impl Drop for StrongRuleNode { return; } - debug_assert!(node.children.read().is_empty()); + #[cfg(feature = "gecko")] + #[inline(always)] + fn assert_on_release() -> bool { + crate::gecko_bindings::structs::GECKO_IS_NIGHTLY + } + + #[cfg(feature = "servo")] + fn assert_on_release() -> bool { + false + } + + if cfg!(debug_assertions) || assert_on_release() { + let children = node.children.read(); + if !children.is_empty() { + let mut crash_str = vec![]; + + #[cfg(feature = "gecko")] + unsafe { + // Try to unsafely collect some information of this before + // crashing the process. + if let Some(ref s) = node.source { + s.dump_unchecked(&mut crash_str); + crash_str.push(b'\n'); + } + children.each(|child| { + (*child.ptr()) + .source + .as_ref() + .unwrap() + .dump_unchecked(&mut crash_str); + crash_str.push(b'\n'); + }); + } + + panic!( + "Children left in the rule tree on drop: {}", + String::from_utf8_lossy(&crash_str).trim() + ); + } + } + if node.parent.is_none() { debug!("Dropping root node!"); // The free list should be null by this point diff --git a/components/style/servo/media_queries.rs b/components/style/servo/media_queries.rs index 4cdfc80eef7..8c0ac014437 100644 --- a/components/style/servo/media_queries.rs +++ b/components/style/servo/media_queries.rs @@ -16,7 +16,7 @@ use crate::values::KeyframesName; use app_units::Au; use cssparser::RGBA; use euclid::default::Size2D as UntypedSize2D; -use euclid::{Scale, Size2D}; +use euclid::{Scale, SideOffsets2D, Size2D}; use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering}; use style_traits::viewport::ViewportConstraints; use style_traits::{CSSPixel, DevicePixel}; @@ -164,6 +164,16 @@ impl Device { pub fn default_background_color(&self) -> RGBA { RGBA::new(255, 255, 255, 255) } + + /// Returns the default color color. + pub fn default_color(&self) -> RGBA { + RGBA::new(0, 0, 0, 255) + } + + /// Returns safe area insets + pub fn safe_area_insets(&self) -> SideOffsets2D<f32, CSSPixel> { + SideOffsets2D::zero() + } } /// https://drafts.csswg.org/mediaqueries-4/#width diff --git a/components/style/sharing/checks.rs b/components/style/sharing/checks.rs index 19c1c52acdc..5e8350e78d3 100644 --- a/components/style/sharing/checks.rs +++ b/components/style/sharing/checks.rs @@ -81,7 +81,7 @@ where target.pres_hints() == candidate.pres_hints() } -/// Whether a given element has the same class attribute than a given candidate. +/// Whether a given element has the same class attribute as a given candidate. /// /// We don't try to share style across elements with different class attributes. pub fn have_same_class<E>( @@ -94,6 +94,19 @@ where target.class_list() == candidate.class_list() } +/// Whether a given element has the same part attribute as a given candidate. +/// +/// We don't try to share style across elements with different part attributes. +pub fn have_same_parts<E>( + target: &mut StyleSharingTarget<E>, + candidate: &mut StyleSharingCandidate<E>, +) -> bool +where + E: TElement, +{ + target.part_list() == candidate.part_list() +} + /// Whether a given element and a candidate match the same set of "revalidation" /// selectors. /// diff --git a/components/style/sharing/mod.rs b/components/style/sharing/mod.rs index 9b8c1275e4b..7f85104cb7c 100644 --- a/components/style/sharing/mod.rs +++ b/components/style/sharing/mod.rs @@ -124,10 +124,16 @@ impl OpaqueComputedValues { pub struct ValidationData { /// The class list of this element. /// - /// TODO(emilio): See if it's worth to sort them, or doing something else in - /// a similar fashion as what Boris is doing for the ID attribute. + /// TODO(emilio): Maybe check whether rules for these classes apply to the + /// element? class_list: Option<SmallVec<[Atom; 5]>>, + /// The part list of this element. + /// + /// TODO(emilio): Maybe check whether rules with these part names apply to + /// the element? + part_list: Option<SmallVec<[Atom; 5]>>, + /// The list of presentational attributes of the element. pres_hints: Option<SmallVec<[ApplicableDeclarationBlock; 5]>>, @@ -161,22 +167,41 @@ impl ValidationData { }) } + /// Get or compute the part-list associated with this element. + pub fn part_list<E>(&mut self, element: E) -> &[Atom] + where + E: TElement, + { + if !element.has_part_attr() { + return &[]; + } + self.part_list.get_or_insert_with(|| { + let mut list = SmallVec::<[Atom; 5]>::new(); + element.each_part(|p| list.push(p.clone())); + // See below for the reasoning. + if !list.spilled() { + list.sort_unstable_by_key(|a| a.get_hash()); + } + list + }) + } + /// Get or compute the class-list associated with this element. pub fn class_list<E>(&mut self, element: E) -> &[Atom] where E: TElement, { self.class_list.get_or_insert_with(|| { - let mut class_list = SmallVec::<[Atom; 5]>::new(); - element.each_class(|c| class_list.push(c.clone())); + let mut list = SmallVec::<[Atom; 5]>::new(); + element.each_class(|c| list.push(c.clone())); // Assuming there are a reasonable number of classes (we use the // inline capacity as "reasonable number"), sort them to so that // we don't mistakenly reject sharing candidates when one element // has "foo bar" and the other has "bar foo". - if !class_list.spilled() { - class_list.sort_by(|a, b| a.get_hash().cmp(&b.get_hash())); + if !list.spilled() { + list.sort_unstable_by_key(|a| a.get_hash()); } - class_list + list }) } @@ -273,6 +298,11 @@ impl<E: TElement> StyleSharingCandidate<E> { self.validation_data.class_list(self.element) } + /// Get the part list of this candidate. + fn part_list(&mut self) -> &[Atom] { + self.validation_data.part_list(self.element) + } + /// Get the pres hints of this candidate. fn pres_hints(&mut self) -> &[ApplicableDeclarationBlock] { self.validation_data.pres_hints(self.element) @@ -335,6 +365,10 @@ impl<E: TElement> StyleSharingTarget<E> { self.validation_data.class_list(self.element) } + fn part_list(&mut self) -> &[Atom] { + self.validation_data.part_list(self.element) + } + /// Get the pres hints of this candidate. fn pres_hints(&mut self) -> &[ApplicableDeclarationBlock] { self.validation_data.pres_hints(self.element) @@ -772,6 +806,11 @@ impl<E: TElement> StyleSharingCache<E> { return None; } + if !checks::have_same_parts(target, candidate) { + trace!("Miss: Shadow parts"); + return None; + } + if !checks::revalidate( target, candidate, diff --git a/components/style/style_adjuster.rs b/components/style/style_adjuster.rs index 6a3dc75f4d8..a2c6e5a2928 100644 --- a/components/style/style_adjuster.rs +++ b/components/style/style_adjuster.rs @@ -5,15 +5,13 @@ //! 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::computed_value_flags::ComputedValueFlags; 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}; -#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] -use crate::values::specified::box_::DisplayInside; use app_units::Au; /// A struct that implements all the adjustment methods. @@ -191,8 +189,7 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { }; } - let is_root = self.style.pseudo.is_none() && element.map_or(false, |e| e.is_root()); - blockify_if!(is_root); + 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()); @@ -207,7 +204,10 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { self.style.pseudo.map_or(false, |p| p.is_marker()) && self.style.get_parent_list().clone_list_style_position() == ListStylePosition::Outside && - layout_parent_style.get_box().clone_display().inside() != DisplayInside::Inline + !layout_parent_style + .get_box() + .clone_display() + .is_inline_flow() ); if !blockify { @@ -215,7 +215,7 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { } let display = self.style.get_box().clone_display(); - let blockified_display = display.equivalent_block_display(is_root); + let blockified_display = display.equivalent_block_display(self.style.is_root_element); if display != blockified_display { self.style .mutate_box() @@ -224,7 +224,7 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { } /// Compute a few common flags for both text and element's style. - pub fn set_bits(&mut self) { + fn set_bits(&mut self) { let display = self.style.get_box().clone_display(); if !display.is_contents() && @@ -243,6 +243,11 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { .add_flags(ComputedValueFlags::IS_IN_PSEUDO_ELEMENT_SUBTREE); } + if self.style.is_root_element { + self.style + .add_flags(ComputedValueFlags::IS_ROOT_ELEMENT_STYLE); + } + #[cfg(feature = "servo-layout-2013")] { if self.style.get_parent_column().is_multicol() { @@ -259,6 +264,7 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { /// 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(); @@ -283,8 +289,10 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { 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 writing_mode != WritingMode::HorizontalTb && - text_combine_upright == TextCombineUpright::All + if matches!( + writing_mode, + WritingMode::VerticalRl | WritingMode::VerticalLr + ) && text_combine_upright == TextCombineUpright::All { self.style.add_flags(ComputedValueFlags::IS_TEXT_COMBINED); self.style @@ -549,7 +557,14 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { } #[cfg(feature = "gecko")] - fn should_suppress_linebreak(&self, layout_parent_style: &ComputedValues) -> bool { + 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; @@ -569,11 +584,18 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { // 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 since they themselves are non-breakable. - Display::RubyBaseContainer | Display::RubyTextContainer => false, + // 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. @@ -595,7 +617,7 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { 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) { + if self.should_suppress_linebreak(layout_parent_style, element) { self.style .add_flags(ComputedValueFlags::SHOULD_SUPPRESS_LINEBREAK); // Inlinify the display type if allowed. diff --git a/components/style/style_resolver.rs b/components/style/style_resolver.rs index 180de3ff281..f3f2c6b41b7 100644 --- a/components/style/style_resolver.rs +++ b/components/style/style_resolver.rs @@ -110,7 +110,7 @@ fn eager_pseudo_is_definitely_not_generated( pseudo: &PseudoElement, style: &ComputedValues, ) -> bool { - use crate::properties::computed_value_flags::ComputedValueFlags; + use crate::computed_value_flags::ComputedValueFlags; if !pseudo.is_before_or_after() { return false; diff --git a/components/style/stylesheets/viewport_rule.rs b/components/style/stylesheets/viewport_rule.rs index 7d0881e3336..cd875eefb69 100644 --- a/components/style/stylesheets/viewport_rule.rs +++ b/components/style/stylesheets/viewport_rule.rs @@ -733,7 +733,6 @@ impl MaybeNew for ViewportConstraints { let mut conditions = RuleCacheConditions::default(); let context = Context { - is_root_element: false, // Note: DEVICE-ADAPT § 5. states that relative length values are // resolved against initial values builder: StyleBuilder::for_inheritance(device, None, None), diff --git a/components/style/traversal.rs b/components/style/traversal.rs index f73a533b718..254d40f0393 100644 --- a/components/style/traversal.rs +++ b/components/style/traversal.rs @@ -775,7 +775,7 @@ fn note_children<E, D, F>( child_hint |= RestyleHint::RECASCADE_SELF | RestyleHint::RECASCADE_DESCENDANTS; }, ChildCascadeRequirement::MustCascadeChildrenIfInheritResetStyle => { - use crate::properties::computed_value_flags::ComputedValueFlags; + use crate::computed_value_flags::ComputedValueFlags; if child_data .styles .primary() diff --git a/components/style/values/animated/length.rs b/components/style/values/animated/length.rs index 04a0844dbe0..04690446e64 100644 --- a/components/style/values/animated/length.rs +++ b/components/style/values/animated/length.rs @@ -7,6 +7,7 @@ use super::{Animate, Procedure}; use crate::values::computed::length::LengthPercentage; use crate::values::computed::Percentage; +use style_traits::values::specified::AllowedNumericType; /// <https://drafts.csswg.org/css-transitions/#animtype-lpcalc> impl Animate for LengthPercentage { @@ -26,10 +27,13 @@ impl Animate for LengthPercentage { .animate(&other.unclamped_length(), procedure)?; let percentage = animate_percentage_half(self.specified_percentage(), other.specified_percentage())?; - Ok(Self::with_clamping_mode( + + // Gets clamped as needed after the animation if needed, so no need to + // specify any particular AllowedNumericType. + Ok(LengthPercentage::new_calc( length, percentage, - self.clamping_mode, + AllowedNumericType::All, )) } } diff --git a/components/style/values/animated/mod.rs b/components/style/values/animated/mod.rs index ea43d01112e..7e699542fd4 100644 --- a/components/style/values/animated/mod.rs +++ b/components/style/values/animated/mod.rs @@ -107,7 +107,7 @@ pub fn animate_multiplicative_factor( /// be equal or an error is returned. /// /// If a variant is annotated with `#[animation(error)]`, the corresponding -/// `match` arm is not generated. +/// `match` arm returns an error. /// /// If the two values are not similar, an error is returned unless a fallback /// function has been specified through `#[animate(fallback)]`. @@ -424,6 +424,16 @@ impl ToAnimatedZero for i32 { } } +impl<T> ToAnimatedZero for Box<T> +where + T: ToAnimatedZero, +{ + #[inline] + fn to_animated_zero(&self) -> Result<Self, ()> { + Ok(Box::new((**self).to_animated_zero()?)) + } +} + impl<T> ToAnimatedZero for Option<T> where T: ToAnimatedZero, diff --git a/components/style/values/animated/transform.rs b/components/style/values/animated/transform.rs index 3d93ae93f6d..621ae60084d 100644 --- a/components/style/values/animated/transform.rs +++ b/components/style/values/animated/transform.rs @@ -1168,10 +1168,11 @@ impl ComputeSquaredDistance for ComputedTransformOperation { // However, dropping percentage makes us impossible to compute // the distance for the percentage-percentage case, but Gecko // uses the same formula, so it's fine for now. - let fx = fx.length_component().px(); - let fy = fy.length_component().px(); - let tx = tx.length_component().px(); - let ty = ty.length_component().px(); + let basis = Length::new(0.); + let fx = fx.resolve(basis).px(); + let fy = fy.resolve(basis).px(); + let tx = tx.resolve(basis).px(); + let ty = ty.resolve(basis).px(); Ok(fx.compute_squared_distance(&tx)? + fy.compute_squared_distance(&ty)? + diff --git a/components/style/values/computed/align.rs b/components/style/values/computed/align.rs index ea6088db5d5..45d6cfac323 100644 --- a/components/style/values/computed/align.rs +++ b/components/style/values/computed/align.rs @@ -9,7 +9,9 @@ use crate::values::computed::{Context, ToComputedValue}; use crate::values::specified; -pub use super::specified::{AlignContent, AlignItems, JustifyContent, SelfAlignment}; +pub use super::specified::{ + AlignContent, AlignItems, ContentDistribution, JustifyContent, SelfAlignment, +}; pub use super::specified::{AlignSelf, JustifySelf}; /// The computed value for the `justify-items` property. @@ -34,7 +36,8 @@ pub use super::specified::{AlignSelf, JustifySelf}; /// /// See the discussion in https://bugzil.la/1384542. #[derive(Clone, Copy, Debug, Eq, PartialEq, ToCss, ToResolvedValue)] -pub struct JustifyItems { +#[repr(C)] +pub struct ComputedJustifyItems { /// The specified value for the property. Can contain the bare `legacy` /// keyword. #[css(skip)] @@ -45,6 +48,8 @@ pub struct JustifyItems { pub computed: specified::JustifyItems, } +pub use self::ComputedJustifyItems as JustifyItems; + impl JustifyItems { /// Returns the `legacy` value. pub fn legacy() -> Self { diff --git a/components/style/values/computed/counters.rs b/components/style/values/computed/counters.rs index 3a083632eb9..40cfe46efa6 100644 --- a/components/style/values/computed/counters.rs +++ b/components/style/values/computed/counters.rs @@ -16,7 +16,7 @@ pub type CounterIncrement = GenericCounterIncrement<i32>; pub type CounterSetOrReset = GenericCounterSetOrReset<i32>; /// A computed value for the `content` property. -pub type Content = generics::Content<ComputedImageUrl>; +pub type Content = generics::GenericContent<ComputedImageUrl>; /// A computed content item. -pub type ContentItem = generics::ContentItem<ComputedImageUrl>; +pub type ContentItem = generics::GenericContentItem<ComputedImageUrl>; diff --git a/components/style/values/computed/length.rs b/components/style/values/computed/length.rs index 485ef954d8e..eedd05d1f15 100644 --- a/components/style/values/computed/length.rs +++ b/components/style/values/computed/length.rs @@ -4,27 +4,24 @@ //! `<length>` computed values, and related ones. -use super::{Context, Number, Percentage, ToComputedValue}; +use super::{Context, Number, ToComputedValue}; use crate::values::animated::ToAnimatedValue; use crate::values::computed::NonNegativeNumber; -use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; use crate::values::generics::length as generics; use crate::values::generics::length::{ GenericLengthOrNumber, GenericLengthPercentageOrNormal, GenericMaxSize, GenericSize, }; use crate::values::generics::NonNegative; -use crate::values::specified::length::ViewportPercentageLength; -use crate::values::specified::length::{AbsoluteLength, FontBaseSize, FontRelativeLength}; +use crate::values::specified::length::{AbsoluteLength, FontBaseSize}; use crate::values::{specified, CSSFloat}; use crate::Zero; use app_units::Au; -use ordered_float::NotNan; use std::fmt::{self, Write}; use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub}; -use style_traits::values::specified::AllowedNumericType; use style_traits::{CSSPixel, CssWriter, ToCss}; pub use super::image::Image; +pub use super::length_percentage::{LengthPercentage, NonNegativeLengthPercentage}; pub use crate::values::specified::url::UrlOrNone; pub use crate::values::specified::{Angle, BorderStyle, Time}; @@ -54,13 +51,15 @@ impl ToComputedValue for specified::NoCalcLength { } impl ToComputedValue for specified::Length { - type ComputedValue = CSSPixelLength; + type ComputedValue = Length; #[inline] fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { match *self { specified::Length::NoCalc(l) => l.to_computed_value(context), - specified::Length::Calc(ref calc) => calc.to_computed_value(context).length(), + specified::Length::Calc(ref calc) => { + calc.to_computed_value(context).to_length().unwrap() + }, } } @@ -70,382 +69,6 @@ impl ToComputedValue for specified::Length { } } -/// A `<length-percentage>` value. This can be either a `<length>`, a -/// `<percentage>`, or a combination of both via `calc()`. -/// -/// https://drafts.csswg.org/css-values-4/#typedef-length-percentage -#[allow(missing_docs)] -#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize, ToAnimatedZero, ToResolvedValue)] -#[repr(C)] -pub struct LengthPercentage { - length: Length, - percentage: Percentage, - #[animation(constant)] - pub clamping_mode: AllowedNumericType, - /// Whether we specified a percentage or not. - #[animation(constant)] - pub has_percentage: bool, -} - -// NOTE(emilio): We don't compare `clamping_mode` since we want to preserve the -// invariant that `from_computed_value(length).to_computed_value(..) == length`. -// -// Right now for e.g. a non-negative length, we set clamping_mode to `All` -// unconditionally for non-calc values, and to `NonNegative` for calc. -// -// If we determine that it's sound, from_computed_value() can generate an -// absolute length, which then would get `All` as the clamping mode. -// -// We may want to just eagerly-detect whether we can clamp in -// `LengthPercentage::new` and switch to `AllowedNumericType::NonNegative` then, -// maybe. -impl PartialEq for LengthPercentage { - fn eq(&self, other: &Self) -> bool { - self.length == other.length && - self.percentage == other.percentage && - self.has_percentage == other.has_percentage - } -} - -impl ComputeSquaredDistance for LengthPercentage { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - // FIXME(nox): This looks incorrect to me, to add a distance between lengths - // with a distance between percentages. - Ok(self - .unclamped_length() - .compute_squared_distance(&other.unclamped_length())? + - self.percentage - .compute_squared_distance(&other.percentage)?) - } -} - -impl LengthPercentage { - /// Returns a new `LengthPercentage`. - #[inline] - pub fn new(length: Length, percentage: Option<Percentage>) -> Self { - Self::with_clamping_mode(length, percentage, AllowedNumericType::All) - } - - /// Returns a new `LengthPercentage` with zero length and some percentage. - pub fn new_percent(percentage: Percentage) -> Self { - Self::new(Length::zero(), Some(percentage)) - } - - /// Returns a new `LengthPercentage` with a specific clamping mode. - #[inline] - pub fn with_clamping_mode( - length: Length, - percentage: Option<Percentage>, - clamping_mode: AllowedNumericType, - ) -> Self { - Self { - clamping_mode, - length, - percentage: percentage.unwrap_or_default(), - has_percentage: percentage.is_some(), - } - } - - /// Returns this `calc()` as a `<length>`. - /// - /// Panics in debug mode if a percentage is present in the expression. - #[inline] - pub fn length(&self) -> CSSPixelLength { - debug_assert!(!self.has_percentage); - self.length_component() - } - - /// Returns the length component of this `calc()` - #[inline] - pub fn length_component(&self) -> CSSPixelLength { - CSSPixelLength::new(self.clamping_mode.clamp(self.length.px())) - } - - /// Returns the `<length>` component of this `calc()`, unclamped. - #[inline] - pub fn unclamped_length(&self) -> CSSPixelLength { - self.length - } - - /// Returns the percentage component of this `calc()` - #[inline] - pub fn percentage_component(&self) -> Percentage { - Percentage(self.clamping_mode.clamp(self.percentage.0)) - } - - /// Return the percentage value as CSSFloat. - #[inline] - pub fn percentage(&self) -> CSSFloat { - self.percentage.0 - } - - /// Return the specified percentage if any. - #[inline] - pub fn specified_percentage(&self) -> Option<Percentage> { - if self.has_percentage { - Some(self.percentage) - } else { - None - } - } - - /// Returns the length component if this could be represented as a - /// non-calc length. - pub fn as_length(&self) -> Option<Length> { - if !self.has_percentage { - Some(self.length_component()) - } else { - None - } - } - - /// Returns the percentage component if this could be represented as a - /// non-calc percentage. - pub fn as_percentage(&self) -> Option<Percentage> { - if !self.has_percentage || self.length.px() != 0. { - return None; - } - - Some(Percentage(self.clamping_mode.clamp(self.percentage.0))) - } - - /// Resolves the percentage. - #[inline] - pub fn percentage_relative_to(&self, basis: Length) -> Length { - let length = self.unclamped_length().0 + basis.0 * self.percentage.0; - Length::new(self.clamping_mode.clamp(length)) - } - - /// Convert the computed value into used value. - #[inline] - pub fn maybe_to_used_value(&self, container_len: Option<Length>) -> Option<Au> { - self.maybe_percentage_relative_to(container_len) - .map(Au::from) - } - - /// If there are special rules for computing percentages in a value (e.g. - /// the height property), they apply whenever a calc() expression contains - /// percentages. - pub fn maybe_percentage_relative_to(&self, container_len: Option<Length>) -> Option<Length> { - if self.has_percentage { - return Some(self.percentage_relative_to(container_len?)); - } - Some(self.length()) - } -} - -impl ToCss for LengthPercentage { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - specified::LengthPercentage::from_computed_value(self).to_css(dest) - } -} - -impl specified::CalcLengthPercentage { - /// Compute the value, zooming any absolute units by the zoom function. - fn to_computed_value_with_zoom<F>( - &self, - context: &Context, - zoom_fn: F, - base_size: FontBaseSize, - ) -> LengthPercentage - where - F: Fn(Length) -> Length, - { - use std::f32; - let mut length = 0.; - - if let Some(absolute) = self.absolute { - length += zoom_fn(absolute.to_computed_value(context)).px(); - } - - for val in &[ - self.vw.map(ViewportPercentageLength::Vw), - self.vh.map(ViewportPercentageLength::Vh), - self.vmin.map(ViewportPercentageLength::Vmin), - self.vmax.map(ViewportPercentageLength::Vmax), - ] { - if let Some(val) = *val { - let viewport_size = context.viewport_size_for_viewport_unit_resolution(); - length += val.to_computed_value(viewport_size).px(); - } - } - - for val in &[ - self.ch.map(FontRelativeLength::Ch), - self.em.map(FontRelativeLength::Em), - self.ex.map(FontRelativeLength::Ex), - self.rem.map(FontRelativeLength::Rem), - ] { - if let Some(val) = *val { - length += val.to_computed_value(context, base_size).px(); - } - } - - LengthPercentage::with_clamping_mode( - Length::new(length.min(f32::MAX).max(f32::MIN)), - self.percentage, - self.clamping_mode, - ) - } - - /// Compute font-size or line-height taking into account text-zoom if necessary. - pub fn to_computed_value_zoomed( - &self, - context: &Context, - base_size: FontBaseSize, - ) -> LengthPercentage { - self.to_computed_value_with_zoom( - context, - |abs| context.maybe_zoom_text(abs.into()), - base_size, - ) - } - - /// Compute the value into pixel length as CSSFloat without context, - /// so it returns Err(()) if there is any non-absolute unit. - pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> { - if self.vw.is_some() || - self.vh.is_some() || - self.vmin.is_some() || - self.vmax.is_some() || - self.em.is_some() || - self.ex.is_some() || - self.ch.is_some() || - self.rem.is_some() || - self.percentage.is_some() - { - return Err(()); - } - - match self.absolute { - Some(abs) => Ok(abs.to_px()), - None => { - debug_assert!(false, "Someone forgot to handle an unit here: {:?}", self); - Err(()) - }, - } - } -} - -impl ToComputedValue for specified::CalcLengthPercentage { - type ComputedValue = LengthPercentage; - - fn to_computed_value(&self, context: &Context) -> LengthPercentage { - // normal properties don't zoom, and compute em units against the current style's font-size - self.to_computed_value_with_zoom(context, |abs| abs, FontBaseSize::CurrentStyle) - } - - #[inline] - fn from_computed_value(computed: &LengthPercentage) -> Self { - specified::CalcLengthPercentage { - clamping_mode: computed.clamping_mode, - absolute: Some(AbsoluteLength::from_computed_value(&computed.length)), - percentage: computed.specified_percentage(), - ..Default::default() - } - } -} - -impl LengthPercentage { - /// 1px length value for SVG defaults - #[inline] - pub fn one() -> LengthPercentage { - LengthPercentage::new(Length::new(1.), None) - } - - /// Returns true if the computed value is absolute 0 or 0%. - #[inline] - pub fn is_definitely_zero(&self) -> bool { - self.unclamped_length().px() == 0.0 && self.percentage.0 == 0.0 - } - - // CSSFloat doesn't implement Hash, so does CSSPixelLength. Therefore, we - // still use Au as the hash key. - #[allow(missing_docs)] - pub fn to_hash_key(&self) -> (Au, NotNan<f32>) { - ( - Au::from(self.unclamped_length()), - NotNan::new(self.percentage.0).unwrap(), - ) - } - - /// Returns the used value. - pub fn to_used_value(&self, containing_length: Au) -> Au { - Au::from(self.to_pixel_length(containing_length)) - } - - /// Returns the used value as CSSPixelLength. - pub fn to_pixel_length(&self, containing_length: Au) -> Length { - self.percentage_relative_to(containing_length.into()) - } - - /// Returns the clamped non-negative values. - #[inline] - pub fn clamp_to_non_negative(self) -> Self { - if let Some(p) = self.specified_percentage() { - // If we can eagerly clamp the percentage then just do that. - if self.length.is_zero() { - return Self::with_clamping_mode( - Length::zero(), - Some(p.clamp_to_non_negative()), - AllowedNumericType::NonNegative, - ); - } - - return Self::with_clamping_mode(self.length, Some(p), AllowedNumericType::NonNegative); - } - - Self::with_clamping_mode( - self.length.clamp_to_non_negative(), - None, - AllowedNumericType::NonNegative, - ) - } -} - -impl ToComputedValue for specified::LengthPercentage { - type ComputedValue = LengthPercentage; - - fn to_computed_value(&self, context: &Context) -> LengthPercentage { - match *self { - specified::LengthPercentage::Length(ref value) => { - LengthPercentage::new(value.to_computed_value(context), None) - }, - specified::LengthPercentage::Percentage(value) => LengthPercentage::new_percent(value), - specified::LengthPercentage::Calc(ref calc) => (**calc).to_computed_value(context), - } - } - - fn from_computed_value(computed: &LengthPercentage) -> Self { - if let Some(p) = computed.as_percentage() { - return specified::LengthPercentage::Percentage(p); - } - - if !computed.has_percentage { - return specified::LengthPercentage::Length(ToComputedValue::from_computed_value( - &computed.length(), - )); - } - - specified::LengthPercentage::Calc(Box::new(ToComputedValue::from_computed_value(computed))) - } -} - -impl Zero for LengthPercentage { - fn zero() -> Self { - LengthPercentage::new(Length::zero(), None) - } - - #[inline] - fn is_zero(&self) -> bool { - self.is_definitely_zero() - } -} - /// Some boilerplate to share between negative and non-negative /// length-percentage or auto. macro_rules! computed_length_percentage_or_auto { @@ -521,70 +144,6 @@ impl NonNegativeLengthPercentageOrAuto { computed_length_percentage_or_auto!(NonNegativeLengthPercentage); } -/// A wrapper of LengthPercentage, whose value must be >= 0. -pub type NonNegativeLengthPercentage = NonNegative<LengthPercentage>; - -impl ToAnimatedValue for NonNegativeLengthPercentage { - type AnimatedValue = LengthPercentage; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - self.0 - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - NonNegative(animated.clamp_to_non_negative()) - } -} - -impl From<NonNegativeLength> for NonNegativeLengthPercentage { - #[inline] - fn from(length: NonNegativeLength) -> Self { - NonNegative(LengthPercentage::new(length.0, None)) - } -} - -impl From<LengthPercentage> for NonNegativeLengthPercentage { - #[inline] - fn from(lp: LengthPercentage) -> Self { - NonNegative(lp) - } -} - -// TODO(emilio): This is a really generic impl which is only needed to implement -// Animated and co for Spacing<>. Get rid of this, probably? -impl From<Au> for LengthPercentage { - #[inline] - fn from(length: Au) -> Self { - LengthPercentage::new(length.into(), None) - } -} - -impl NonNegativeLengthPercentage { - /// Returns true if the computed value is absolute 0 or 0%. - #[inline] - pub fn is_definitely_zero(&self) -> bool { - self.0.is_definitely_zero() - } - - /// Returns the used value. - #[inline] - pub fn to_used_value(&self, containing_length: Au) -> Au { - let resolved = self.0.to_used_value(containing_length); - ::std::cmp::max(resolved, Au(0)) - } - - /// Convert the computed value into used value. - #[inline] - pub fn maybe_to_used_value(&self, containing_length: Option<Au>) -> Option<Au> { - let resolved = self - .0 - .maybe_to_used_value(containing_length.map(|v| v.into()))?; - Some(::std::cmp::max(resolved, Au(0))) - } -} - #[cfg(feature = "servo")] impl MaxSize { /// Convert the computed value into used value. diff --git a/components/style/values/computed/length_percentage.rs b/components/style/values/computed/length_percentage.rs new file mode 100644 index 00000000000..96bf76c98d4 --- /dev/null +++ b/components/style/values/computed/length_percentage.rs @@ -0,0 +1,793 @@ +/* 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/. */ + +//! `<length-percentage>` computed values, and related ones. +//! +//! The over-all design is a tagged pointer, with the lower bits of the pointer +//! being non-zero if it is a non-calc value. +//! +//! It is expected to take 64 bits both in x86 and x86-64. This is implemented +//! as a `union`, with 4 different variants: +//! +//! * The length and percentage variants have a { tag, f32 } (effectively) +//! layout. The tag has to overlap with the lower 2 bits of the calc variant. +//! +//! * The `calc()` variant is a { tag, pointer } in x86 (so same as the +//! others), or just a { pointer } in x86-64 (so that the two bits of the tag +//! can be obtained from the lower bits of the pointer). +//! +//! * There's a `tag` variant just to make clear when only the tag is intended +//! to be read. Note that the tag needs to be masked always by `TAG_MASK`, to +//! deal with the pointer variant in x86-64. +//! +//! The assertions in the constructor methods ensure that the tag getter matches +//! our expectations. + +use super::{Context, Length, Percentage, ToComputedValue}; +use crate::values::animated::{ToAnimatedValue, ToAnimatedZero}; +use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; +use crate::values::generics::NonNegative; +use crate::values::specified::length::FontBaseSize; +use crate::values::{specified, CSSFloat}; +use crate::Zero; +use app_units::Au; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Write}; +use style_traits::values::specified::AllowedNumericType; +use style_traits::{CssWriter, ToCss}; + +#[doc(hidden)] +#[derive(Clone, Copy)] +#[repr(C)] +pub struct LengthVariant { + tag: u8, + length: Length, +} + +#[doc(hidden)] +#[derive(Clone, Copy)] +#[repr(C)] +pub struct PercentageVariant { + tag: u8, + percentage: Percentage, +} + +// NOTE(emilio): cbindgen only understands the #[cfg] on the top level +// definition. +#[doc(hidden)] +#[derive(Clone, Copy)] +#[repr(C)] +#[cfg(target_pointer_width = "32")] +pub struct CalcVariant { + tag: u8, + ptr: *mut CalcLengthPercentage, +} + +#[doc(hidden)] +#[derive(Clone, Copy)] +#[repr(C)] +#[cfg(target_pointer_width = "64")] +pub struct CalcVariant { + ptr: usize, // In little-endian byte order +} + +// `CalcLengthPercentage` is `Send + Sync` as asserted below. +unsafe impl Send for CalcVariant {} +unsafe impl Sync for CalcVariant {} + +#[doc(hidden)] +#[derive(Clone, Copy)] +#[repr(C)] +pub struct TagVariant { + tag: u8, +} + +/// A `<length-percentage>` value. This can be either a `<length>`, a +/// `<percentage>`, or a combination of both via `calc()`. +/// +/// cbindgen:private-default-tagged-enum-constructor=false +/// cbindgen:derive-mut-casts=true +/// +/// https://drafts.csswg.org/css-values-4/#typedef-length-percentage +/// +/// The tag is stored in the lower two bits. +/// +/// We need to use a struct instead of the union directly because unions with +/// Drop implementations are unstable, looks like. +/// +/// Also we need the union and the variants to be `pub` (even though the member +/// is private) so that cbindgen generates it. They're not part of the public +/// API otherwise. +#[repr(transparent)] +pub struct LengthPercentage(LengthPercentageUnion); + +#[doc(hidden)] +#[repr(C)] +pub union LengthPercentageUnion { + length: LengthVariant, + percentage: PercentageVariant, + calc: CalcVariant, + tag: TagVariant, +} + +impl LengthPercentageUnion { + #[doc(hidden)] // Need to be public so that cbindgen generates it. + pub const TAG_CALC: u8 = 0; + #[doc(hidden)] + pub const TAG_LENGTH: u8 = 1; + #[doc(hidden)] + pub const TAG_PERCENTAGE: u8 = 2; + #[doc(hidden)] + pub const TAG_MASK: u8 = 0b11; +} + +#[derive(Clone, Copy, Debug, PartialEq)] +#[repr(u8)] +enum Tag { + Calc = LengthPercentageUnion::TAG_CALC, + Length = LengthPercentageUnion::TAG_LENGTH, + Percentage = LengthPercentageUnion::TAG_PERCENTAGE, +} + +// All the members should be 64 bits, even in 32-bit builds. +#[allow(unused)] +unsafe fn static_assert() { + fn assert_send_and_sync<T: Send + Sync>() {} + std::mem::transmute::<u64, LengthVariant>(0u64); + std::mem::transmute::<u64, PercentageVariant>(0u64); + std::mem::transmute::<u64, CalcVariant>(0u64); + std::mem::transmute::<u64, LengthPercentage>(0u64); + assert_send_and_sync::<LengthVariant>(); + assert_send_and_sync::<PercentageVariant>(); + assert_send_and_sync::<CalcLengthPercentage>(); +} + +impl Drop for LengthPercentage { + fn drop(&mut self) { + if self.tag() == Tag::Calc { + let _ = unsafe { Box::from_raw(self.calc_ptr()) }; + } + } +} + +impl MallocSizeOf for LengthPercentage { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + match self.unpack() { + Unpacked::Length(..) | Unpacked::Percentage(..) => 0, + Unpacked::Calc(c) => unsafe { ops.malloc_size_of(c) }, + } + } +} + +/// An unpacked `<length-percentage>` that borrows the `calc()` variant. +#[derive(Clone, Debug, PartialEq)] +enum Unpacked<'a> { + Calc(&'a CalcLengthPercentage), + Length(Length), + Percentage(Percentage), +} + +/// An unpacked `<length-percentage>` that owns the `calc()` variant, for +/// serialization purposes. +#[derive(Deserialize, PartialEq, Serialize)] +enum Serializable { + Calc(CalcLengthPercentage), + Length(Length), + Percentage(Percentage), +} + +impl LengthPercentage { + /// 1px length value for SVG defaults + #[inline] + pub fn one() -> Self { + Self::new_length(Length::new(1.)) + } + + /// Constructs a length value. + #[inline] + pub fn new_length(length: Length) -> Self { + let length = Self(LengthPercentageUnion { + length: LengthVariant { + tag: LengthPercentageUnion::TAG_LENGTH, + length, + }, + }); + debug_assert_eq!(length.tag(), Tag::Length); + length + } + + /// Constructs a percentage value. + #[inline] + pub fn new_percent(percentage: Percentage) -> Self { + let percent = Self(LengthPercentageUnion { + percentage: PercentageVariant { + tag: LengthPercentageUnion::TAG_PERCENTAGE, + percentage, + }, + }); + debug_assert_eq!(percent.tag(), Tag::Percentage); + percent + } + + /// Constructs a `calc()` value. + #[inline] + pub fn new_calc( + length: Length, + percentage: Option<Percentage>, + clamping_mode: AllowedNumericType, + ) -> Self { + let percentage = match percentage { + Some(p) => p, + None => return Self::new_length(Length::new(clamping_mode.clamp(length.px()))), + }; + if length.is_zero() { + return Self::new_percent(Percentage(clamping_mode.clamp(percentage.0))); + } + Self::new_calc_unchecked(Box::new(CalcLengthPercentage { + length, + percentage, + clamping_mode, + })) + } + + /// Private version of new_calc() that constructs a calc() variant without + /// checking. + fn new_calc_unchecked(calc: Box<CalcLengthPercentage>) -> Self { + let ptr = Box::into_raw(calc); + + #[cfg(target_pointer_width = "32")] + let calc = CalcVariant { + tag: LengthPercentageUnion::TAG_CALC, + ptr, + }; + + #[cfg(target_pointer_width = "64")] + let calc = CalcVariant { + #[cfg(target_endian = "little")] + ptr: ptr as usize, + #[cfg(target_endian = "big")] + ptr: (ptr as usize).swap_bytes(), + }; + + let calc = Self(LengthPercentageUnion { calc }); + debug_assert_eq!(calc.tag(), Tag::Calc); + calc + } + + #[inline] + fn tag(&self) -> Tag { + match unsafe { self.0.tag.tag & LengthPercentageUnion::TAG_MASK } { + LengthPercentageUnion::TAG_CALC => Tag::Calc, + LengthPercentageUnion::TAG_LENGTH => Tag::Length, + LengthPercentageUnion::TAG_PERCENTAGE => Tag::Percentage, + _ => unreachable!("Bogus tag?"), + } + } + + #[inline] + fn unpack<'a>(&'a self) -> Unpacked<'a> { + unsafe { + match self.tag() { + Tag::Calc => Unpacked::Calc(&*self.calc_ptr()), + Tag::Length => Unpacked::Length(self.0.length.length), + Tag::Percentage => Unpacked::Percentage(self.0.percentage.percentage), + } + } + } + + #[inline] + unsafe fn calc_ptr(&self) -> *mut CalcLengthPercentage { + #[cfg(not(all(target_endian = "big", target_pointer_width = "64")))] + { + self.0.calc.ptr as *mut _ + } + #[cfg(all(target_endian = "big", target_pointer_width = "64"))] + { + self.0.calc.ptr.swap_bytes() as *mut _ + } + } + + #[inline] + fn to_serializable(&self) -> Serializable { + match self.unpack() { + Unpacked::Calc(c) => Serializable::Calc(c.clone()), + Unpacked::Length(l) => Serializable::Length(l), + Unpacked::Percentage(p) => Serializable::Percentage(p), + } + } + + #[inline] + fn from_serializable(s: Serializable) -> Self { + match s { + Serializable::Calc(c) => Self::new_calc_unchecked(Box::new(c)), + Serializable::Length(l) => Self::new_length(l), + Serializable::Percentage(p) => Self::new_percent(p), + } + } + + /// Returns true if the computed value is absolute 0 or 0%. + #[inline] + pub fn is_definitely_zero(&self) -> bool { + match self.unpack() { + Unpacked::Length(l) => l.px() == 0.0, + Unpacked::Percentage(p) => p.0 == 0.0, + Unpacked::Calc(ref c) => { + debug_assert_ne!( + c.length.px(), + 0.0, + "Should've been simplified to a percentage" + ); + false + }, + } + } + + /// Returns the `<length>` component of this `calc()`, unclamped. + #[inline] + pub fn unclamped_length(&self) -> Length { + match self.unpack() { + Unpacked::Length(l) => l, + Unpacked::Percentage(..) => Zero::zero(), + Unpacked::Calc(c) => c.unclamped_length(), + } + } + + /// Returns this `calc()` as a `<length>`. + /// + /// Panics in debug mode if a percentage is present in the expression. + #[inline] + fn length(&self) -> Length { + debug_assert!(!self.has_percentage()); + self.length_component() + } + + /// Returns the `<length>` component of this `calc()`, clamped. + #[inline] + pub fn length_component(&self) -> Length { + match self.unpack() { + Unpacked::Length(l) => l, + Unpacked::Percentage(..) => Zero::zero(), + Unpacked::Calc(c) => c.length_component(), + } + } + + /// Returns the `<percentage>` component of this `calc()`, unclamped, as a + /// float. + /// + /// FIXME: This are very different semantics from length(), we should + /// probably rename this. + #[inline] + pub fn percentage(&self) -> CSSFloat { + match self.unpack() { + Unpacked::Length(..) => 0., + Unpacked::Percentage(p) => p.0, + Unpacked::Calc(c) => c.percentage.0, + } + } + + /// Resolves the percentage. + #[inline] + pub fn resolve(&self, basis: Length) -> Length { + match self.unpack() { + Unpacked::Length(l) => l, + Unpacked::Percentage(p) => Length::new(basis.px() * p.0), + Unpacked::Calc(ref c) => c.resolve(basis), + } + } + + /// Resolves the percentage. Just an alias of resolve(). + #[inline] + pub fn percentage_relative_to(&self, basis: Length) -> Length { + self.resolve(basis) + } + + /// Return whether there's any percentage in this value. + #[inline] + pub fn has_percentage(&self) -> bool { + match self.unpack() { + Unpacked::Length(..) => false, + Unpacked::Percentage(..) | Unpacked::Calc(..) => true, + } + } + + /// Converts to a `<length>` if possible. + pub fn to_length(&self) -> Option<Length> { + match self.unpack() { + Unpacked::Length(l) => Some(l), + Unpacked::Percentage(..) | Unpacked::Calc(..) => { + debug_assert!(self.has_percentage()); + return None; + }, + } + } + + /// Converts to a `<percentage>` if possible. + #[inline] + pub fn to_percentage(&self) -> Option<Percentage> { + match self.unpack() { + Unpacked::Length(..) => None, + Unpacked::Percentage(p) => Some(p), + Unpacked::Calc(ref c) => { + debug_assert!(!c.length.is_zero()); + None + }, + } + } + + /// Return the specified percentage if any. + #[inline] + pub fn specified_percentage(&self) -> Option<Percentage> { + match self.unpack() { + Unpacked::Length(..) => None, + Unpacked::Percentage(p) => Some(p), + Unpacked::Calc(ref c) => { + debug_assert!(self.has_percentage()); + Some(c.percentage) + }, + } + } + + /// Returns the used value. + #[inline] + pub fn to_used_value(&self, containing_length: Au) -> Au { + Au::from(self.to_pixel_length(containing_length)) + } + + /// Returns the used value as CSSPixelLength. + #[inline] + pub fn to_pixel_length(&self, containing_length: Au) -> Length { + self.resolve(containing_length.into()) + } + + /// Convert the computed value into used value. + #[inline] + pub fn maybe_to_used_value(&self, container_len: Option<Length>) -> Option<Au> { + self.maybe_percentage_relative_to(container_len) + .map(Au::from) + } + + /// If there are special rules for computing percentages in a value (e.g. + /// the height property), they apply whenever a calc() expression contains + /// percentages. + pub fn maybe_percentage_relative_to(&self, container_len: Option<Length>) -> Option<Length> { + if self.has_percentage() { + return Some(self.resolve(container_len?)); + } + Some(self.length()) + } + + /// Returns the clamped non-negative values. + #[inline] + pub fn clamp_to_non_negative(&self) -> Self { + match self.unpack() { + Unpacked::Length(l) => Self::new_length(l.clamp_to_non_negative()), + Unpacked::Percentage(p) => Self::new_percent(p.clamp_to_non_negative()), + Unpacked::Calc(c) => c.clamp_to_non_negative(), + } + } +} + +impl PartialEq for LengthPercentage { + fn eq(&self, other: &Self) -> bool { + self.unpack() == other.unpack() + } +} + +impl fmt::Debug for LengthPercentage { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + self.unpack().fmt(formatter) + } +} + +impl ToAnimatedZero for LengthPercentage { + fn to_animated_zero(&self) -> Result<Self, ()> { + Ok(match self.unpack() { + Unpacked::Length(l) => Self::new_length(l.to_animated_zero()?), + Unpacked::Percentage(p) => Self::new_percent(p.to_animated_zero()?), + Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.to_animated_zero()?)), + }) + } +} + +impl Clone for LengthPercentage { + fn clone(&self) -> Self { + match self.unpack() { + Unpacked::Length(l) => Self::new_length(l), + Unpacked::Percentage(p) => Self::new_percent(p), + Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.clone())), + } + } +} + +impl ToComputedValue for specified::LengthPercentage { + type ComputedValue = LengthPercentage; + + fn to_computed_value(&self, context: &Context) -> LengthPercentage { + match *self { + specified::LengthPercentage::Length(ref value) => { + LengthPercentage::new_length(value.to_computed_value(context)) + }, + specified::LengthPercentage::Percentage(value) => LengthPercentage::new_percent(value), + specified::LengthPercentage::Calc(ref calc) => (**calc).to_computed_value(context), + } + } + + fn from_computed_value(computed: &LengthPercentage) -> Self { + match computed.unpack() { + Unpacked::Length(ref l) => { + specified::LengthPercentage::Length(ToComputedValue::from_computed_value(l)) + }, + Unpacked::Percentage(p) => specified::LengthPercentage::Percentage(p), + Unpacked::Calc(c) => { + // We simplify before constructing the LengthPercentage if + // needed, so this is always fine. + specified::LengthPercentage::Calc(Box::new( + specified::CalcLengthPercentage::from_computed_value(c), + )) + }, + } + } +} + +impl ComputeSquaredDistance for LengthPercentage { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + // A somewhat arbitrary base, it doesn't really make sense to mix + // lengths with percentages, but we can't do much better here, and this + // ensures that the distance between length-only and percentage-only + // lengths makes sense. + let basis = Length::new(100.); + self.resolve(basis) + .compute_squared_distance(&other.resolve(basis)) + } +} + +impl ToCss for LengthPercentage { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + specified::LengthPercentage::from_computed_value(self).to_css(dest) + } +} + +impl Zero for LengthPercentage { + fn zero() -> Self { + LengthPercentage::new_length(Length::zero()) + } + + #[inline] + fn is_zero(&self) -> bool { + self.is_definitely_zero() + } +} + +impl Serialize for LengthPercentage { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + self.to_serializable().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for LengthPercentage { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + Ok(Self::from_serializable(Serializable::deserialize( + deserializer, + )?)) + } +} + +/// The representation of a calc() function with mixed lengths and percentages. +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize, ToAnimatedZero, ToResolvedValue)] +#[repr(C)] +pub struct CalcLengthPercentage { + length: Length, + + percentage: Percentage, + + #[animation(constant)] + clamping_mode: AllowedNumericType, +} + +impl CalcLengthPercentage { + /// Returns the length component of this `calc()`, clamped. + #[inline] + fn length_component(&self) -> Length { + Length::new(self.clamping_mode.clamp(self.length.px())) + } + + /// Resolves the percentage. + #[inline] + pub fn resolve(&self, basis: Length) -> Length { + let length = self.length.px() + basis.px() * self.percentage.0; + Length::new(self.clamping_mode.clamp(length)) + } + + /// Returns the length, without clamping. + #[inline] + fn unclamped_length(&self) -> Length { + self.length + } + + /// Returns the clamped non-negative values. + #[inline] + fn clamp_to_non_negative(&self) -> LengthPercentage { + LengthPercentage::new_calc( + self.length, + Some(self.percentage), + AllowedNumericType::NonNegative, + ) + } +} + +// NOTE(emilio): We don't compare `clamping_mode` since we want to preserve the +// invariant that `from_computed_value(length).to_computed_value(..) == length`. +// +// Right now for e.g. a non-negative length, we set clamping_mode to `All` +// unconditionally for non-calc values, and to `NonNegative` for calc. +// +// If we determine that it's sound, from_computed_value() can generate an +// absolute length, which then would get `All` as the clamping mode. +// +// We may want to just eagerly-detect whether we can clamp in +// `LengthPercentage::new` and switch to `AllowedNumericType::NonNegative` then, +// maybe. +impl PartialEq for CalcLengthPercentage { + fn eq(&self, other: &Self) -> bool { + self.length == other.length && self.percentage == other.percentage + } +} + +impl specified::CalcLengthPercentage { + /// Compute the value, zooming any absolute units by the zoom function. + fn to_computed_value_with_zoom<F>( + &self, + context: &Context, + zoom_fn: F, + base_size: FontBaseSize, + ) -> LengthPercentage + where + F: Fn(Length) -> Length, + { + use crate::values::specified::length::{FontRelativeLength, ViewportPercentageLength}; + use std::f32; + + let mut length = 0.; + + if let Some(absolute) = self.absolute { + length += zoom_fn(absolute.to_computed_value(context)).px(); + } + + for val in &[ + self.vw.map(ViewportPercentageLength::Vw), + self.vh.map(ViewportPercentageLength::Vh), + self.vmin.map(ViewportPercentageLength::Vmin), + self.vmax.map(ViewportPercentageLength::Vmax), + ] { + if let Some(val) = *val { + let viewport_size = context.viewport_size_for_viewport_unit_resolution(); + length += val.to_computed_value(viewport_size).px(); + } + } + + for val in &[ + self.ch.map(FontRelativeLength::Ch), + self.em.map(FontRelativeLength::Em), + self.ex.map(FontRelativeLength::Ex), + self.rem.map(FontRelativeLength::Rem), + ] { + if let Some(val) = *val { + length += val.to_computed_value(context, base_size).px(); + } + } + + LengthPercentage::new_calc( + Length::new(length.min(f32::MAX).max(f32::MIN)), + self.percentage, + self.clamping_mode, + ) + } + + /// Compute font-size or line-height taking into account text-zoom if necessary. + pub fn to_computed_value_zoomed( + &self, + context: &Context, + base_size: FontBaseSize, + ) -> LengthPercentage { + self.to_computed_value_with_zoom( + context, + |abs| context.maybe_zoom_text(abs.into()), + base_size, + ) + } + + /// Compute the value into pixel length as CSSFloat without context, + /// so it returns Err(()) if there is any non-absolute unit. + pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> { + if self.vw.is_some() || + self.vh.is_some() || + self.vmin.is_some() || + self.vmax.is_some() || + self.em.is_some() || + self.ex.is_some() || + self.ch.is_some() || + self.rem.is_some() || + self.percentage.is_some() + { + return Err(()); + } + + match self.absolute { + Some(abs) => Ok(abs.to_px()), + None => { + debug_assert!(false, "Someone forgot to handle an unit here: {:?}", self); + Err(()) + }, + } + } + + /// Compute the calc using the current font-size (and without text-zoom). + pub fn to_computed_value(&self, context: &Context) -> LengthPercentage { + self.to_computed_value_with_zoom(context, |abs| abs, FontBaseSize::CurrentStyle) + } + + #[inline] + fn from_computed_value(computed: &CalcLengthPercentage) -> Self { + use crate::values::specified::length::AbsoluteLength; + + specified::CalcLengthPercentage { + clamping_mode: computed.clamping_mode, + absolute: Some(AbsoluteLength::from_computed_value(&computed.length)), + percentage: Some(computed.percentage), + ..Default::default() + } + } +} + +/// A wrapper of LengthPercentage, whose value must be >= 0. +pub type NonNegativeLengthPercentage = NonNegative<LengthPercentage>; + +impl ToAnimatedValue for NonNegativeLengthPercentage { + type AnimatedValue = LengthPercentage; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + self.0 + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + NonNegative(animated.clamp_to_non_negative()) + } +} + +impl NonNegativeLengthPercentage { + /// Returns true if the computed value is absolute 0 or 0%. + #[inline] + pub fn is_definitely_zero(&self) -> bool { + self.0.is_definitely_zero() + } + + /// Returns the used value. + #[inline] + pub fn to_used_value(&self, containing_length: Au) -> Au { + let resolved = self.0.to_used_value(containing_length); + std::cmp::max(resolved, Au(0)) + } + + /// Convert the computed value into used value. + #[inline] + pub fn maybe_to_used_value(&self, containing_length: Option<Au>) -> Option<Au> { + let resolved = self + .0 + .maybe_to_used_value(containing_length.map(|v| v.into()))?; + Some(std::cmp::max(resolved, Au(0))) + } +} diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index f5b3440f426..7cfd1e20abe 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -108,6 +108,7 @@ pub mod flex; pub mod font; pub mod image; pub mod length; +pub mod length_percentage; pub mod list; pub mod motion; pub mod outline; @@ -125,9 +126,6 @@ pub mod url; /// A `Context` is all the data a specified value could ever need to compute /// itself and be transformed to a computed value. pub struct Context<'a> { - /// Whether the current element is the root element. - pub is_root_element: bool, - /// Values accessed through this need to be in the properties "computed /// early": color, text-decoration, font-size, display, position, float, /// border-*-style, outline-style, font-family, writing-mode... @@ -186,7 +184,6 @@ impl<'a> Context<'a> { let provider = get_metrics_provider_for_product(); let context = Context { - is_root_element: false, builder: StyleBuilder::for_inheritance(device, None, None), font_metrics_provider: &provider, cached_system_font: None, @@ -200,11 +197,6 @@ impl<'a> Context<'a> { f(&context) } - /// Whether the current element is the root element. - pub fn is_root_element(&self) -> bool { - self.is_root_element - } - /// The current device. pub fn device(&self) -> &Device { self.builder.device @@ -471,6 +463,53 @@ trivial_to_computed_value!(Atom); trivial_to_computed_value!(Prefix); trivial_to_computed_value!(String); trivial_to_computed_value!(Box<str>); +trivial_to_computed_value!(crate::OwnedStr); + +#[allow(missing_docs)] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + ToAnimatedZero, + ToCss, + ToResolvedValue, +)] +#[repr(C, u8)] +pub enum AngleOrPercentage { + Percentage(Percentage), + Angle(Angle), +} + +impl ToComputedValue for specified::AngleOrPercentage { + type ComputedValue = AngleOrPercentage; + + #[inline] + fn to_computed_value(&self, context: &Context) -> AngleOrPercentage { + match *self { + specified::AngleOrPercentage::Percentage(percentage) => { + AngleOrPercentage::Percentage(percentage.to_computed_value(context)) + }, + specified::AngleOrPercentage::Angle(angle) => { + AngleOrPercentage::Angle(angle.to_computed_value(context)) + }, + } + } + #[inline] + fn from_computed_value(computed: &AngleOrPercentage) -> Self { + match *computed { + AngleOrPercentage::Percentage(percentage) => specified::AngleOrPercentage::Percentage( + ToComputedValue::from_computed_value(&percentage), + ), + AngleOrPercentage::Angle(angle) => { + specified::AngleOrPercentage::Angle(ToComputedValue::from_computed_value(&angle)) + }, + } + } +} /// A `<number>` value. pub type Number = CSSFloat; diff --git a/components/style/values/computed/motion.rs b/components/style/values/computed/motion.rs index 88a5b7c7280..e2565ff3468 100644 --- a/components/style/values/computed/motion.rs +++ b/components/style/values/computed/motion.rs @@ -23,8 +23,10 @@ fn is_auto_zero_angle(auto: &bool, angle: &Angle) -> bool { ComputeSquaredDistance, Copy, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, ToAnimatedZero, ToCss, ToResolvedValue, diff --git a/components/style/values/computed/text.rs b/components/style/values/computed/text.rs index 3f9ffb2ad0f..0ca2e6044ed 100644 --- a/components/style/values/computed/text.rs +++ b/components/style/values/computed/text.rs @@ -27,8 +27,8 @@ pub use crate::values::specified::{TextDecorationSkipInk, TextTransform}; /// A computed value for the `initial-letter` property. pub type InitialLetter = GenericInitialLetter<CSSFloat, CSSInteger>; -/// Implements type for `text-underline-offset` and `text-decoration-thickness` properties -pub type TextDecorationLength = GenericTextDecorationLength<Length>; +/// Implements type for `text-decoration-thickness` property. +pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>; /// A computed value for the `letter-spacing` property. #[repr(transparent)] diff --git a/components/style/values/distance.rs b/components/style/values/distance.rs index a1872366c2a..67c735676b5 100644 --- a/components/style/values/distance.rs +++ b/components/style/values/distance.rs @@ -17,7 +17,7 @@ use std::ops::Add; /// on each fields of the values. /// /// If a variant is annotated with `#[animation(error)]`, the corresponding -/// `match` arm is not generated. +/// `match` arm returns an error. /// /// If the two values are not similar, an error is returned unless a fallback /// function has been specified through `#[distance(fallback)]`. diff --git a/components/style/values/generics/counters.rs b/components/style/values/generics/counters.rs index 69893c75561..05e34703911 100644 --- a/components/style/values/generics/counters.rs +++ b/components/style/values/generics/counters.rs @@ -145,30 +145,27 @@ fn is_decimal(counter_type: &CounterStyleType) -> bool { /// /// https://drafts.csswg.org/css-content/#propdef-content #[derive( - Clone, - Debug, - Eq, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, + Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToShmem, )] -pub enum Content<ImageUrl> { +#[repr(u8)] +pub enum GenericContent<ImageUrl> { /// `normal` reserved keyword. Normal, /// `none` reserved keyword. None, - /// `-moz-alt-content`. - #[cfg(feature = "gecko")] - MozAltContent, /// Content items. - Items(#[css(iterable)] Box<[ContentItem<ImageUrl>]>), + Items(#[css(iterable)] crate::OwnedSlice<GenericContentItem<ImageUrl>>), } +pub use self::GenericContent as Content; + impl<ImageUrl> Content<ImageUrl> { + /// Whether `self` represents list of items. + #[inline] + pub fn is_items(&self) -> bool { + matches!(*self, Self::Items(..)) + } + /// Set `content` property to `normal`. #[inline] pub fn normal() -> Self { @@ -189,9 +186,10 @@ impl<ImageUrl> Content<ImageUrl> { ToResolvedValue, ToShmem, )] -pub enum ContentItem<ImageUrl> { +#[repr(u8)] +pub enum GenericContentItem<ImageUrl> { /// Literal string content. - String(Box<str>), + String(crate::OwnedStr), /// `counter(name, style)`. #[css(comma, function)] Counter(CustomIdent, #[css(skip_if = "is_decimal")] CounterStyleType), @@ -199,7 +197,7 @@ pub enum ContentItem<ImageUrl> { #[css(comma, function)] Counters( CustomIdent, - Box<str>, + crate::OwnedStr, #[css(skip_if = "is_decimal")] CounterStyleType, ), /// `open-quote`. @@ -210,9 +208,14 @@ pub enum ContentItem<ImageUrl> { NoOpenQuote, /// `no-close-quote`. NoCloseQuote, + /// `-moz-alt-content`. + #[cfg(feature = "gecko")] + MozAltContent, /// `attr([namespace? `|`]? ident)` #[cfg(feature = "gecko")] Attr(Attr), /// `url(url)` Url(ImageUrl), } + +pub use self::GenericContentItem as ContentItem; diff --git a/components/style/values/generics/mod.rs b/components/style/values/generics/mod.rs index 4db80abc8a8..a97d9e1018a 100644 --- a/components/style/values/generics/mod.rs +++ b/components/style/values/generics/mod.rs @@ -39,7 +39,7 @@ pub mod transform; pub mod ui; pub mod url; -// https://drafts.csswg.org/css-counter-styles/#typedef-symbols-type +/// https://drafts.csswg.org/css-counter-styles/#typedef-symbols-type #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[derive( @@ -55,6 +55,7 @@ pub mod url; ToResolvedValue, ToShmem, )] +#[repr(u8)] pub enum SymbolsType { Cyclic, Numeric, @@ -63,39 +64,12 @@ pub enum SymbolsType { Fixed, } -#[cfg(feature = "gecko")] -impl SymbolsType { - /// Convert symbols type to their corresponding Gecko values. - pub fn to_gecko_keyword(self) -> u8 { - use crate::gecko_bindings::structs; - match self { - SymbolsType::Cyclic => structs::NS_STYLE_COUNTER_SYSTEM_CYCLIC as u8, - SymbolsType::Numeric => structs::NS_STYLE_COUNTER_SYSTEM_NUMERIC as u8, - SymbolsType::Alphabetic => structs::NS_STYLE_COUNTER_SYSTEM_ALPHABETIC as u8, - SymbolsType::Symbolic => structs::NS_STYLE_COUNTER_SYSTEM_SYMBOLIC as u8, - SymbolsType::Fixed => structs::NS_STYLE_COUNTER_SYSTEM_FIXED as u8, - } - } - - /// Convert Gecko value to symbol type. - pub fn from_gecko_keyword(gecko_value: u32) -> SymbolsType { - use crate::gecko_bindings::structs; - match gecko_value { - structs::NS_STYLE_COUNTER_SYSTEM_CYCLIC => SymbolsType::Cyclic, - structs::NS_STYLE_COUNTER_SYSTEM_NUMERIC => SymbolsType::Numeric, - structs::NS_STYLE_COUNTER_SYSTEM_ALPHABETIC => SymbolsType::Alphabetic, - structs::NS_STYLE_COUNTER_SYSTEM_SYMBOLIC => SymbolsType::Symbolic, - structs::NS_STYLE_COUNTER_SYSTEM_FIXED => SymbolsType::Fixed, - x => panic!("Unexpected value for symbol type {}", x), - } - } -} - /// <https://drafts.csswg.org/css-counter-styles/#typedef-counter-style> /// /// Note that 'none' is not a valid name. #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] #[derive(Clone, Debug, Eq, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] +#[repr(u8)] pub enum CounterStyle { /// `<counter-style-name>` Name(CustomIdent), diff --git a/components/style/values/generics/motion.rs b/components/style/values/generics/motion.rs index 685eaad43e2..25ba5873adc 100644 --- a/components/style/values/generics/motion.rs +++ b/components/style/values/generics/motion.rs @@ -73,13 +73,16 @@ pub struct RayFunction<Angle> { /// The offset-path value. /// /// https://drafts.fxtf.org/motion-1/#offset-path-property +/// cbindgen:private-default-tagged-enum-constructor=false #[derive( Animate, Clone, ComputeSquaredDistance, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToAnimatedZero, ToComputedValue, diff --git a/components/style/values/generics/position.rs b/components/style/values/generics/position.rs index 2742dd87913..a3552ea3eab 100644 --- a/components/style/values/generics/position.rs +++ b/components/style/values/generics/position.rs @@ -12,8 +12,10 @@ ComputeSquaredDistance, Copy, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToAnimatedValue, ToAnimatedZero, @@ -44,15 +46,18 @@ impl<H, V> Position<H, V> { /// A generic type for representing an `Auto | <position>`. /// This is used by <offset-anchor> for now. /// https://drafts.fxtf.org/motion-1/#offset-anchor-property +/// cbindgen:private-default-tagged-enum-constructor=false #[derive( Animate, Clone, ComputeSquaredDistance, Copy, Debug, + Deserialize, MallocSizeOf, Parse, PartialEq, + Serialize, SpecifiedValueInfo, ToAnimatedZero, ToComputedValue, diff --git a/components/style/values/generics/text.rs b/components/style/values/generics/text.rs index fd434cc1273..ceeb59a3d0c 100644 --- a/components/style/values/generics/text.rs +++ b/components/style/values/generics/text.rs @@ -123,8 +123,8 @@ impl<N, L> LineHeight<N, L> { } } -/// Implements type for text-underline-offset and text-decoration-thickness -/// which take the grammar of auto | from-font | <length> +/// Implements type for text-decoration-thickness +/// which takes the grammar of auto | from-font | <length> | <percentage> /// /// https://drafts.csswg.org/css-text-decor-4/ #[repr(C, u8)] @@ -148,7 +148,7 @@ impl<N, L> LineHeight<N, L> { )] #[allow(missing_docs)] pub enum GenericTextDecorationLength<L> { - Length(L), + LengthPercentage(L), Auto, FromFont, } diff --git a/components/style/values/generics/transform.rs b/components/style/values/generics/transform.rs index 3322323e76e..26e54ab8df4 100644 --- a/components/style/values/generics/transform.rs +++ b/components/style/values/generics/transform.rs @@ -23,8 +23,10 @@ use style_traits::{CssWriter, ToCss}; Clone, Copy, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToComputedValue, ToCss, @@ -51,8 +53,10 @@ pub use self::GenericMatrix as Matrix; Clone, Copy, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToComputedValue, ToCss, @@ -141,8 +145,10 @@ fn is_same<N: PartialEq>(x: &N, y: &N) -> bool { #[derive( Clone, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToComputedValue, ToCss, @@ -267,8 +273,10 @@ pub use self::GenericTransformOperation as TransformOperation; #[derive( Clone, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToComputedValue, ToCss, @@ -369,7 +377,7 @@ impl ToAbsoluteLength for ComputedLengthPercentage { // distance without any layout info. // // FIXME(emilio): This looks wrong. - None => Ok(self.length_component().px()), + None => Ok(self.resolve(Zero::zero()).px()), } } } @@ -611,8 +619,10 @@ pub fn get_normalized_vector_and_angle<T: Zero>( Clone, Copy, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToAnimatedZero, ToComputedValue, @@ -623,6 +633,7 @@ pub fn get_normalized_vector_and_angle<T: Zero>( /// A value of the `Rotate` property /// /// <https://drafts.csswg.org/css-transforms-2/#individual-transforms> +/// cbindgen:private-default-tagged-enum-constructor=false pub enum GenericRotate<Number, Angle> { /// 'none' None, @@ -685,8 +696,10 @@ where Clone, Copy, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToAnimatedZero, ToComputedValue, @@ -697,6 +710,7 @@ where /// A value of the `Scale` property /// /// <https://drafts.csswg.org/css-transforms-2/#individual-transforms> +/// cbindgen:private-default-tagged-enum-constructor=false pub enum GenericScale<Number> { /// 'none' None, @@ -749,8 +763,10 @@ fn y_axis_and_z_axis_are_zero<LengthPercentage: Zero, Length: Zero>( #[derive( Clone, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToAnimatedZero, ToComputedValue, @@ -772,6 +788,7 @@ fn y_axis_and_z_axis_are_zero<LengthPercentage: Zero, Length: Zero>( /// https://github.com/w3c/csswg-drafts/issues/3305 /// /// <https://drafts.csswg.org/css-transforms-2/#individual-transforms> +/// cbindgen:private-default-tagged-enum-constructor=false pub enum GenericTranslate<LengthPercentage, Length> where LengthPercentage: Zero, @@ -803,9 +820,8 @@ pub use self::GenericTranslate as Translate; ToResolvedValue, ToShmem, )] +#[repr(u8)] pub enum TransformStyle { - #[cfg(feature = "servo")] - Auto, Flat, #[css(keyword = "preserve-3d")] Preserve3d, diff --git a/components/style/values/resolved/counters.rs b/components/style/values/resolved/counters.rs new file mode 100644 index 00000000000..bf6490f7401 --- /dev/null +++ b/components/style/values/resolved/counters.rs @@ -0,0 +1,47 @@ +/* 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/. */ + +//! Resolved values for counter properties + +use super::{Context, ToResolvedValue}; +use crate::values::computed; + +/// https://drafts.csswg.org/css-content/#content-property +/// +/// We implement this at resolved value time because otherwise it causes us to +/// allocate a bunch of useless initial structs for ::before / ::after, which is +/// a bit unfortunate. +/// +/// Though these should be temporary, mostly, so if this causes complexity in +/// other places, it should be fine to move to `StyleAdjuster`. +/// +/// See https://github.com/w3c/csswg-drafts/issues/4632 for where some related +/// issues are being discussed. +impl ToResolvedValue for computed::Content { + type ResolvedValue = Self; + + #[inline] + fn to_resolved_value(self, context: &Context) -> Self { + let is_before_or_after = context + .style + .pseudo() + .map_or(false, |p| p.is_before_or_after()); + + match self { + Self::Normal if is_before_or_after => Self::None, + // For now, make `content: none` compute to `normal` on other + // elements, as we don't respect it. + // + // FIXME(emilio, bug 1605473): for marker this should be preserved + // and respected, probably. + Self::None if !is_before_or_after => Self::Normal, + other => other, + } + } + + #[inline] + fn from_resolved_value(resolved: Self) -> Self { + resolved + } +} diff --git a/components/style/values/resolved/mod.rs b/components/style/values/resolved/mod.rs index e64178a1c95..022cb7893c6 100644 --- a/components/style/values/resolved/mod.rs +++ b/components/style/values/resolved/mod.rs @@ -10,6 +10,7 @@ use cssparser; use smallvec::SmallVec; mod color; +mod counters; use crate::values::computed; @@ -68,6 +69,7 @@ trivial_to_resolved_value!(u32); trivial_to_resolved_value!(usize); trivial_to_resolved_value!(String); trivial_to_resolved_value!(Box<str>); +trivial_to_resolved_value!(crate::OwnedStr); trivial_to_resolved_value!(cssparser::RGBA); trivial_to_resolved_value!(crate::Atom); trivial_to_resolved_value!(app_units::Au); @@ -76,6 +78,7 @@ trivial_to_resolved_value!(computed::url::ComputedUrl); trivial_to_resolved_value!(computed::url::ComputedImageUrl); #[cfg(feature = "servo")] trivial_to_resolved_value!(html5ever::Prefix); +trivial_to_resolved_value!(computed::LengthPercentage); impl<A, B> ToResolvedValue for (A, B) where diff --git a/components/style/values/specified/align.rs b/components/style/values/specified/align.rs index 0dc1e422515..d0160a32ae6 100644 --- a/components/style/values/specified/align.rs +++ b/components/style/values/specified/align.rs @@ -6,7 +6,6 @@ //! //! https://drafts.csswg.org/css-align/ -use crate::gecko_bindings::structs; use crate::parser::{Parse, ParserContext}; use cssparser::Parser; use std::fmt::{self, Write}; @@ -14,56 +13,55 @@ use style_traits::{CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, bitflags! { /// Constants shared by multiple CSS Box Alignment properties - /// - /// These constants match Gecko's `NS_STYLE_ALIGN_*` constants. #[derive(MallocSizeOf, ToComputedValue, ToResolvedValue, ToShmem)] + #[repr(C)] pub struct AlignFlags: u8 { // Enumeration stored in the lower 5 bits: - /// 'auto' - const AUTO = structs::NS_STYLE_ALIGN_AUTO as u8; + /// {align,justify}-{content,items,self}: 'auto' + const AUTO = 0; /// 'normal' - const NORMAL = structs::NS_STYLE_ALIGN_NORMAL as u8; + const NORMAL = 1; /// 'start' - const START = structs::NS_STYLE_ALIGN_START as u8; + const START = 2; /// 'end' - const END = structs::NS_STYLE_ALIGN_END as u8; + const END = 3; /// 'flex-start' - const FLEX_START = structs::NS_STYLE_ALIGN_FLEX_START as u8; + const FLEX_START = 4; /// 'flex-end' - const FLEX_END = structs::NS_STYLE_ALIGN_FLEX_END as u8; + const FLEX_END = 5; /// 'center' - const CENTER = structs::NS_STYLE_ALIGN_CENTER as u8; + const CENTER = 6; /// 'left' - const LEFT = structs::NS_STYLE_ALIGN_LEFT as u8; + const LEFT = 7; /// 'right' - const RIGHT = structs::NS_STYLE_ALIGN_RIGHT as u8; + const RIGHT = 8; /// 'baseline' - const BASELINE = structs::NS_STYLE_ALIGN_BASELINE as u8; + const BASELINE = 9; /// 'last-baseline' - const LAST_BASELINE = structs::NS_STYLE_ALIGN_LAST_BASELINE as u8; + const LAST_BASELINE = 10; /// 'stretch' - const STRETCH = structs::NS_STYLE_ALIGN_STRETCH as u8; + const STRETCH = 11; /// 'self-start' - const SELF_START = structs::NS_STYLE_ALIGN_SELF_START as u8; + const SELF_START = 12; /// 'self-end' - const SELF_END = structs::NS_STYLE_ALIGN_SELF_END as u8; + const SELF_END = 13; /// 'space-between' - const SPACE_BETWEEN = structs::NS_STYLE_ALIGN_SPACE_BETWEEN as u8; + const SPACE_BETWEEN = 14; /// 'space-around' - const SPACE_AROUND = structs::NS_STYLE_ALIGN_SPACE_AROUND as u8; + const SPACE_AROUND = 15; /// 'space-evenly' - const SPACE_EVENLY = structs::NS_STYLE_ALIGN_SPACE_EVENLY as u8; + const SPACE_EVENLY = 16; // Additional flags stored in the upper bits: /// 'legacy' (mutually exclusive w. SAFE & UNSAFE) - const LEGACY = structs::NS_STYLE_ALIGN_LEGACY as u8; + const LEGACY = 1 << 5; /// 'safe' - const SAFE = structs::NS_STYLE_ALIGN_SAFE as u8; + const SAFE = 1 << 6; /// 'unsafe' (mutually exclusive w. SAFE) - const UNSAFE = structs::NS_STYLE_ALIGN_UNSAFE as u8; + const UNSAFE = 1 << 7; /// Mask for the additional flags above. - const FLAG_BITS = structs::NS_STYLE_ALIGN_FLAG_BITS as u8; + const FLAG_BITS = 0b11100000; } } @@ -146,6 +144,7 @@ pub enum AxisDirection { ToResolvedValue, ToShmem, )] +#[repr(C)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] pub struct ContentDistribution { primary: AlignFlags, @@ -270,6 +269,7 @@ impl ContentDistribution { ToResolvedValue, ToShmem, )] +#[repr(transparent)] pub struct AlignContent(pub ContentDistribution); impl Parse for AlignContent { @@ -292,20 +292,6 @@ impl SpecifiedValueInfo for AlignContent { } } -#[cfg(feature = "gecko")] -impl From<u16> for AlignContent { - fn from(bits: u16) -> Self { - AlignContent(ContentDistribution::from_bits(bits)) - } -} - -#[cfg(feature = "gecko")] -impl From<AlignContent> for u16 { - fn from(v: AlignContent) -> u16 { - v.0.as_bits() - } -} - /// Value for the `justify-content` property. /// /// <https://drafts.csswg.org/css-align/#propdef-justify-content> @@ -321,6 +307,7 @@ impl From<AlignContent> for u16 { ToResolvedValue, ToShmem, )] +#[repr(transparent)] pub struct JustifyContent(pub ContentDistribution); impl Parse for JustifyContent { @@ -370,6 +357,7 @@ impl From<JustifyContent> for u16 { ToResolvedValue, ToShmem, )] +#[repr(transparent)] pub struct SelfAlignment(pub AlignFlags); impl SelfAlignment { @@ -441,6 +429,7 @@ impl SelfAlignment { ToResolvedValue, ToShmem, )] +#[repr(C)] pub struct AlignSelf(pub SelfAlignment); impl Parse for AlignSelf { @@ -463,18 +452,6 @@ impl SpecifiedValueInfo for AlignSelf { } } -impl From<u8> for AlignSelf { - fn from(bits: u8) -> Self { - AlignSelf(SelfAlignment(AlignFlags::from_bits_truncate(bits))) - } -} - -impl From<AlignSelf> for u8 { - fn from(align: AlignSelf) -> u8 { - (align.0).0.bits() - } -} - /// The specified value of the justify-self property. /// /// <https://drafts.csswg.org/css-align/#propdef-justify-self> @@ -490,6 +467,7 @@ impl From<AlignSelf> for u8 { ToResolvedValue, ToShmem, )] +#[repr(C)] pub struct JustifySelf(pub SelfAlignment); impl Parse for JustifySelf { @@ -512,18 +490,6 @@ impl SpecifiedValueInfo for JustifySelf { } } -impl From<u8> for JustifySelf { - fn from(bits: u8) -> Self { - JustifySelf(SelfAlignment(AlignFlags::from_bits_truncate(bits))) - } -} - -impl From<JustifySelf> for u8 { - fn from(justify: JustifySelf) -> u8 { - (justify.0).0.bits() - } -} - /// Value of the `align-items` property /// /// <https://drafts.csswg.org/css-align/#propdef-align-items> @@ -539,6 +505,7 @@ impl From<JustifySelf> for u8 { ToResolvedValue, ToShmem, )] +#[repr(C)] pub struct AlignItems(pub AlignFlags); impl AlignItems { @@ -590,6 +557,7 @@ impl SpecifiedValueInfo for AlignItems { /// /// <https://drafts.csswg.org/css-align/#justify-items-property> #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)] +#[repr(C)] pub struct JustifyItems(pub AlignFlags); impl JustifyItems { diff --git a/components/style/values/specified/angle.rs b/components/style/values/specified/angle.rs index 458f4178e59..fa60f66507d 100644 --- a/components/style/values/specified/angle.rs +++ b/components/style/values/specified/angle.rs @@ -163,7 +163,8 @@ impl Angle { /// https://github.com/w3c/fxtf-drafts/issues/228 /// /// See also: https://github.com/w3c/csswg-drafts/issues/1162. -enum AllowUnitlessZeroAngle { +#[allow(missing_docs)] +pub enum AllowUnitlessZeroAngle { Yes, No, } @@ -203,12 +204,14 @@ impl Angle { Self::parse_internal(context, input, AllowUnitlessZeroAngle::Yes) } - fn parse_internal<'i, 't>( + pub(super) fn parse_internal<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, allow_unitless_zero: AllowUnitlessZeroAngle, ) -> Result<Self, ParseError<'i>> { + let location = input.current_source_location(); let t = input.next()?; + let allow_unitless_zero = matches!(allow_unitless_zero, AllowUnitlessZeroAngle::Yes); match *t { Token::Dimension { value, ref unit, .. @@ -221,16 +224,11 @@ impl Angle { }, } }, - Token::Number { value, .. } if value == 0. => match allow_unitless_zero { - AllowUnitlessZeroAngle::Yes => Ok(Angle::zero()), - AllowUnitlessZeroAngle::No => { - let t = t.clone(); - Err(input.new_unexpected_token_error(t)) - }, - }, - Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { - return input.parse_nested_block(|i| CalcNode::parse_angle(context, i)); + Token::Function(ref name) => { + let function = CalcNode::math_function(name, location)?; + CalcNode::parse_angle(context, input, function) }, + Token::Number { value, .. } if value == 0. && allow_unitless_zero => Ok(Angle::zero()), ref t => { let t = t.clone(); Err(input.new_unexpected_token_error(t)) diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index ad8602ac431..fa5d5a2d43a 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -61,12 +61,9 @@ pub enum DisplayInside { None = 0, #[cfg(any(feature = "servo-layout-2020", feature = "gecko"))] Contents, - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] - Block, + Flow, FlowRoot, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] - Inline, - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] Flex, #[cfg(feature = "gecko")] Grid, @@ -101,8 +98,6 @@ pub enum DisplayInside { #[cfg(feature = "gecko")] MozBox, #[cfg(feature = "gecko")] - MozInlineBox, - #[cfg(feature = "gecko")] MozGrid, #[cfg(feature = "gecko")] MozGridGroup, @@ -111,10 +106,7 @@ pub enum DisplayInside { #[cfg(feature = "gecko")] MozDeck, #[cfg(feature = "gecko")] - MozGroupbox, - #[cfg(feature = "gecko")] MozPopup, - Flow, // only used for parsing, not computed value } #[allow(missing_docs)] @@ -147,14 +139,8 @@ impl Display { pub const None: Self = Self::new(DisplayOutside::None, DisplayInside::None); #[cfg(any(feature = "servo-layout-2020", feature = "gecko"))] pub const Contents: Self = Self::new(DisplayOutside::None, DisplayInside::Contents); - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] - pub const Inline: Self = Self::new(DisplayOutside::Inline, DisplayInside::Inline); - #[cfg(any(feature = "servo-layout-2020"))] pub const Inline: Self = Self::new(DisplayOutside::Inline, DisplayInside::Flow); pub const InlineBlock: Self = Self::new(DisplayOutside::Inline, DisplayInside::FlowRoot); - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] - pub const Block: Self = Self::new(DisplayOutside::Block, DisplayInside::Block); - #[cfg(any(feature = "servo-layout-2020"))] pub const Block: Self = Self::new(DisplayOutside::Block, DisplayInside::Flow); #[cfg(feature = "gecko")] pub const FlowRoot: Self = Self::new(DisplayOutside::Block, DisplayInside::FlowRoot); @@ -171,7 +157,7 @@ impl Display { #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const InlineTable: Self = Self::new(DisplayOutside::Inline, DisplayInside::Table); #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] - pub const TableCaption: Self = Self::new(DisplayOutside::TableCaption, DisplayInside::Block); + pub const TableCaption: Self = Self::new(DisplayOutside::TableCaption, DisplayInside::Flow); #[cfg(feature = "gecko")] pub const Ruby: Self = Self::new(DisplayOutside::Inline, DisplayInside::Ruby); #[cfg(feature = "gecko")] @@ -231,9 +217,9 @@ impl Display { /// XUL boxes. #[cfg(feature = "gecko")] - pub const MozBox: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozBox); + pub const MozBox: Self = Self::new(DisplayOutside::Block, DisplayInside::MozBox); #[cfg(feature = "gecko")] - pub const MozInlineBox: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozInlineBox); + pub const MozInlineBox: Self = Self::new(DisplayOutside::Inline, DisplayInside::MozBox); #[cfg(feature = "gecko")] pub const MozGrid: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozGrid); #[cfg(feature = "gecko")] @@ -243,8 +229,6 @@ impl Display { #[cfg(feature = "gecko")] pub const MozDeck: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozDeck); #[cfg(feature = "gecko")] - pub const MozGroupbox: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozGroupbox); - #[cfg(feature = "gecko")] pub const MozPopup: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozPopup); /// Make a raw display value from <display-outside> and <display-inside> values. @@ -256,18 +240,8 @@ impl Display { } /// Make a display enum value from <display-outside> and <display-inside> values. - /// We store `flow` as a synthetic `block` or `inline` inside-value to simplify - /// our layout code. #[inline] fn from3(outside: DisplayOutside, inside: DisplayInside, list_item: bool) -> Self { - let inside = match inside { - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] - DisplayInside::Flow => match outside { - DisplayOutside::Inline => DisplayInside::Inline, - _ => DisplayInside::Block, - }, - _ => inside, - }; let v = Self::new(outside, inside); if !list_item { return v; @@ -290,6 +264,12 @@ impl Display { .unwrap() } + /// Whether this is `display: inline` (or `inline list-item`). + #[inline] + pub fn is_inline_flow(&self) -> bool { + self.outside() == DisplayOutside::Inline && self.inside() == DisplayInside::Flow + } + /// Returns whether this `display` value is some kind of list-item. #[inline] pub const fn is_list_item(&self) -> bool { @@ -381,19 +361,13 @@ impl Display { match self.outside() { DisplayOutside::Inline => { let inside = match self.inside() { - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] - DisplayInside::Inline | DisplayInside::FlowRoot => DisplayInside::Block, - #[cfg(feature = "servo-layout-2020")] + // `inline-block` blockifies to `block` rather than + // `flow-root`, for legacy reasons. DisplayInside::FlowRoot => DisplayInside::Flow, inside => inside, }; Display::from3(DisplayOutside::Block, inside, self.is_list_item()) }, - #[cfg(feature = "gecko")] - DisplayOutside::XUL => match self.inside() { - DisplayInside::MozInlineBox | DisplayInside::MozBox => Display::MozBox, - _ => Display::Block, - }, DisplayOutside::Block | DisplayOutside::None => *self, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] _ => Display::Block, @@ -407,16 +381,13 @@ impl Display { match self.outside() { DisplayOutside::Block => { let inside = match self.inside() { - DisplayInside::Block => DisplayInside::FlowRoot, + // `display: block` inlinifies to `display: inline-block`, + // rather than `inline`, for legacy reasons. + DisplayInside::Flow => DisplayInside::FlowRoot, inside => inside, }; Display::from3(DisplayOutside::Inline, inside, self.is_list_item()) }, - #[cfg(feature = "gecko")] - DisplayOutside::XUL => match self.inside() { - DisplayInside::MozBox => Display::MozInlineBox, - _ => *self, - }, _ => *self, } } @@ -443,18 +414,8 @@ impl ToCss for Display { where W: fmt::Write, { - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] - debug_assert_ne!( - self.inside(), - DisplayInside::Flow, - "`flow` fears in `display` computed value" - ); let outside = self.outside(); - let inside = match self.inside() { - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] - DisplayInside::Block | DisplayInside::Inline => DisplayInside::Flow, - inside => inside, - }; + let inside = self.inside(); match *self { Display::Block | Display::Inline => outside.to_css(dest), Display::InlineBlock => dest.write_str("inline-block"), @@ -657,8 +618,6 @@ impl Parse for Display { #[cfg(feature = "gecko")] "-moz-deck" if moz_display_values_enabled(context) => Display::MozDeck, #[cfg(feature = "gecko")] - "-moz-groupbox" if moz_display_values_enabled(context) => Display::MozGroupbox, - #[cfg(feature = "gecko")] "-moz-popup" if moz_display_values_enabled(context) => Display::MozPopup, }) } diff --git a/components/style/values/specified/calc.rs b/components/style/values/specified/calc.rs index f9feb616fff..9736491adce 100644 --- a/components/style/values/specified/calc.rs +++ b/components/style/values/specified/calc.rs @@ -10,15 +10,61 @@ use crate::parser::ParserContext; use crate::values::computed; use crate::values::specified::length::ViewportPercentageLength; use crate::values::specified::length::{AbsoluteLength, FontRelativeLength, NoCalcLength}; -use crate::values::specified::{Angle, Time}; +use crate::values::specified::{self, Angle, Time}; use crate::values::{CSSFloat, CSSInteger}; -use cssparser::{AngleOrNumber, NumberOrPercentage, Parser, Token}; +use cssparser::{AngleOrNumber, CowRcStr, NumberOrPercentage, Parser, Token}; +use smallvec::SmallVec; use std::fmt::{self, Write}; +use std::{cmp, mem}; use style_traits::values::specified::AllowedNumericType; use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss}; +/// The name of the mathematical function that we're parsing. +#[derive(Clone, Copy, Debug)] +pub enum MathFunction { + /// `calc()`: https://drafts.csswg.org/css-values-4/#funcdef-calc + Calc, + /// `min()`: https://drafts.csswg.org/css-values-4/#funcdef-min + Min, + /// `max()`: https://drafts.csswg.org/css-values-4/#funcdef-max + Max, + /// `clamp()`: https://drafts.csswg.org/css-values-4/#funcdef-clamp + Clamp, +} + +/// This determines the order in which we serialize members of a calc() +/// sum. +/// +/// See https://drafts.csswg.org/css-values-4/#sort-a-calculations-children +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +enum SortKey { + Number, + Percentage, + Ch, + Deg, + Em, + Ex, + Px, + Rem, + Sec, + Vh, + Vmax, + Vmin, + Vw, + Other, +} + +/// Whether we're a `min` or `max` function. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum MinMaxOp { + /// `min()` + Min, + /// `max()` + Max, +} + /// A node inside a `Calc` expression's AST. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum CalcNode { /// `<length>` Length(NoCalcLength), @@ -30,14 +76,20 @@ pub enum CalcNode { Percentage(CSSFloat), /// `<number>` Number(CSSFloat), - /// An expression of the form `x + y` - Sum(Box<CalcNode>, Box<CalcNode>), - /// An expression of the form `x - y` - Sub(Box<CalcNode>, Box<CalcNode>), - /// An expression of the form `x * y` - Mul(Box<CalcNode>, Box<CalcNode>), - /// An expression of the form `x / y` - Div(Box<CalcNode>, Box<CalcNode>), + /// An expression of the form `x + y + ...`. Subtraction is represented by + /// the negated expression of the right hand side. + Sum(Box<[CalcNode]>), + /// A `min()` / `max()` function. + MinMax(Box<[CalcNode]>, MinMaxOp), + /// A `clamp()` function. + Clamp { + /// The minimum value. + min: Box<CalcNode>, + /// The central value. + center: Box<CalcNode>, + /// The maximum value. + max: Box<CalcNode>, + }, } /// An expected unit we intend to parse within a `calc()` expression. @@ -150,7 +202,362 @@ impl ToCss for CalcLengthPercentage { impl SpecifiedValueInfo for CalcLengthPercentage {} +macro_rules! impl_generic_to_type { + ($self:ident, $self_variant:ident, $to_self:ident, $to_float:ident, $from_float:path) => {{ + if let Self::$self_variant(ref v) = *$self { + return Ok(v.clone()); + } + + Ok(match *$self { + Self::Sum(ref expressions) => { + let mut sum = 0.; + for sub in &**expressions { + sum += sub.$to_self()?.$to_float(); + } + $from_float(sum) + }, + Self::Clamp { + ref min, + ref center, + ref max, + } => { + let min = min.$to_self()?; + let center = center.$to_self()?; + let max = max.$to_self()?; + + // Equivalent to cmp::max(min, cmp::min(center, max)) + // + // But preserving units when appropriate. + let center_float = center.$to_float(); + let min_float = min.$to_float(); + let max_float = max.$to_float(); + + let mut result = center; + let mut result_float = center_float; + + if result_float > max_float { + result = max; + result_float = max_float; + } + + if result_float < min_float { + min + } else { + result + } + }, + Self::MinMax(ref nodes, op) => { + let mut result = nodes[0].$to_self()?; + let mut result_float = result.$to_float(); + for node in nodes.iter().skip(1) { + let candidate = node.$to_self()?; + let candidate_float = candidate.$to_float(); + let candidate_wins = match op { + MinMaxOp::Min => candidate_float < result_float, + MinMaxOp::Max => candidate_float > result_float, + }; + if candidate_wins { + result = candidate; + result_float = candidate_float; + } + } + result + }, + Self::Length(..) | + Self::Angle(..) | + Self::Time(..) | + Self::Percentage(..) | + Self::Number(..) => return Err(()), + }) + }}; +} + +impl PartialOrd for CalcNode { + fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { + use self::CalcNode::*; + match (self, other) { + (&Length(ref one), &Length(ref other)) => one.partial_cmp(other), + (&Percentage(ref one), &Percentage(ref other)) => one.partial_cmp(other), + (&Angle(ref one), &Angle(ref other)) => one.degrees().partial_cmp(&other.degrees()), + (&Time(ref one), &Time(ref other)) => one.seconds().partial_cmp(&other.seconds()), + (&Number(ref one), &Number(ref other)) => one.partial_cmp(other), + _ => None, + } + } +} + impl CalcNode { + fn negate(&mut self) { + self.mul_by(-1.); + } + + fn mul_by(&mut self, scalar: f32) { + match *self { + Self::Length(ref mut l) => { + // FIXME: For consistency this should probably convert absolute + // lengths into pixels. + *l = *l * scalar; + }, + Self::Number(ref mut n) => { + *n *= scalar; + }, + Self::Angle(ref mut a) => { + *a = Angle::from_calc(a.degrees() * scalar); + }, + Self::Time(ref mut t) => { + *t = Time::from_calc(t.seconds() * scalar); + }, + Self::Percentage(ref mut p) => { + *p *= scalar; + }, + // Multiplication is distributive across this. + Self::Sum(ref mut children) => { + for node in &mut **children { + node.mul_by(scalar); + } + }, + // This one is a bit trickier. + Self::MinMax(ref mut children, ref mut op) => { + for node in &mut **children { + node.mul_by(scalar); + } + + // For negatives we need to invert the operation. + if scalar < 0. { + *op = match *op { + MinMaxOp::Min => MinMaxOp::Max, + MinMaxOp::Max => MinMaxOp::Min, + } + } + }, + // Multiplication is distributive across these. + Self::Clamp { + ref mut min, + ref mut center, + ref mut max, + } => { + min.mul_by(scalar); + center.mul_by(scalar); + max.mul_by(scalar); + // For negatives we need to swap min / max. + if scalar < 0. { + mem::swap(min, max); + } + }, + } + } + + fn calc_node_sort_key(&self) -> SortKey { + match *self { + Self::Number(..) => SortKey::Number, + Self::Percentage(..) => SortKey::Percentage, + Self::Time(..) => SortKey::Sec, + Self::Angle(..) => SortKey::Deg, + Self::Length(ref l) => match *l { + NoCalcLength::Absolute(..) => SortKey::Px, + NoCalcLength::FontRelative(ref relative) => match *relative { + FontRelativeLength::Ch(..) => SortKey::Ch, + FontRelativeLength::Em(..) => SortKey::Em, + FontRelativeLength::Ex(..) => SortKey::Ex, + FontRelativeLength::Rem(..) => SortKey::Rem, + }, + NoCalcLength::ViewportPercentage(ref vp) => match *vp { + ViewportPercentageLength::Vh(..) => SortKey::Vh, + ViewportPercentageLength::Vw(..) => SortKey::Vw, + ViewportPercentageLength::Vmax(..) => SortKey::Vmax, + ViewportPercentageLength::Vmin(..) => SortKey::Vmin, + }, + NoCalcLength::ServoCharacterWidth(..) => unreachable!(), + }, + Self::Sum(..) | Self::MinMax(..) | Self::Clamp { .. } => SortKey::Other, + } + } + + /// Tries to merge one sum to another, that is, perform `x` + `y`. + /// + /// Only handles leaf nodes, it's the caller's responsibility to simplify + /// them before calling this if needed. + fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> { + use self::CalcNode::*; + + match (self, other) { + (&mut Number(ref mut one), &Number(ref other)) | + (&mut Percentage(ref mut one), &Percentage(ref other)) => { + *one += *other; + }, + (&mut Angle(ref mut one), &Angle(ref other)) => { + *one = specified::Angle::from_calc(one.degrees() + other.degrees()); + }, + (&mut Time(ref mut one), &Time(ref other)) => { + *one = specified::Time::from_calc(one.seconds() + other.seconds()); + }, + (&mut Length(ref mut one), &Length(ref other)) => { + *one = one.try_sum(other)?; + }, + _ => return Err(()), + } + + Ok(()) + } + + /// Simplifies and sorts the calculation. This is only needed if it's going + /// to be preserved after parsing (so, for `<length-percentage>`). Otherwise + /// we can just evaluate it and we'll come up with a simplified value + /// anyways. + fn simplify_and_sort_children(&mut self) { + macro_rules! replace_self_with { + ($slot:expr) => {{ + let result = mem::replace($slot, Self::Number(0.)); + mem::replace(self, result); + }}; + } + match *self { + Self::Clamp { + ref mut min, + ref mut center, + ref mut max, + } => { + min.simplify_and_sort_children(); + center.simplify_and_sort_children(); + max.simplify_and_sort_children(); + + // NOTE: clamp() is max(min, min(center, max)) + let min_cmp_center = match min.partial_cmp(¢er) { + Some(o) => o, + None => return, + }; + + // So if we can prove that min is more than center, then we won, + // as that's what we should always return. + if matches!(min_cmp_center, cmp::Ordering::Greater) { + return replace_self_with!(&mut **min); + } + + // Otherwise try with max. + let max_cmp_center = match max.partial_cmp(¢er) { + Some(o) => o, + None => return, + }; + + if matches!(max_cmp_center, cmp::Ordering::Less) { + // max is less than center, so we need to return effectively + // `max(min, max)`. + let max_cmp_min = match max.partial_cmp(&min) { + Some(o) => o, + None => { + debug_assert!( + false, + "We compared center with min and max, how are \ + min / max not comparable with each other?" + ); + return; + }, + }; + + if matches!(max_cmp_min, cmp::Ordering::Less) { + return replace_self_with!(&mut **min); + } + + return replace_self_with!(&mut **max); + } + + // Otherwise we're the center node. + return replace_self_with!(&mut **center); + }, + Self::MinMax(ref mut children, op) => { + for child in &mut **children { + child.simplify_and_sort_children(); + } + + let winning_order = match op { + MinMaxOp::Min => cmp::Ordering::Less, + MinMaxOp::Max => cmp::Ordering::Greater, + }; + + let mut result = 0; + for i in 1..children.len() { + let o = match children[i].partial_cmp(&children[result]) { + // We can't compare all the children, so we can't + // know which one will actually win. Bail out and + // keep ourselves as a min / max function. + // + // TODO: Maybe we could simplify compatible children, + // see https://github.com/w3c/csswg-drafts/issues/4756 + None => return, + Some(o) => o, + }; + + if o == winning_order { + result = i; + } + } + + replace_self_with!(&mut children[result]); + }, + Self::Sum(ref mut children_slot) => { + let mut sums_to_merge = SmallVec::<[_; 3]>::new(); + let mut extra_kids = 0; + for (i, child) in children_slot.iter_mut().enumerate() { + child.simplify_and_sort_children(); + if let Self::Sum(ref mut children) = *child { + extra_kids += children.len(); + sums_to_merge.push(i); + } + } + + // If we only have one kid, we've already simplified it, and it + // doesn't really matter whether it's a sum already or not, so + // lift it up and continue. + if children_slot.len() == 1 { + return replace_self_with!(&mut children_slot[0]); + } + + let mut children = mem::replace(children_slot, Box::new([])).into_vec(); + + if !sums_to_merge.is_empty() { + children.reserve(extra_kids - sums_to_merge.len()); + // Merge all our nested sums, in reverse order so that the + // list indices are not invalidated. + for i in sums_to_merge.drain(..).rev() { + let kid_children = match children.swap_remove(i) { + Self::Sum(c) => c, + _ => unreachable!(), + }; + + // This would be nicer with + // https://github.com/rust-lang/rust/issues/59878 fixed. + children.extend(kid_children.into_vec()); + } + } + + debug_assert!(children.len() >= 2, "Should still have multiple kids!"); + + // Sort by spec order. + children.sort_unstable_by_key(|c| c.calc_node_sort_key()); + + // NOTE: if the function returns true, by the docs of dedup_by, + // a is removed. + children.dedup_by(|a, b| b.try_sum_in_place(a).is_ok()); + + if children.len() == 1 { + // If only one children remains, lift it up, and carry on. + replace_self_with!(&mut children[0]); + } else { + // Else put our simplified children back. + mem::replace(children_slot, children.into_boxed_slice()); + } + }, + Self::Length(ref mut len) => { + if let NoCalcLength::Absolute(ref mut absolute_length) = *len { + *absolute_length = AbsoluteLength::Px(absolute_length.to_px()); + } + }, + Self::Percentage(..) | Self::Angle(..) | Self::Time(..) | Self::Number(..) => { + // These are leaves already, nothing to do. + }, + } + } + /// Tries to parse a single element in the expression, that is, a /// `<length>`, `<angle>`, `<time>`, `<percentage>`, according to /// `expected_unit`. @@ -203,11 +610,12 @@ impl CalcNode { (&Token::Percentage { unit_value, .. }, CalcUnit::Percentage) => { Ok(CalcNode::Percentage(unit_value)) }, - (&Token::ParenthesisBlock, _) => { - input.parse_nested_block(|i| CalcNode::parse(context, i, expected_unit)) - }, - (&Token::Function(ref name), _) if name.eq_ignore_ascii_case("calc") => { - input.parse_nested_block(|i| CalcNode::parse(context, i, expected_unit)) + (&Token::ParenthesisBlock, _) => input.parse_nested_block(|input| { + CalcNode::parse_argument(context, input, expected_unit) + }), + (&Token::Function(ref name), _) => { + let function = CalcNode::math_function(name, location)?; + CalcNode::parse(context, input, function, expected_unit) }, (t, _) => Err(location.new_unexpected_token_error(t.clone())), } @@ -219,9 +627,58 @@ impl CalcNode { fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + function: MathFunction, + expected_unit: CalcUnit, + ) -> Result<Self, ParseError<'i>> { + // TODO: Do something different based on the function name. In + // particular, for non-calc function we need to take a list of + // comma-separated arguments and such. + input.parse_nested_block(|input| { + match function { + MathFunction::Calc => Self::parse_argument(context, input, expected_unit), + MathFunction::Clamp => { + let min = Self::parse_argument(context, input, expected_unit)?; + input.expect_comma()?; + let center = Self::parse_argument(context, input, expected_unit)?; + input.expect_comma()?; + let max = Self::parse_argument(context, input, expected_unit)?; + Ok(Self::Clamp { + min: Box::new(min), + center: Box::new(center), + max: Box::new(max), + }) + }, + MathFunction::Min | MathFunction::Max => { + // TODO(emilio): The common case for parse_comma_separated + // is just one element, but for min / max is two, really... + // + // Consider adding an API to cssparser to specify the + // initial vector capacity? + let arguments = input + .parse_comma_separated(|input| { + Self::parse_argument(context, input, expected_unit) + })? + .into_boxed_slice(); + + let op = match function { + MathFunction::Min => MinMaxOp::Min, + MathFunction::Max => MinMaxOp::Max, + _ => unreachable!(), + }; + + Ok(Self::MinMax(arguments, op)) + }, + } + }) + } + + fn parse_argument<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, expected_unit: CalcUnit, ) -> Result<Self, ParseError<'i>> { - let mut root = Self::parse_product(context, input, expected_unit)?; + let mut sum = SmallVec::<[CalcNode; 1]>::new(); + sum.push(Self::parse_product(context, input, expected_unit)?); loop { let start = input.state(); @@ -232,14 +689,12 @@ impl CalcNode { } match *input.next()? { Token::Delim('+') => { - let rhs = Self::parse_product(context, input, expected_unit)?; - let new_root = CalcNode::Sum(Box::new(root), Box::new(rhs)); - root = new_root; + sum.push(Self::parse_product(context, input, expected_unit)?); }, Token::Delim('-') => { - let rhs = Self::parse_product(context, input, expected_unit)?; - let new_root = CalcNode::Sub(Box::new(root), Box::new(rhs)); - root = new_root; + let mut rhs = Self::parse_product(context, input, expected_unit)?; + rhs.negate(); + sum.push(rhs); }, ref t => { let t = t.clone(); @@ -254,7 +709,11 @@ impl CalcNode { } } - Ok(root) + Ok(if sum.len() == 1 { + sum.drain(..).next().unwrap() + } else { + Self::Sum(sum.into_boxed_slice()) + }) } /// Parse a top-level `calc` expression, and all the products that may @@ -271,20 +730,38 @@ impl CalcNode { input: &mut Parser<'i, 't>, expected_unit: CalcUnit, ) -> Result<Self, ParseError<'i>> { - let mut root = Self::parse_one(context, input, expected_unit)?; + let mut node = Self::parse_one(context, input, expected_unit)?; loop { let start = input.state(); match input.next() { Ok(&Token::Delim('*')) => { let rhs = Self::parse_one(context, input, expected_unit)?; - let new_root = CalcNode::Mul(Box::new(root), Box::new(rhs)); - root = new_root; + if let Ok(rhs) = rhs.to_number() { + node.mul_by(rhs); + } else if let Ok(number) = node.to_number() { + node = rhs; + node.mul_by(number); + } else { + // One of the two parts of the multiplication has to be + // a number, at least until we implement unit math. + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } }, Ok(&Token::Delim('/')) => { let rhs = Self::parse_one(context, input, expected_unit)?; - let new_root = CalcNode::Div(Box::new(root), Box::new(rhs)); - root = new_root; + // Dividing by units is not ok. + // + // TODO(emilio): Eventually it should be. + let number = match rhs.to_number() { + Ok(n) if n != 0. => n, + _ => { + return Err( + input.new_custom_error(StyleParseErrorKind::UnspecifiedError) + ); + }, + }; + node.mul_by(1. / number); }, _ => { input.reset(&start); @@ -293,55 +770,24 @@ impl CalcNode { } } - Ok(root) + Ok(node) } /// Tries to simplify this expression into a `<length>` or `<percentage`> /// value. fn to_length_or_percentage( - &self, + &mut self, clamping_mode: AllowedNumericType, ) -> Result<CalcLengthPercentage, ()> { let mut ret = CalcLengthPercentage { - clamping_mode: clamping_mode, + clamping_mode, ..Default::default() }; + self.simplify_and_sort_children(); self.add_length_or_percentage_to(&mut ret, 1.0)?; Ok(ret) } - /// Tries to simplify this expression into a `<percentage>` value. - fn to_percentage(&self) -> Result<CSSFloat, ()> { - Ok(match *self { - CalcNode::Percentage(percentage) => percentage, - CalcNode::Sub(ref a, ref b) => a.to_percentage()? - b.to_percentage()?, - CalcNode::Sum(ref a, ref b) => a.to_percentage()? + b.to_percentage()?, - CalcNode::Mul(ref a, ref b) => match a.to_percentage() { - Ok(lhs) => { - let rhs = b.to_number()?; - lhs * rhs - }, - Err(..) => { - let lhs = a.to_number()?; - let rhs = b.to_percentage()?; - lhs * rhs - }, - }, - CalcNode::Div(ref a, ref b) => { - let lhs = a.to_percentage()?; - let rhs = b.to_number()?; - if rhs == 0. { - return Err(()); - } - lhs / rhs - }, - CalcNode::Number(..) | - CalcNode::Length(..) | - CalcNode::Angle(..) | - CalcNode::Time(..) => return Err(()), - }) - } - /// Puts this `<length>` or `<percentage>` into `ret`, or error. /// /// `factor` is the sign or multiplicative factor to account for the sign @@ -394,29 +840,14 @@ impl CalcNode { }, NoCalcLength::ServoCharacterWidth(..) => unreachable!(), }, - CalcNode::Sub(ref a, ref b) => { - a.add_length_or_percentage_to(ret, factor)?; - b.add_length_or_percentage_to(ret, factor * -1.0)?; - }, - CalcNode::Sum(ref a, ref b) => { - a.add_length_or_percentage_to(ret, factor)?; - b.add_length_or_percentage_to(ret, factor)?; - }, - CalcNode::Mul(ref a, ref b) => match b.to_number() { - Ok(rhs) => { - a.add_length_or_percentage_to(ret, factor * rhs)?; - }, - Err(..) => { - let lhs = a.to_number()?; - b.add_length_or_percentage_to(ret, factor * lhs)?; - }, - }, - CalcNode::Div(ref a, ref b) => { - let new_factor = b.to_number()?; - if new_factor == 0. { - return Err(()); + CalcNode::Sum(ref children) => { + for child in &**children { + child.add_length_or_percentage_to(ret, factor)?; } - a.add_length_or_percentage_to(ret, factor / new_factor)?; + }, + CalcNode::MinMax(..) | CalcNode::Clamp { .. } => { + // FIXME(emilio): Implement min/max/clamp for length-percentage. + return Err(()); }, CalcNode::Angle(..) | CalcNode::Time(..) | CalcNode::Number(..) => return Err(()), } @@ -426,103 +857,55 @@ impl CalcNode { /// Tries to simplify this expression into a `<time>` value. fn to_time(&self) -> Result<Time, ()> { - Ok(match *self { - CalcNode::Time(ref time) => time.clone(), - CalcNode::Sub(ref a, ref b) => { - let lhs = a.to_time()?; - let rhs = b.to_time()?; - Time::from_calc(lhs.seconds() - rhs.seconds()) - }, - CalcNode::Sum(ref a, ref b) => { - let lhs = a.to_time()?; - let rhs = b.to_time()?; - Time::from_calc(lhs.seconds() + rhs.seconds()) - }, - CalcNode::Mul(ref a, ref b) => match b.to_number() { - Ok(rhs) => { - let lhs = a.to_time()?; - Time::from_calc(lhs.seconds() * rhs) - }, - Err(()) => { - let lhs = a.to_number()?; - let rhs = b.to_time()?; - Time::from_calc(lhs * rhs.seconds()) - }, - }, - CalcNode::Div(ref a, ref b) => { - let lhs = a.to_time()?; - let rhs = b.to_number()?; - if rhs == 0. { - return Err(()); - } - Time::from_calc(lhs.seconds() / rhs) - }, - CalcNode::Number(..) | - CalcNode::Length(..) | - CalcNode::Percentage(..) | - CalcNode::Angle(..) => return Err(()), - }) + impl_generic_to_type!(self, Time, to_time, seconds, Time::from_calc) } /// Tries to simplify this expression into an `Angle` value. fn to_angle(&self) -> Result<Angle, ()> { - Ok(match *self { - CalcNode::Angle(ref angle) => angle.clone(), - CalcNode::Sub(ref a, ref b) => { - let lhs = a.to_angle()?; - let rhs = b.to_angle()?; - Angle::from_calc(lhs.degrees() - rhs.degrees()) - }, - CalcNode::Sum(ref a, ref b) => { - let lhs = a.to_angle()?; - let rhs = b.to_angle()?; - Angle::from_calc(lhs.degrees() + rhs.degrees()) - }, - CalcNode::Mul(ref a, ref b) => match a.to_angle() { - Ok(lhs) => { - let rhs = b.to_number()?; - Angle::from_calc(lhs.degrees() * rhs) - }, - Err(..) => { - let lhs = a.to_number()?; - let rhs = b.to_angle()?; - Angle::from_calc(lhs * rhs.degrees()) - }, - }, - CalcNode::Div(ref a, ref b) => { - let lhs = a.to_angle()?; - let rhs = b.to_number()?; - if rhs == 0. { - return Err(()); - } - Angle::from_calc(lhs.degrees() / rhs) - }, - CalcNode::Number(..) | - CalcNode::Length(..) | - CalcNode::Percentage(..) | - CalcNode::Time(..) => return Err(()), - }) + impl_generic_to_type!(self, Angle, to_angle, degrees, Angle::from_calc) } /// Tries to simplify this expression into a `<number>` value. fn to_number(&self) -> Result<CSSFloat, ()> { - Ok(match *self { - CalcNode::Number(n) => n, - CalcNode::Sum(ref a, ref b) => a.to_number()? + b.to_number()?, - CalcNode::Sub(ref a, ref b) => a.to_number()? - b.to_number()?, - CalcNode::Mul(ref a, ref b) => a.to_number()? * b.to_number()?, - CalcNode::Div(ref a, ref b) => { - let lhs = a.to_number()?; - let rhs = b.to_number()?; - if rhs == 0. { - return Err(()); - } - lhs / rhs - }, - CalcNode::Length(..) | - CalcNode::Percentage(..) | - CalcNode::Angle(..) | - CalcNode::Time(..) => return Err(()), + impl_generic_to_type!(self, Number, to_number, clone, From::from) + } + + /// Tries to simplify this expression into a `<percentage>` value. + fn to_percentage(&self) -> Result<CSSFloat, ()> { + impl_generic_to_type!(self, Percentage, to_percentage, clone, From::from) + } + + /// Given a function name, and the location from where the token came from, + /// return a mathematical function corresponding to that name or an error. + #[inline] + pub fn math_function<'i>( + name: &CowRcStr<'i>, + location: cssparser::SourceLocation, + ) -> Result<MathFunction, ParseError<'i>> { + // TODO(emilio): Unify below when the pref for math functions is gone. + if name.eq_ignore_ascii_case("calc") { + return Ok(MathFunction::Calc); + } + + #[cfg(feature = "gecko")] + fn comparison_functions_enabled() -> bool { + static_prefs::pref!("layout.css.comparison-functions.enabled") + } + + #[cfg(feature = "servo")] + fn comparison_functions_enabled() -> bool { + false + } + + if !comparison_functions_enabled() { + return Err(location.new_unexpected_token_error(Token::Function(name.clone()))); + } + + Ok(match_ignore_ascii_case! { &*name, + "min" => MathFunction::Min, + "max" => MathFunction::Max, + "clamp" => MathFunction::Clamp, + _ => return Err(location.new_unexpected_token_error(Token::Function(name.clone()))), }) } @@ -530,8 +913,9 @@ impl CalcNode { pub fn parse_integer<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + function: MathFunction, ) -> Result<CSSInteger, ParseError<'i>> { - Self::parse_number(context, input).map(|n| n.round() as CSSInteger) + Self::parse_number(context, input, function).map(|n| n.round() as CSSInteger) } /// Convenience parsing function for `<length> | <percentage>`. @@ -539,8 +923,9 @@ impl CalcNode { context: &ParserContext, input: &mut Parser<'i, 't>, clamping_mode: AllowedNumericType, + function: MathFunction, ) -> Result<CalcLengthPercentage, ParseError<'i>> { - Self::parse(context, input, CalcUnit::LengthPercentage)? + Self::parse(context, input, function, CalcUnit::LengthPercentage)? .to_length_or_percentage(clamping_mode) .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } @@ -549,8 +934,9 @@ impl CalcNode { pub fn parse_percentage<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + function: MathFunction, ) -> Result<CSSFloat, ParseError<'i>> { - Self::parse(context, input, CalcUnit::Percentage)? + Self::parse(context, input, function, CalcUnit::Percentage)? .to_percentage() .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } @@ -560,8 +946,9 @@ impl CalcNode { context: &ParserContext, input: &mut Parser<'i, 't>, clamping_mode: AllowedNumericType, + function: MathFunction, ) -> Result<CalcLengthPercentage, ParseError<'i>> { - Self::parse(context, input, CalcUnit::Length)? + Self::parse(context, input, function, CalcUnit::Length)? .to_length_or_percentage(clamping_mode) .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } @@ -570,8 +957,9 @@ impl CalcNode { pub fn parse_number<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + function: MathFunction, ) -> Result<CSSFloat, ParseError<'i>> { - Self::parse(context, input, CalcUnit::Number)? + Self::parse(context, input, function, CalcUnit::Number)? .to_number() .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } @@ -580,8 +968,9 @@ impl CalcNode { pub fn parse_angle<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + function: MathFunction, ) -> Result<Angle, ParseError<'i>> { - Self::parse(context, input, CalcUnit::Angle)? + Self::parse(context, input, function, CalcUnit::Angle)? .to_angle() .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } @@ -590,8 +979,9 @@ impl CalcNode { pub fn parse_time<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + function: MathFunction, ) -> Result<Time, ParseError<'i>> { - Self::parse(context, input, CalcUnit::Time)? + Self::parse(context, input, function, CalcUnit::Time)? .to_time() .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } @@ -600,8 +990,9 @@ impl CalcNode { pub fn parse_number_or_percentage<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + function: MathFunction, ) -> Result<NumberOrPercentage, ParseError<'i>> { - let node = Self::parse(context, input, CalcUnit::Percentage)?; + let node = Self::parse(context, input, function, CalcUnit::Percentage)?; if let Ok(value) = node.to_number() { return Ok(NumberOrPercentage::Number { value }); @@ -617,8 +1008,9 @@ impl CalcNode { pub fn parse_angle_or_number<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + function: MathFunction, ) -> Result<AngleOrNumber, ParseError<'i>> { - let node = Self::parse(context, input, CalcUnit::Angle)?; + let node = Self::parse(context, input, function, CalcUnit::Angle)?; if let Ok(angle) = node.to_angle() { let degrees = angle.degrees(); diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 4e761f95338..be969f27cd4 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -298,8 +298,9 @@ impl<'a, 'b: 'a, 'i: 'a> ::cssparser::ColorComponentParser<'i> for ColorComponen Ok(AngleOrNumber::Angle { degrees }) }, Token::Number { value, .. } => Ok(AngleOrNumber::Number { value }), - Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { - input.parse_nested_block(|i| CalcNode::parse_angle_or_number(self.0, i)) + Token::Function(ref name) => { + let function = CalcNode::math_function(name, location)?; + CalcNode::parse_angle_or_number(self.0, input, function) }, t => return Err(location.new_unexpected_token_error(t)), } @@ -323,15 +324,16 @@ impl<'a, 'b: 'a, 'i: 'a> ::cssparser::ColorComponentParser<'i> for ColorComponen ) -> Result<NumberOrPercentage, ParseError<'i>> { let location = input.current_source_location(); - match input.next()?.clone() { + match *input.next()? { Token::Number { value, .. } => Ok(NumberOrPercentage::Number { value }), Token::Percentage { unit_value, .. } => { Ok(NumberOrPercentage::Percentage { unit_value }) }, - Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { - input.parse_nested_block(|i| CalcNode::parse_number_or_percentage(self.0, i)) + Token::Function(ref name) => { + let function = CalcNode::math_function(name, location)?; + CalcNode::parse_number_or_percentage(self.0, input, function) }, - t => return Err(location.new_unexpected_token_error(t)), + ref t => return Err(location.new_unexpected_token_error(t.clone())), } } } diff --git a/components/style/values/specified/counters.rs b/components/style/values/specified/counters.rs index 09020e73448..0849cc19116 100644 --- a/components/style/values/specified/counters.rs +++ b/components/style/values/specified/counters.rs @@ -9,8 +9,6 @@ use crate::computed_values::list_style_type::T as ListStyleType; use crate::parser::{Parse, ParserContext}; use crate::values::generics::counters as generics; use crate::values::generics::counters::CounterPair; -use crate::values::generics::counters::GenericCounterIncrement; -use crate::values::generics::counters::GenericCounterSetOrReset; #[cfg(feature = "gecko")] use crate::values::generics::CounterStyle; use crate::values::specified::url::SpecifiedImageUrl; @@ -23,7 +21,7 @@ use selectors::parser::SelectorParseErrorKind; use style_traits::{ParseError, StyleParseErrorKind}; /// A specified value for the `counter-increment` property. -pub type CounterIncrement = GenericCounterIncrement<Integer>; +pub type CounterIncrement = generics::GenericCounterIncrement<Integer>; impl Parse for CounterIncrement { fn parse<'i, 't>( @@ -35,7 +33,7 @@ impl Parse for CounterIncrement { } /// A specified value for the `counter-set` and `counter-reset` properties. -pub type CounterSetOrReset = GenericCounterSetOrReset<Integer>; +pub type CounterSetOrReset = generics::GenericCounterSetOrReset<Integer>; impl Parse for CounterSetOrReset { fn parse<'i, 't>( @@ -84,10 +82,10 @@ fn parse_counters<'i, 't>( } /// The specified value for the `content` property. -pub type Content = generics::Content<SpecifiedImageUrl>; +pub type Content = generics::GenericContent<SpecifiedImageUrl>; /// The specified value for a content item in the `content` property. -pub type ContentItem = generics::ContentItem<SpecifiedImageUrl>; +pub type ContentItem = generics::GenericContentItem<SpecifiedImageUrl>; impl Content { #[cfg(feature = "servo")] @@ -115,6 +113,7 @@ impl Parse for Content { // normal | none | [ <string> | <counter> | open-quote | close-quote | no-open-quote | // no-close-quote ]+ // TODO: <uri>, attr(<identifier>) + #[cfg_attr(feature = "servo", allow(unused_mut))] fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, @@ -131,17 +130,9 @@ impl Parse for Content { { return Ok(generics::Content::None); } - #[cfg(feature = "gecko")] - { - if input - .try(|input| input.expect_ident_matching("-moz-alt-content")) - .is_ok() - { - return Ok(generics::Content::MozAltContent); - } - } let mut content = vec![]; + let mut has_alt_content = false; loop { #[cfg(feature = "gecko")] { @@ -153,7 +144,7 @@ impl Parse for Content { match input.next() { Ok(&Token::QuotedString(ref value)) => { content.push(generics::ContentItem::String( - value.as_ref().to_owned().into_boxed_str(), + value.as_ref().to_owned().into(), )); }, Ok(&Token::Function(ref name)) => { @@ -168,7 +159,7 @@ impl Parse for Content { let location = input.current_source_location(); let name = CustomIdent::from_ident(location, input.expect_ident()?, &[])?; input.expect_comma()?; - let separator = input.expect_string()?.as_ref().to_owned().into_boxed_str(); + let separator = input.expect_string()?.as_ref().to_owned().into(); let style = Content::parse_counter_style(context, input); Ok(generics::ContentItem::Counters(name, separator, style)) }), @@ -191,6 +182,11 @@ impl Parse for Content { "close-quote" => generics::ContentItem::CloseQuote, "no-open-quote" => generics::ContentItem::NoOpenQuote, "no-close-quote" => generics::ContentItem::NoCloseQuote, + #[cfg(feature = "gecko")] + "-moz-alt-content" => { + has_alt_content = true; + generics::ContentItem::MozAltContent + }, _ =>{ let ident = ident.clone(); return Err(input.new_custom_error( @@ -206,9 +202,10 @@ impl Parse for Content { }, } } - if content.is_empty() { + // We don't allow to parse `-moz-alt-content in multiple positions. + if content.is_empty() || (has_alt_content && content.len() != 1) { return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } - Ok(generics::Content::Items(content.into_boxed_slice())) + Ok(generics::Content::Items(content.into())) } } diff --git a/components/style/values/specified/font.rs b/components/style/values/specified/font.rs index 05a29c94087..84c5141ed92 100644 --- a/components/style/values/specified/font.rs +++ b/components/style/values/specified/font.rs @@ -574,11 +574,11 @@ impl KeywordInfo { /// Given a parent keyword info (self), apply an additional factor/offset to /// it. - pub fn compose(self, factor: f32, offset: CSSPixelLength) -> Self { + fn compose(self, factor: f32) -> Self { KeywordInfo { kw: self.kw, factor: self.factor * factor, - offset: self.offset * factor + offset, + offset: self.offset * factor, } } } @@ -614,12 +614,6 @@ pub enum FontSize { System(SystemFont), } -impl From<LengthPercentage> for FontSize { - fn from(other: LengthPercentage) -> Self { - FontSize::Length(other) - } -} - /// Specifies a prioritized list of font family names or generic family names. #[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)] #[cfg_attr(feature = "servo", derive(Hash))] @@ -901,7 +895,7 @@ impl FontSize { .get_parent_font() .clone_font_size() .keyword_info - .map(|i| i.compose(factor, CSSPixelLength::new(0.))) + .map(|i| i.compose(factor)) }; let mut info = None; let size = match *self { @@ -927,42 +921,8 @@ impl FontSize { base_size.resolve(context) * pc.0 }, FontSize::Length(LengthPercentage::Calc(ref calc)) => { - let parent = context.style().get_parent_font().clone_font_size(); - // if we contain em/% units and the parent was keyword derived, this is too - // Extract the ratio/offset and compose it - if (calc.em.is_some() || calc.percentage.is_some()) && parent.keyword_info.is_some() - { - let ratio = calc.em.unwrap_or(0.) + calc.percentage.map_or(0., |pc| pc.0); - // Compute it, but shave off the font-relative part (em, %). - // - // This will mean that other font-relative units like ex and - // ch will be computed against the old parent font even when - // the font changes. - // - // There's no particular "right answer" for what to do here, - // Gecko recascades as if the font had changed, we instead - // track the changes and reapply, which means that we carry - // over old computed ex/ch values whilst Gecko recomputes - // new ones. - // - // This is enough of an edge case to not really matter. - let abs = calc - .to_computed_value_zoomed( - context, - FontBaseSize::InheritedStyleButStripEmUnits, - ) - .unclamped_length(); - - info = parent.keyword_info.map(|i| i.compose(ratio, abs)); - } let calc = calc.to_computed_value_zoomed(context, base_size); - // FIXME(emilio): we _could_ use clamp_to_non_negative() - // everywhere, without affecting behavior in theory, since the - // others should reject negatives during parsing. But SMIL - // allows parsing negatives, and relies on us _not_ doing that - // clamping. That's so bonkers :( - calc.percentage_relative_to(base_size.resolve(context)) - .clamp_to_non_negative() + calc.resolve(base_size.resolve(context)) }, FontSize::Keyword(i) => { // As a specified keyword, this is keyword derived diff --git a/components/style/values/specified/gecko.rs b/components/style/values/specified/gecko.rs index 4c85d1df668..3e3085c8849 100644 --- a/components/style/values/specified/gecko.rs +++ b/components/style/values/specified/gecko.rs @@ -23,7 +23,7 @@ fn parse_pixel_or_percent<'i, 't>( value, ref unit, .. } => { match_ignore_ascii_case! { unit, - "px" => Ok(LengthPercentage::new(Length::new(value), None)), + "px" => Ok(LengthPercentage::new_length(Length::new(value))), _ => Err(()), } }, diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index d9a51665ead..ff24404c09a 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -7,9 +7,9 @@ //! [length]: https://drafts.csswg.org/css-values/#lengths use super::{AllowQuirks, Number, Percentage, ToComputedValue}; +use crate::computed_value_flags::ComputedValueFlags; use crate::font_metrics::{FontMetrics, FontMetricsOrientation}; use crate::parser::{Parse, ParserContext}; -use crate::properties::computed_value_flags::ComputedValueFlags; use crate::values::computed::{self, CSSPixelLength, Context}; use crate::values::generics::length as generics; use crate::values::generics::length::{ @@ -71,10 +71,6 @@ pub enum FontBaseSize { CurrentStyle, /// Use the inherited font-size. InheritedStyle, - /// Use the inherited font-size, but strip em units. - /// - /// FIXME(emilio): This is very complex, and should go away. - InheritedStyleButStripEmUnits, } impl FontBaseSize { @@ -82,7 +78,7 @@ impl FontBaseSize { pub fn resolve(&self, context: &Context) -> computed::Length { match *self { FontBaseSize::CurrentStyle => context.style().get_font().clone_font_size().size(), - FontBaseSize::InheritedStyleButStripEmUnits | FontBaseSize::InheritedStyle => { + FontBaseSize::InheritedStyle => { context.style().get_parent_font().clone_font_size().size() }, } @@ -100,6 +96,29 @@ impl FontRelativeLength { } } + fn try_sum(&self, other: &Self) -> Result<Self, ()> { + use self::FontRelativeLength::*; + + if std::mem::discriminant(self) != std::mem::discriminant(other) { + return Err(()); + } + + Ok(match (self, other) { + (&Em(one), &Em(other)) => Em(one + other), + (&Ex(one), &Ex(other)) => Ex(one + other), + (&Ch(one), &Ch(other)) => Ch(one + other), + (&Rem(one), &Rem(other)) => Rem(one + other), + // See https://github.com/rust-lang/rust/issues/68867. rustc isn't + // able to figure it own on its own so we help. + _ => unsafe { + match *self { + Em(..) | Ex(..) | Ch(..) | Rem(..) => {}, + } + debug_unreachable!("Forgot to handle unit in try_sum()") + }, + }) + } + /// Computes the font-relative length. pub fn to_computed_value( &self, @@ -144,11 +163,7 @@ impl FontRelativeLength { } } - if base_size == FontBaseSize::InheritedStyleButStripEmUnits { - (Zero::zero(), length) - } else { - (reference_font_size, length) - } + (reference_font_size, length) }, FontRelativeLength::Ex(length) => { if context.for_non_inherited_property.is_some() { @@ -214,7 +229,7 @@ impl FontRelativeLength { // element, the rem units refer to the property's initial // value. // - let reference_size = if context.is_root_element || context.in_media_query { + let reference_size = if context.builder.is_root_element || context.in_media_query { reference_font_size } else { computed::Length::new(context.device().root_font_size().to_f32_px()) @@ -255,6 +270,29 @@ impl ViewportPercentageLength { } } + fn try_sum(&self, other: &Self) -> Result<Self, ()> { + use self::ViewportPercentageLength::*; + + if std::mem::discriminant(self) != std::mem::discriminant(other) { + return Err(()); + } + + Ok(match (self, other) { + (&Vw(one), &Vw(other)) => Vw(one + other), + (&Vh(one), &Vh(other)) => Vh(one + other), + (&Vmin(one), &Vmin(other)) => Vmin(one + other), + (&Vmax(one), &Vmax(other)) => Vmax(one + other), + // See https://github.com/rust-lang/rust/issues/68867. rustc isn't + // able to figure it own on its own so we help. + _ => unsafe { + match *self { + Vw(..) | Vh(..) | Vmin(..) | Vmax(..) => {}, + } + debug_unreachable!("Forgot to handle unit in try_sum()") + }, + }) + } + /// Computes the given viewport-relative length for the given viewport size. pub fn to_computed_value(&self, viewport_size: Size2D<Au>) -> CSSPixelLength { let (factor, length) = match *self { @@ -362,6 +400,12 @@ impl ToComputedValue for AbsoluteLength { } } +impl PartialOrd for AbsoluteLength { + fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { + self.to_px().partial_cmp(&other.to_px()) + } +} + impl Mul<CSSFloat> for AbsoluteLength { type Output = AbsoluteLength; @@ -476,6 +520,37 @@ impl NoCalcLength { }) } + /// Try to sume two lengths if compatible into the left hand side. + pub(crate) fn try_sum(&self, other: &Self) -> Result<Self, ()> { + use self::NoCalcLength::*; + + if std::mem::discriminant(self) != std::mem::discriminant(other) { + return Err(()); + } + + Ok(match (self, other) { + (&Absolute(ref one), &Absolute(ref other)) => Absolute(*one + *other), + (&FontRelative(ref one), &FontRelative(ref other)) => FontRelative(one.try_sum(other)?), + (&ViewportPercentage(ref one), &ViewportPercentage(ref other)) => { + ViewportPercentage(one.try_sum(other)?) + }, + (&ServoCharacterWidth(ref one), &ServoCharacterWidth(ref other)) => { + ServoCharacterWidth(CharacterWidth(one.0 + other.0)) + }, + // See https://github.com/rust-lang/rust/issues/68867. rustc isn't + // able to figure it own on its own so we help. + _ => unsafe { + match *self { + Absolute(..) | + FontRelative(..) | + ViewportPercentage(..) | + ServoCharacterWidth(..) => {}, + } + debug_unreachable!("Forgot to handle unit in try_sum()") + }, + }) + } + /// Get a px value without context. #[inline] pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> { @@ -494,6 +569,38 @@ impl NoCalcLength { impl SpecifiedValueInfo for NoCalcLength {} +impl PartialOrd for NoCalcLength { + fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { + use self::NoCalcLength::*; + + if std::mem::discriminant(self) != std::mem::discriminant(other) { + return None; + } + + match (self, other) { + (&Absolute(ref one), &Absolute(ref other)) => one.to_px().partial_cmp(&other.to_px()), + (&FontRelative(ref one), &FontRelative(ref other)) => one.partial_cmp(other), + (&ViewportPercentage(ref one), &ViewportPercentage(ref other)) => { + one.partial_cmp(other) + }, + (&ServoCharacterWidth(ref one), &ServoCharacterWidth(ref other)) => { + one.0.partial_cmp(&other.0) + }, + // See https://github.com/rust-lang/rust/issues/68867. rustc isn't + // able to figure it own on its own so we help. + _ => unsafe { + match *self { + Absolute(..) | + FontRelative(..) | + ViewportPercentage(..) | + ServoCharacterWidth(..) => {}, + } + debug_unreachable!("Forgot an arm in partial_cmp?") + }, + } + } +} + impl Zero for NoCalcLength { fn zero() -> Self { NoCalcLength::Absolute(AbsoluteLength::Px(0.)) @@ -542,6 +649,31 @@ impl Mul<CSSFloat> for Length { } } +impl PartialOrd for FontRelativeLength { + fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { + use self::FontRelativeLength::*; + + if std::mem::discriminant(self) != std::mem::discriminant(other) { + return None; + } + + match (self, other) { + (&Em(ref one), &Em(ref other)) => one.partial_cmp(other), + (&Ex(ref one), &Ex(ref other)) => one.partial_cmp(other), + (&Ch(ref one), &Ch(ref other)) => one.partial_cmp(other), + (&Rem(ref one), &Rem(ref other)) => one.partial_cmp(other), + // See https://github.com/rust-lang/rust/issues/68867. rustc isn't + // able to figure it own on its own so we help. + _ => unsafe { + match *self { + Em(..) | Ex(..) | Ch(..) | Rem(..) => {}, + } + debug_unreachable!("Forgot an arm in partial_cmp?") + }, + } + } +} + impl Mul<CSSFloat> for FontRelativeLength { type Output = FontRelativeLength; @@ -570,6 +702,31 @@ impl Mul<CSSFloat> for ViewportPercentageLength { } } +impl PartialOrd for ViewportPercentageLength { + fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { + use self::ViewportPercentageLength::*; + + if std::mem::discriminant(self) != std::mem::discriminant(other) { + return None; + } + + match (self, other) { + (&Vw(ref one), &Vw(ref other)) => one.partial_cmp(other), + (&Vh(ref one), &Vh(ref other)) => one.partial_cmp(other), + (&Vmin(ref one), &Vmin(ref other)) => one.partial_cmp(other), + (&Vmax(ref one), &Vmax(ref other)) => one.partial_cmp(other), + // See https://github.com/rust-lang/rust/issues/68867. rustc isn't + // able to figure it own on its own so we help. + _ => unsafe { + match *self { + Vw(..) | Vh(..) | Vmin(..) | Vmax(..) => {}, + } + debug_unreachable!("Forgot an arm in partial_cmp?") + }, + } + } +} + impl Length { #[inline] fn parse_internal<'i, 't>( @@ -599,11 +756,11 @@ impl Length { value, )))) }, - Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => input - .parse_nested_block(|input| { - CalcNode::parse_length(context, input, num_context) - .map(|calc| Length::Calc(Box::new(calc))) - }), + Token::Function(ref name) => { + let function = CalcNode::math_function(name, location)?; + let calc = CalcNode::parse_length(context, input, num_context, function)?; + Ok(Length::Calc(Box::new(calc))) + }, ref token => return Err(location.new_unexpected_token_error(token.clone())), } } @@ -822,10 +979,10 @@ impl LengthPercentage { return Ok(LengthPercentage::Length(NoCalcLength::from_px(value))); } }, - Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { - let calc = input.parse_nested_block(|i| { - CalcNode::parse_length_or_percentage(context, i, num_context) - })?; + Token::Function(ref name) => { + let function = CalcNode::math_function(name, location)?; + let calc = + CalcNode::parse_length_or_percentage(context, input, num_context, function)?; Ok(LengthPercentage::Calc(Box::new(calc))) }, _ => return Err(location.new_unexpected_token_error(token.clone())), diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 7f3ecfb96b3..54401045809 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -31,7 +31,7 @@ use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKin pub use self::align::{AlignContent, AlignItems, AlignSelf, ContentDistribution}; #[cfg(feature = "gecko")] pub use self::align::{JustifyContent, JustifyItems, JustifySelf, SelfAlignment}; -pub use self::angle::Angle; +pub use self::angle::{AllowUnitlessZeroAngle, Angle}; pub use self::background::{BackgroundRepeat, BackgroundSize}; pub use self::basic_shape::FillRule; pub use self::border::{BorderCornerRadius, BorderImageSlice, BorderImageWidth}; @@ -130,6 +130,47 @@ pub mod transform; pub mod ui; pub mod url; +/// <angle> | <percentage> +/// https://drafts.csswg.org/css-values/#typedef-angle-percentage +#[allow(missing_docs)] +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] +pub enum AngleOrPercentage { + Percentage(Percentage), + Angle(Angle), +} + +impl AngleOrPercentage { + fn parse_internal<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + allow_unitless_zero: AllowUnitlessZeroAngle, + ) -> Result<Self, ParseError<'i>> { + if let Ok(per) = input.try(|i| Percentage::parse(context, i)) { + return Ok(AngleOrPercentage::Percentage(per)); + } + + Angle::parse_internal(context, input, allow_unitless_zero).map(AngleOrPercentage::Angle) + } + + /// Allow unitless angles, used for conic-gradients as specified by the spec. + /// https://drafts.csswg.org/css-images-4/#valdef-conic-gradient-angle + pub fn parse_with_unitless<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + AngleOrPercentage::parse_internal(context, input, AllowUnitlessZeroAngle::Yes) + } +} + +impl Parse for AngleOrPercentage { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + AngleOrPercentage::parse_internal(context, input, AllowUnitlessZeroAngle::No) + } +} + /// Parse a `<number>` value, with a given clamping mode. fn parse_number_with_clamping_mode<'i, 't>( context: &ParserContext, @@ -144,8 +185,9 @@ fn parse_number_with_clamping_mode<'i, 't>( calc_clamping_mode: None, }) }, - Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { - let result = input.parse_nested_block(|i| CalcNode::parse_number(context, i))?; + Token::Function(ref name) => { + let function = CalcNode::math_function(name, location)?; + let result = CalcNode::parse_number(context, input, function)?; Ok(Number { value: result.min(f32::MAX).max(f32::MIN), calc_clamping_mode: Some(clamping_mode), @@ -543,8 +585,9 @@ impl Parse for Integer { Token::Number { int_value: Some(v), .. } => Ok(Integer::new(v)), - Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { - let result = input.parse_nested_block(|i| CalcNode::parse_integer(context, i))?; + Token::Function(ref name) => { + let function = CalcNode::math_function(name, location)?; + let result = CalcNode::parse_integer(context, input, function)?; Ok(Integer::from_calc(result)) }, ref t => Err(location.new_unexpected_token_error(t.clone())), @@ -559,16 +602,16 @@ impl Integer { input: &mut Parser<'i, 't>, min: i32, ) -> Result<Integer, ParseError<'i>> { - match Integer::parse(context, input) { - // FIXME(emilio): The spec asks us to avoid rejecting it at parse - // time except until computed value time. - // - // It's not totally clear it's worth it though, and no other browser - // does this. - Ok(value) if value.value() >= min => Ok(value), - Ok(_value) => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), - Err(e) => Err(e), + let value = Integer::parse(context, input)?; + // FIXME(emilio): The spec asks us to avoid rejecting it at parse + // time except until computed value time. + // + // It's not totally clear it's worth it though, and no other browser + // does this. + if value.value() < min { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } + Ok(value) } /// Parse a non-negative integer. @@ -767,9 +810,12 @@ impl AllowQuirks { ToShmem, )] #[css(function)] +#[repr(C)] pub struct Attr { - /// Optional namespace prefix and URL. - pub namespace: Option<(Prefix, Namespace)>, + /// Optional namespace prefix. + pub namespace_prefix: Prefix, + /// Optional namespace URL. + pub namespace_url: Namespace, /// Attribute name pub attribute: Atom, } @@ -814,7 +860,7 @@ impl Attr { ref t => return Err(location.new_unexpected_token_error(t.clone())), }; - let prefix_and_ns = if let Some(ns) = first { + let (namespace_prefix, namespace_url) = if let Some(ns) = first { let prefix = Prefix::from(ns.as_ref()); let ns = match get_namespace_for_prefix(&prefix, context) { Some(ns) => ns, @@ -823,17 +869,18 @@ impl Attr { .new_custom_error(StyleParseErrorKind::UnspecifiedError)); }, }; - Some((prefix, ns)) + (prefix, ns) } else { - None + (Prefix::default(), Namespace::default()) }; return Ok(Attr { - namespace: prefix_and_ns, + namespace_prefix, + namespace_url, attribute: Atom::from(second_token.as_ref()), }); }, // In the case of attr(foobar ) we don't want to error out - // because of the trailing whitespace + // because of the trailing whitespace. Token::WhiteSpace(..) => {}, ref t => return Err(input.new_unexpected_token_error(t.clone())), } @@ -841,7 +888,8 @@ impl Attr { if let Some(first) = first { Ok(Attr { - namespace: None, + namespace_prefix: Prefix::default(), + namespace_url: Namespace::default(), attribute: Atom::from(first.as_ref()), }) } else { @@ -856,8 +904,8 @@ impl ToCss for Attr { W: Write, { dest.write_str("attr(")?; - if let Some((ref prefix, ref _url)) = self.namespace { - serialize_atom_identifier(prefix, dest)?; + if !self.namespace_prefix.is_empty() { + serialize_atom_identifier(&self.namespace_prefix, dest)?; dest.write_str("|")?; } serialize_atom_identifier(&self.attribute, dest)?; diff --git a/components/style/values/specified/percentage.rs b/components/style/values/specified/percentage.rs index 50ebbb3bcf6..75549dca3be 100644 --- a/components/style/values/specified/percentage.rs +++ b/components/style/values/specified/percentage.rs @@ -117,14 +117,14 @@ impl Percentage { { Ok(Percentage::new(unit_value)) }, - Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { - let result = - input.parse_nested_block(|i| CalcNode::parse_percentage(context, i))?; + Token::Function(ref name) => { + let function = CalcNode::math_function(name, location)?; + let value = CalcNode::parse_percentage(context, input, function)?; // TODO(emilio): -moz-image-rect is the only thing that uses // the clamping mode... I guess we could disallow it... Ok(Percentage { - value: result, + value, calc_clamping_mode: Some(num_context), }) }, diff --git a/components/style/values/specified/position.rs b/components/style/values/specified/position.rs index 8d35671991d..3f01988e49d 100644 --- a/components/style/values/specified/position.rs +++ b/components/style/values/specified/position.rs @@ -22,6 +22,7 @@ use cssparser::Parser; use selectors::parser::SelectorParseErrorKind; use servo_arc::Arc; use std::fmt::{self, Write}; +use style_traits::values::specified::AllowedNumericType; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; /// The specified value of a CSS `<position>` @@ -297,7 +298,7 @@ impl<S: Side> ToComputedValue for PositionComponent<S> { let p = Percentage(1. - length.percentage()); let l = -length.unclamped_length(); // We represent `<end-side> <length>` as `calc(100% - <length>)`. - ComputedLengthPercentage::with_clamping_mode(l, Some(p), length.clamping_mode) + ComputedLengthPercentage::new_calc(l, Some(p), AllowedNumericType::All) }, PositionComponent::Side(_, Some(ref length)) | PositionComponent::Length(ref length) => length.to_computed_value(context), diff --git a/components/style/values/specified/svg_path.rs b/components/style/values/specified/svg_path.rs index 9a94af6a82f..288f396b73c 100644 --- a/components/style/values/specified/svg_path.rs +++ b/components/style/values/specified/svg_path.rs @@ -21,8 +21,10 @@ use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; #[derive( Clone, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToAnimatedZero, ToComputedValue, @@ -40,7 +42,6 @@ impl SVGPathData { /// Get the array of PathCommand. #[inline] pub fn commands(&self) -> &[PathCommand] { - debug_assert!(!self.0.is_empty()); &self.0 } @@ -90,10 +91,6 @@ impl Parse for SVGPathData { ) -> Result<Self, ParseError<'i>> { let location = input.current_source_location(); let path_string = input.expect_string()?.as_ref(); - if path_string.is_empty() { - // Treat an empty string as invalid, so we will not set it. - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } // Parse the svg path string as multiple sub-paths. let mut path_parser = PathParser::new(path_string); @@ -156,8 +153,10 @@ impl ComputeSquaredDistance for SVGPathData { ComputeSquaredDistance, Copy, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToAnimatedZero, ToShmem, @@ -483,8 +482,10 @@ impl ToCss for PathCommand { ComputeSquaredDistance, Copy, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToAnimatedZero, ToShmem, @@ -511,8 +512,10 @@ impl IsAbsolute { ComputeSquaredDistance, Copy, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToAnimatedZero, ToCss, @@ -530,7 +533,9 @@ impl CoordPair { } /// The EllipticalArc flag type. -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem)] +#[derive( + Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, SpecifiedValueInfo, ToShmem, +)] #[repr(C)] pub struct ArcFlag(bool); diff --git a/components/style/values/specified/text.rs b/components/style/values/specified/text.rs index 8c50a61ef94..438b926802f 100644 --- a/components/style/values/specified/text.rs +++ b/components/style/values/specified/text.rs @@ -97,7 +97,7 @@ impl ToComputedValue for LineHeight { let computed_calc = calc.to_computed_value_zoomed(context, FontBaseSize::CurrentStyle); let base = context.style().get_font().clone_font_size().size(); - computed_calc.percentage_relative_to(base) + computed_calc.resolve(base) }, }; GenericLineHeight::Length(result.into()) @@ -596,7 +596,7 @@ impl ToComputedValue for TextAlign { // In that case, the default behavior here will set it to left, // but we want to set it to right -- instead set it to the default (`start`), // which will do the right thing in this case (but not the general case) - if _context.is_root_element { + if _context.builder.is_root_element { return TextAlignKeyword::Start; } let parent = _context @@ -1029,8 +1029,8 @@ pub enum TextDecorationSkipInk { None, } -/// Implements type for `text-underline-offset` and `text-decoration-thickness` properties -pub type TextDecorationLength = GenericTextDecorationLength<Length>; +/// Implements type for `text-decoration-thickness` property +pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>; impl TextDecorationLength { /// `Auto` value. @@ -1048,21 +1048,23 @@ impl TextDecorationLength { bitflags! { #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] - #[value_info(other_values = "auto,under,left,right")] + #[value_info(other_values = "auto,from-font,under,left,right")] #[repr(C)] /// Specified keyword values for the text-underline-position property. - /// (Non-exclusive, but not all combinations are allowed: only `under` may occur - /// together with either `left` or `right`.) - /// https://drafts.csswg.org/css-text-decor-3/#text-underline-position-property + /// (Non-exclusive, but not all combinations are allowed: the spec grammar gives + /// `auto | [ from-font | under ] || [ left | right ]`.) + /// https://drafts.csswg.org/css-text-decor-4/#text-underline-position-property pub struct TextUnderlinePosition: u8 { /// Use automatic positioning below the alphabetic baseline. const AUTO = 0; + /// Use underline position from the first available font. + const FROM_FONT = 1 << 0; /// Below the glyph box. - const UNDER = 1 << 0; + const UNDER = 1 << 1; /// In vertical mode, place to the left of the text. - const LEFT = 1 << 1; + const LEFT = 1 << 2; /// In vertical mode, place to the right of the text. - const RIGHT = 1 << 2; + const RIGHT = 1 << 3; } } @@ -1085,7 +1087,12 @@ impl Parse for TextUnderlinePosition { "auto" if result.is_empty() => { return Ok(result); }, - "under" if !result.intersects(TextUnderlinePosition::UNDER) => { + "from-font" if !result.intersects(TextUnderlinePosition::FROM_FONT | + TextUnderlinePosition::UNDER) => { + result.insert(TextUnderlinePosition::FROM_FONT); + }, + "under" if !result.intersects(TextUnderlinePosition::FROM_FONT | + TextUnderlinePosition::UNDER) => { result.insert(TextUnderlinePosition::UNDER); }, "left" if !result.intersects(TextUnderlinePosition::LEFT | @@ -1131,6 +1138,7 @@ impl ToCss for TextUnderlinePosition { }; } + maybe_write!(FROM_FONT => "from-font"); maybe_write!(UNDER => "under"); maybe_write!(LEFT => "left"); maybe_write!(RIGHT => "right"); diff --git a/components/style/values/specified/time.rs b/components/style/values/specified/time.rs index 0006ec45923..aba3f0a828f 100644 --- a/components/style/values/specified/time.rs +++ b/components/style/values/specified/time.rs @@ -69,7 +69,7 @@ impl Time { /// Returns a `Time` value from a CSS `calc()` expression. pub fn from_calc(seconds: CSSFloat) -> Self { Time { - seconds: seconds, + seconds, unit: TimeUnit::Second, was_calc: true, } @@ -95,11 +95,20 @@ impl Time { Time::parse_dimension(value, unit, /* from_calc = */ false) .map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) }, - Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { - match input.parse_nested_block(|i| CalcNode::parse_time(context, i)) { - Ok(time) if clamping_mode.is_ok(ParsingMode::DEFAULT, time.seconds) => Ok(time), - _ => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), + Token::Function(ref name) => { + let function = CalcNode::math_function(name, location)?; + let time = CalcNode::parse_time(context, input, function)?; + + // FIXME(emilio): Rejecting calc() at parse time is wrong, + // was_calc should probably be replaced by calc_clamping_mode or + // something like we do for numbers, or we should do the + // clamping here instead (simpler, but technically incorrect, + // though still more correct than this!). + if !clamping_mode.is_ok(ParsingMode::DEFAULT, time.seconds) { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } + + Ok(time) }, ref t => return Err(location.new_unexpected_token_error(t.clone())), } diff --git a/components/style_derive/animate.rs b/components/style_derive/animate.rs index 4d8581a8f2b..190568514fc 100644 --- a/components/style_derive/animate.rs +++ b/components/style_derive/animate.rs @@ -11,6 +11,8 @@ use synstructure::{Structure, VariantInfo}; pub fn derive(mut input: DeriveInput) -> TokenStream { let animation_input_attrs = cg::parse_input_attrs::<AnimationInputAttrs>(&input); + let input_attrs = cg::parse_input_attrs::<AnimateInputAttrs>(&input); + let no_bound = animation_input_attrs.no_bound.unwrap_or_default(); let mut where_clause = input.generics.where_clause.take(); for param in input.generics.type_params() { @@ -21,39 +23,32 @@ pub fn derive(mut input: DeriveInput) -> TokenStream { ); } } - let (mut match_body, append_error_clause) = { + let (mut match_body, needs_catchall_branch) = { let s = Structure::new(&input); - let mut append_error_clause = s.variants().len() > 1; - + let needs_catchall_branch = s.variants().len() > 1; let match_body = s.variants().iter().fold(quote!(), |body, variant| { - let arm = match derive_variant_arm(variant, &mut where_clause) { - Ok(arm) => arm, - Err(()) => { - append_error_clause = true; - return body; - }, - }; + let arm = derive_variant_arm(variant, &mut where_clause); quote! { #body #arm } }); - (match_body, append_error_clause) + (match_body, needs_catchall_branch) }; input.generics.where_clause = where_clause; - if append_error_clause { - let input_attrs = cg::parse_input_attrs::<AnimateInputAttrs>(&input); - if let Some(fallback) = input_attrs.fallback { - match_body.append_all(quote! { - (this, other) => #fallback(this, other, procedure) - }); - } else { - match_body.append_all(quote! { _ => Err(()) }); - } + if needs_catchall_branch { + // This ideally shouldn't be needed, but see + // https://github.com/rust-lang/rust/issues/68867 + match_body.append_all(quote! { _ => unsafe { debug_unreachable!() } }); } let name = &input.ident; let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let fallback = match input_attrs.fallback { + Some(fallback) => quote! { #fallback(self, other, procedure) }, + None => quote! { Err(()) }, + }; + quote! { impl #impl_generics crate::values::animated::Animate for #name #ty_generics #where_clause { #[allow(unused_variables, unused_imports)] @@ -63,6 +58,9 @@ pub fn derive(mut input: DeriveInput) -> TokenStream { other: &Self, procedure: crate::values::animated::Procedure, ) -> Result<Self, ()> { + if std::mem::discriminant(self) != std::mem::discriminant(other) { + return #fallback; + } match (self, other) { #match_body } @@ -74,13 +72,17 @@ pub fn derive(mut input: DeriveInput) -> TokenStream { fn derive_variant_arm( variant: &VariantInfo, where_clause: &mut Option<WhereClause>, -) -> Result<TokenStream, ()> { +) -> TokenStream { let variant_attrs = cg::parse_variant_attrs_from_ast::<AnimationVariantAttrs>(&variant.ast()); - if variant_attrs.error { - return Err(()); - } let (this_pattern, this_info) = cg::ref_pattern(&variant, "this"); let (other_pattern, other_info) = cg::ref_pattern(&variant, "other"); + + if variant_attrs.error { + return quote! { + (&#this_pattern, &#other_pattern) => Err(()), + }; + } + let (result_value, result_info) = cg::value(&variant, "result"); let mut computations = quote!(); let iter = result_info.iter().zip(this_info.iter().zip(&other_info)); @@ -107,12 +109,13 @@ fn derive_variant_arm( } } })); - Ok(quote! { + + quote! { (&#this_pattern, &#other_pattern) => { #computations Ok(#result_value) } - }) + } } #[darling(attributes(animate), default)] diff --git a/components/style_derive/compute_squared_distance.rs b/components/style_derive/compute_squared_distance.rs index 9c5f7ec80d1..5e130e75b06 100644 --- a/components/style_derive/compute_squared_distance.rs +++ b/components/style_derive/compute_squared_distance.rs @@ -6,11 +6,12 @@ use crate::animate::{AnimationFieldAttrs, AnimationInputAttrs, AnimationVariantA use derive_common::cg; use proc_macro2::TokenStream; use quote::TokenStreamExt; -use syn::{DeriveInput, Path}; +use syn::{DeriveInput, Path, WhereClause}; use synstructure; pub fn derive(mut input: DeriveInput) -> TokenStream { let animation_input_attrs = cg::parse_input_attrs::<AnimationInputAttrs>(&input); + let input_attrs = cg::parse_input_attrs::<DistanceInputAttrs>(&input); let no_bound = animation_input_attrs.no_bound.unwrap_or_default(); let mut where_clause = input.generics.where_clause.take(); for param in input.generics.type_params() { @@ -22,76 +23,31 @@ pub fn derive(mut input: DeriveInput) -> TokenStream { } } - let (mut match_body, append_error_clause) = { + let (mut match_body, needs_catchall_branch) = { let s = synstructure::Structure::new(&input); - let mut append_error_clause = s.variants().len() > 1; + let needs_catchall_branch = s.variants().len() > 1; let match_body = s.variants().iter().fold(quote!(), |body, variant| { - let attrs = cg::parse_variant_attrs_from_ast::<AnimationVariantAttrs>(&variant.ast()); - if attrs.error { - append_error_clause = true; - return body; - } - - let (this_pattern, this_info) = cg::ref_pattern(&variant, "this"); - let (other_pattern, other_info) = cg::ref_pattern(&variant, "other"); - let sum = if this_info.is_empty() { - quote! { crate::values::distance::SquaredDistance::from_sqrt(0.) } - } else { - let mut sum = quote!(); - sum.append_separated(this_info.iter().zip(&other_info).map(|(this, other)| { - let field_attrs = cg::parse_field_attrs::<DistanceFieldAttrs>(&this.ast()); - if field_attrs.field_bound { - let ty = &this.ast().ty; - cg::add_predicate( - &mut where_clause, - parse_quote!(#ty: crate::values::distance::ComputeSquaredDistance), - ); - } - - let animation_field_attrs = - cg::parse_field_attrs::<AnimationFieldAttrs>(&this.ast()); - - if animation_field_attrs.constant { - quote! { - { - if #this != #other { - return Err(()); - } - crate::values::distance::SquaredDistance::from_sqrt(0.) - } - } - } else { - quote! { - crate::values::distance::ComputeSquaredDistance::compute_squared_distance(#this, #other)? - } - } - }), quote!(+)); - sum - }; - quote! { - #body - (&#this_pattern, &#other_pattern) => { - Ok(#sum) - } - } + let arm = derive_variant_arm(variant, &mut where_clause); + quote! { #body #arm } }); - (match_body, append_error_clause) + (match_body, needs_catchall_branch) }; + input.generics.where_clause = where_clause; - if append_error_clause { - let input_attrs = cg::parse_input_attrs::<DistanceInputAttrs>(&input); - if let Some(fallback) = input_attrs.fallback { - match_body.append_all(quote! { - (this, other) => #fallback(this, other) - }); - } else { - match_body.append_all(quote! { _ => Err(()) }); - } + if needs_catchall_branch { + // This ideally shouldn't be needed, but see: + // https://github.com/rust-lang/rust/issues/68867 + match_body.append_all(quote! { _ => unsafe { debug_unreachable!() } }); } + let fallback = match input_attrs.fallback { + Some(fallback) => quote! { #fallback(self, other) }, + None => quote! { Err(()) }, + }; + let name = &input.ident; let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); @@ -103,6 +59,9 @@ pub fn derive(mut input: DeriveInput) -> TokenStream { &self, other: &Self, ) -> Result<crate::values::distance::SquaredDistance, ()> { + if std::mem::discriminant(self) != std::mem::discriminant(other) { + return #fallback; + } match (self, other) { #match_body } @@ -111,6 +70,60 @@ pub fn derive(mut input: DeriveInput) -> TokenStream { } } +fn derive_variant_arm( + variant: &synstructure::VariantInfo, + mut where_clause: &mut Option<WhereClause>, +) -> TokenStream { + let variant_attrs = cg::parse_variant_attrs_from_ast::<AnimationVariantAttrs>(&variant.ast()); + let (this_pattern, this_info) = cg::ref_pattern(&variant, "this"); + let (other_pattern, other_info) = cg::ref_pattern(&variant, "other"); + + if variant_attrs.error { + return quote! { + (&#this_pattern, &#other_pattern) => Err(()), + }; + } + + let sum = if this_info.is_empty() { + quote! { crate::values::distance::SquaredDistance::from_sqrt(0.) } + } else { + let mut sum = quote!(); + sum.append_separated(this_info.iter().zip(&other_info).map(|(this, other)| { + let field_attrs = cg::parse_field_attrs::<DistanceFieldAttrs>(&this.ast()); + if field_attrs.field_bound { + let ty = &this.ast().ty; + cg::add_predicate( + &mut where_clause, + parse_quote!(#ty: crate::values::distance::ComputeSquaredDistance), + ); + } + + let animation_field_attrs = + cg::parse_field_attrs::<AnimationFieldAttrs>(&this.ast()); + + if animation_field_attrs.constant { + quote! { + { + if #this != #other { + return Err(()); + } + crate::values::distance::SquaredDistance::from_sqrt(0.) + } + } + } else { + quote! { + crate::values::distance::ComputeSquaredDistance::compute_squared_distance(#this, #other)? + } + } + }), quote!(+)); + sum + }; + + return quote! { + (&#this_pattern, &#other_pattern) => Ok(#sum), + }; +} + #[darling(attributes(distance), default)] #[derive(Default, FromDeriveInput)] struct DistanceInputAttrs { diff --git a/components/style_traits/arc_slice.rs b/components/style_traits/arc_slice.rs index bbbac1a0757..f5d0c56e7fc 100644 --- a/components/style_traits/arc_slice.rs +++ b/components/style_traits/arc_slice.rs @@ -4,6 +4,8 @@ //! A thin atomically-reference-counted slice. +use serde::de::{Deserialize, Deserializer}; +use serde::ser::{Serialize, Serializer}; use servo_arc::ThinArc; use std::ops::Deref; use std::ptr::NonNull; @@ -60,6 +62,25 @@ impl<T> Default for ArcSlice<T> { } } +impl<T: Serialize> Serialize for ArcSlice<T> { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + self.deref().serialize(serializer) + } +} + +impl<'de, T: Deserialize<'de>> Deserialize<'de> for ArcSlice<T> { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + let r = Vec::deserialize(deserializer)?; + Ok(ArcSlice::from_iter(r.into_iter())) + } +} + impl<T> ArcSlice<T> { /// Creates an Arc for a slice using the given iterator to generate the /// slice. diff --git a/components/style_traits/owned_slice.rs b/components/style_traits/owned_slice.rs index 33d3ac1c2ab..bbce7065196 100644 --- a/components/style_traits/owned_slice.rs +++ b/components/style_traits/owned_slice.rs @@ -7,6 +7,8 @@ //! A replacement for `Box<[T]>` that cbindgen can understand. use malloc_size_of::{MallocShallowSizeOf, MallocSizeOf, MallocSizeOfOps}; +use serde::de::{Deserialize, Deserializer}; +use serde::ser::{Serialize, Serializer}; use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::ptr::NonNull; @@ -171,3 +173,22 @@ impl<T> iter::FromIterator<T> for OwnedSlice<T> { Vec::from_iter(iter).into() } } + +impl<T: Serialize> Serialize for OwnedSlice<T> { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + self.deref().serialize(serializer) + } +} + +impl<'de, T: Deserialize<'de>> Deserialize<'de> for OwnedSlice<T> { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + let r = Box::<[T]>::deserialize(deserializer)?; + Ok(r.into()) + } +} diff --git a/components/style_traits/owned_str.rs b/components/style_traits/owned_str.rs index 42a83a07713..ebfdcd5e066 100644 --- a/components/style_traits/owned_str.rs +++ b/components/style_traits/owned_str.rs @@ -38,6 +38,34 @@ impl DerefMut for OwnedStr { } } +impl OwnedStr { + /// Convert the OwnedStr into a boxed str. + #[inline] + pub fn into_box(self) -> Box<str> { + self.into_string().into_boxed_str() + } + + /// Convert the OwnedStr into a `String`. + #[inline] + pub fn into_string(self) -> String { + unsafe { String::from_utf8_unchecked(self.0.into_vec()) } + } +} + +impl From<OwnedStr> for String { + #[inline] + fn from(b: OwnedStr) -> Self { + b.into_string() + } +} + +impl From<OwnedStr> for Box<str> { + #[inline] + fn from(b: OwnedStr) -> Self { + b.into_box() + } +} + impl From<Box<str>> for OwnedStr { #[inline] fn from(b: Box<str>) -> Self { diff --git a/tests/unit/style/custom_properties.rs b/tests/unit/style/custom_properties.rs index 5b5741d2690..5bb0a800812 100644 --- a/tests/unit/style/custom_properties.rs +++ b/tests/unit/style/custom_properties.rs @@ -3,10 +3,12 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use cssparser::{Parser, ParserInput}; +use euclid::{Scale, Size2D}; use servo_arc::Arc; use style::custom_properties::{ - CssEnvironment, CustomPropertiesBuilder, CustomPropertiesMap, Name, SpecifiedValue, + CustomPropertiesBuilder, CustomPropertiesMap, Name, SpecifiedValue, }; +use style::media_queries::{Device, MediaType}; use style::properties::{CustomDeclaration, CustomDeclarationValue}; use style::stylesheets::Origin; use test::{self, Bencher}; @@ -26,8 +28,12 @@ fn cascade( }) .collect::<Vec<_>>(); - let env = CssEnvironment; - let mut builder = CustomPropertiesBuilder::new(inherited, &env); + let device = Device::new( + MediaType::screen(), + Size2D::new(800., 600.), + Scale::new(1.0), + ); + let mut builder = CustomPropertiesBuilder::new(inherited, &device); for declaration in &declarations { builder.cascade(declaration, Origin::Author); diff --git a/tests/unit/style/viewport.rs b/tests/unit/style/viewport.rs index 636a6321d89..eeedc653fc8 100644 --- a/tests/unit/style/viewport.rs +++ b/tests/unit/style/viewport.rs @@ -3,8 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use cssparser::{Parser, ParserInput}; -use euclid::Scale; -use euclid::Size2D; +use euclid::{Scale, Size2D}; use servo_arc::Arc; use servo_config::set_pref; use servo_url::ServoUrl; diff --git a/tests/wpt/metadata/css/css-transforms/inheritance.html.ini b/tests/wpt/metadata/css/css-transforms/inheritance.html.ini index f28c3adf97c..b7b8208532b 100644 --- a/tests/wpt/metadata/css/css-transforms/inheritance.html.ini +++ b/tests/wpt/metadata/css/css-transforms/inheritance.html.ini @@ -14,3 +14,6 @@ [Property transform-box does not inherit] expected: FAIL + [Property transform-style has initial value auto] + expected: FAIL + diff --git a/tests/wpt/metadata/css/css-values/calc-serialization-002.html.ini b/tests/wpt/metadata/css/css-values/calc-serialization-002.html.ini deleted file mode 100644 index 30e9a093c56..00000000000 --- a/tests/wpt/metadata/css/css-values/calc-serialization-002.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[calc-serialization-002.html] - [testing calc(1em + 1.27cm + 13% + 3em)] - expected: FAIL - - [testing calc(25.4q + 1vh + 12%)] - expected: FAIL - |