From c87668fcd5442529f651c706cd9e056315debf15 Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Tue, 7 Aug 2018 07:56:01 +0000 Subject: [PATCH 01/35] style: Use union to wrap different shape-like types. r=xidorn 1. We will add more shape-like types in the future, so it's better to use union to reduce the memory usage. 2. Those shape-like types are mutual exclusive, so we could use union to wrap them. Differential Revision: https://phabricator.services.mozilla.com/D2746 --- components/style/gecko/conversions.rs | 6 +++--- components/style/properties/gecko.mako.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/style/gecko/conversions.rs b/components/style/gecko/conversions.rs index 14fee693a03..3b1f31a1b08 100644 --- a/components/style/gecko/conversions.rs +++ b/components/style/gecko/conversions.rs @@ -659,7 +659,7 @@ pub mod basic_shape { StyleShapeSourceType::None => Some(ShapeSource::None), StyleShapeSourceType::Box => Some(ShapeSource::Box(self.mReferenceBox.into())), StyleShapeSourceType::Shape => { - let other_shape = unsafe { &*self.mBasicShape.mPtr }; + let other_shape = unsafe { &*self.__bindgen_anon_1.mBasicShape.as_ref().mPtr }; let shape = other_shape.into(); let reference_box = if self.mReferenceBox == StyleGeometryBox::NoBox { None @@ -677,7 +677,7 @@ pub mod basic_shape { fn from(other: &'a StyleShapeSource) -> Self { match other.mType { StyleShapeSourceType::URL => unsafe { - let shape_image = &*other.mShapeImage.mPtr; + let shape_image = &*other.__bindgen_anon_1.mShapeImage.as_ref().mPtr; let other_url = RefPtr::new(*shape_image.__bindgen_anon_1.mURLValue.as_ref()); let url = ComputedUrl::from_url_value(other_url); ShapeSource::ImageOrUrl(url) @@ -699,7 +699,7 @@ pub mod basic_shape { unreachable!("FloatAreaShape doesn't support URL!"); }, StyleShapeSourceType::Image => unsafe { - let shape_image = &*other.mShapeImage.mPtr; + let shape_image = &*other.__bindgen_anon_1.mShapeImage.as_ref().mPtr; let image = shape_image.into_image().expect("Cannot convert to Image"); ShapeSource::ImageOrUrl(image) }, diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index 312f43878ae..bb4e02ab4d2 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -4960,7 +4960,7 @@ fn static_assert() { ShapeSource::ImageOrUrl(image) => { unsafe { bindings::Gecko_NewShapeImage(${ident}); - let style_image = &mut *${ident}.mShapeImage.mPtr; + let style_image = &mut *${ident}.__bindgen_anon_1.mShapeImage.as_mut().mPtr; style_image.set(image); } } @@ -4980,7 +4980,7 @@ fn static_assert() { // Create StyleBasicShape in StyleShapeSource. mReferenceBox and mType // will be set manually later. Gecko_NewBasicShape(${ident}, basic_shape_type); - &mut *${ident}.mBasicShape.mPtr + &mut *${ident}.__bindgen_anon_1.mBasicShape.as_mut().mPtr } } match servo_shape { From e7945bbfcb77165d18f32a61416c7208e0b27b9d Mon Sep 17 00:00:00 2001 From: Xidorn Quan Date: Tue, 7 Aug 2018 13:57:45 +0000 Subject: [PATCH 02/35] style: Put overflow: -moz-scrollbar-* behind pref. Differential Revision: https://phabricator.services.mozilla.com/D2845 --- .../style/properties/shorthands/box.mako.rs | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/components/style/properties/shorthands/box.mako.rs b/components/style/properties/shorthands/box.mako.rs index 1425f012854..3886c7d2d3c 100644 --- a/components/style/properties/shorthands/box.mako.rs +++ b/components/style/properties/shorthands/box.mako.rs @@ -20,30 +20,36 @@ input: &mut Parser<'i, 't>, ) -> Result> { % if product == "gecko": - let moz_kw_found = input.try(|input| { - try_match_ident_ignore_ascii_case! { input, - "-moz-scrollbars-horizontal" => { - Ok(expanded! { - overflow_x: SpecifiedValue::Scroll, - overflow_y: SpecifiedValue::Hidden, - }) - } - "-moz-scrollbars-vertical" => { - Ok(expanded! { - overflow_x: SpecifiedValue::Hidden, - overflow_y: SpecifiedValue::Scroll, - }) - } - "-moz-scrollbars-none" => { - Ok(expanded! { - overflow_x: SpecifiedValue::Hidden, - overflow_y: SpecifiedValue::Hidden, - }) + use gecko_bindings::structs; + let moz_kw_enabled = unsafe { + structs::StaticPrefs_sVarCache_layout_css_overflow_moz_scrollbars_enabled + }; + if moz_kw_enabled { + let moz_kw_found = input.try(|input| { + try_match_ident_ignore_ascii_case! { input, + "-moz-scrollbars-horizontal" => { + Ok(expanded! { + overflow_x: SpecifiedValue::Scroll, + overflow_y: SpecifiedValue::Hidden, + }) + } + "-moz-scrollbars-vertical" => { + Ok(expanded! { + overflow_x: SpecifiedValue::Hidden, + overflow_y: SpecifiedValue::Scroll, + }) + } + "-moz-scrollbars-none" => { + Ok(expanded! { + overflow_x: SpecifiedValue::Hidden, + overflow_y: SpecifiedValue::Hidden, + }) + } } + }); + if moz_kw_found.is_ok() { + return moz_kw_found } - }); - if moz_kw_found.is_ok() { - return moz_kw_found } % endif let overflow_x = parse_overflow(context, input)?; From e22850dc85104d867acfab235150795d06f92efa Mon Sep 17 00:00:00 2001 From: Xidorn Quan Date: Wed, 8 Aug 2018 12:07:34 +0000 Subject: [PATCH 03/35] style: Add check for non-Copy specified value to ensure specified_is_copy always returns the right result. Differential Revision: https://phabricator.services.mozilla.com/D2931 --- components/style/properties/data.py | 13 +++++++++++++ components/style/properties/longhands/box.mako.rs | 2 +- components/style/properties/properties.mako.rs | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/components/style/properties/data.py b/components/style/properties/data.py index ca05309b4b6..d323243312d 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -290,14 +290,22 @@ class Longhand(object): "AlignContent", "AlignItems", "AlignSelf", + "Appearance", "BackgroundRepeat", "BorderImageRepeat", "BorderStyle", + "Clear", "ColumnCount", "Contain", + "Display", + "Float", + "FontSizeAdjust", + "FontStretch", + "FontStyle", "FontStyleAdjust", "FontSynthesis", "FontWeight", + "GreaterThanOrEqualToOneNumber", "GridAutoFlow", "InitialLetter", "Integer", @@ -311,10 +319,13 @@ class Longhand(object): "NonNegativeNumber", "Opacity", "OutlineStyle", + "OverflowClipBox", "OverscrollBehavior", "Percentage", + "SVGOpacity", "SVGPaintOrder", "ScrollSnapType", + "TextAlign", "TextDecorationLine", "TouchAction", "TransformStyle", @@ -322,6 +333,8 @@ class Longhand(object): "XTextZoom", "ZIndex", } + if self.name == "overflow-y": + return True return bool(self.keyword) def animated_type(self): diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs index 765731c13f2..09866c8b692 100644 --- a/components/style/properties/longhands/box.mako.rs +++ b/components/style/properties/longhands/box.mako.rs @@ -448,7 +448,7 @@ ${helpers.predefined_type( ${helpers.predefined_type( "perspective-origin", - "position::Position", + "Position", "computed::position::Position::center()", boxed=True, extra_prefixes=transform_extra_prefixes, diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index fed6d52b7f7..df0b4655702 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -305,6 +305,21 @@ impl Clone for PropertyDeclaration { } } + // This function ensures that all properties not handled above + // do not have a specified value implements Copy. If you hit + // compile error here, you may want to add the type name into + // Longhand.specified_is_copy in data.py. + fn _static_assert_others_are_not_copy() { + struct Helper(T); + trait AssertCopy { fn assert() {} } + trait AssertNotCopy { fn assert() {} } + impl AssertCopy for Helper {} + % for ty in set(x["type"] for x in others): + impl AssertNotCopy for Helper<${ty}> {} + Helper::<${ty}>::assert(); + % endfor + } + match *self { ${" |\n".join("{}(..)".format(v["name"]) for v in copy)} => { unsafe { debug_unreachable!() } From 5299ce31aae597597fcd00605e3809551bfde90d Mon Sep 17 00:00:00 2001 From: Xidorn Quan Date: Wed, 8 Aug 2018 12:06:19 +0000 Subject: [PATCH 04/35] style: Make several more specified values Copy. Differential Revision: https://phabricator.services.mozilla.com/D2932 --- components/style/properties/data.py | 4 ++++ components/style/values/specified/font.rs | 6 +++--- components/style/values/specified/text.rs | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/components/style/properties/data.py b/components/style/properties/data.py index d323243312d..5e7567562cf 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -304,6 +304,9 @@ class Longhand(object): "FontStyle", "FontStyleAdjust", "FontSynthesis", + "FontVariantEastAsian", + "FontVariantLigatures", + "FontVariantNumeric", "FontWeight", "GreaterThanOrEqualToOneNumber", "GridAutoFlow", @@ -327,6 +330,7 @@ class Longhand(object): "ScrollSnapType", "TextAlign", "TextDecorationLine", + "TextEmphasisPosition", "TouchAction", "TransformStyle", "XSpan", diff --git a/components/style/values/specified/font.rs b/components/style/values/specified/font.rs index f26f5ab74a6..8d8355821e8 100644 --- a/components/style/values/specified/font.rs +++ b/components/style/values/specified/font.rs @@ -1359,7 +1359,7 @@ impl VariantEastAsian { impl_gecko_keyword_conversions!(VariantEastAsian, u16); #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] -#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss)] +#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss)] /// Allows control of glyph substitution and sizing in East Asian text. pub enum FontVariantEastAsian { /// Value variant with `variant-east-asian` @@ -1570,7 +1570,7 @@ impl VariantLigatures { impl_gecko_keyword_conversions!(VariantLigatures, u16); #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] -#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss)] +#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss)] /// Ligatures and contextual forms are ways of combining glyphs /// to produce more harmonized forms pub enum FontVariantLigatures { @@ -1786,7 +1786,7 @@ impl VariantNumeric { impl_gecko_keyword_conversions!(VariantNumeric, u8); #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] -#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss)] +#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss)] /// Specifies control over numerical forms. pub enum FontVariantNumeric { /// Value variant with `variant-numeric` diff --git a/components/style/values/specified/text.rs b/components/style/values/specified/text.rs index ef0e66672d0..ae01040e45f 100644 --- a/components/style/values/specified/text.rs +++ b/components/style/values/specified/text.rs @@ -744,7 +744,7 @@ pub enum TextEmphasisVerticalWritingModeValue { } /// Specified value of `text-emphasis-position` property. -#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss)] pub struct TextEmphasisPosition( pub TextEmphasisHorizontalWritingModeValue, From c77ecd698472f733ee3461f5daa2615f88e6b3db Mon Sep 17 00:00:00 2001 From: Xidorn Quan Date: Wed, 8 Aug 2018 23:40:06 +0000 Subject: [PATCH 05/35] style: Implement flow-relative values for resize property. Differential Revision: https://phabricator.services.mozilla.com/D2908 --- components/style/properties/data.py | 1 + components/style/properties/gecko.mako.rs | 3 + .../style/properties/longhands/box.mako.rs | 18 +++--- components/style/values/computed/box.rs | 55 +++++++++++++++++++ components/style/values/computed/mod.rs | 2 +- components/style/values/specified/box.rs | 15 +++++ components/style/values/specified/mod.rs | 2 +- 7 files changed, 87 insertions(+), 9 deletions(-) diff --git a/components/style/properties/data.py b/components/style/properties/data.py index 5e7567562cf..4d3e4ad2177 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -325,6 +325,7 @@ class Longhand(object): "OverflowClipBox", "OverscrollBehavior", "Percentage", + "Resize", "SVGOpacity", "SVGPaintOrder", "ScrollSnapType", diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index bb4e02ab4d2..81f9b2ec769 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -3106,6 +3106,9 @@ fn static_assert() { ) %> ${impl_keyword('clear', 'mBreakType', clear_keyword)} + <% resize_keyword = Keyword("resize", "None Both Horizontal Vertical") %> + ${impl_keyword('resize', 'mResize', resize_keyword)} + <% overflow_x = data.longhands_by_name["overflow-x"] %> pub fn set_overflow_y(&mut self, v: longhands::overflow_y::computed_value::T) { use properties::longhands::overflow_x::computed_value::T as BaseType; diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs index 09866c8b692..6b6bcf9cbe3 100644 --- a/components/style/properties/longhands/box.mako.rs +++ b/components/style/properties/longhands/box.mako.rs @@ -422,17 +422,21 @@ ${helpers.single_keyword("page-break-inside", // CSS Basic User Interface Module Level 3 // http://dev.w3.org/csswg/css-ui -// FIXME support logical values `block` and `inline` (https://drafts.csswg.org/css-logical-props/#resize) // // This is APPLIES_TO_PLACEHOLDER so we can override, in the UA sheet, the // 'resize' property we'd inherit from textarea otherwise. Basically, just // makes the UA rules easier to write. -${helpers.single_keyword("resize", - "none both horizontal vertical", - products="gecko", - spec="https://drafts.csswg.org/css-ui/#propdef-resize", - flags="APPLIES_TO_PLACEHOLDER", - animation_value_type="discrete")} +${helpers.predefined_type( + "resize", + "Resize", + "computed::Resize::None", + products="gecko", + animation_value_type="discrete", + needs_context=False, + gecko_ffi_name="mResize", + flags="APPLIES_TO_PLACEHOLDER", + spec="https://drafts.csswg.org/css-ui/#propdef-resize", +)} ${helpers.predefined_type( "perspective", diff --git a/components/style/values/computed/box.rs b/components/style/values/computed/box.rs index b0db8337608..15e447ea415 100644 --- a/components/style/values/computed/box.rs +++ b/components/style/values/computed/box.rs @@ -9,6 +9,7 @@ use values::computed::length::{LengthOrPercentage, NonNegativeLength}; use values::generics::box_::AnimationIterationCount as GenericAnimationIterationCount; use values::generics::box_::Perspective as GenericPerspective; use values::generics::box_::VerticalAlign as GenericVerticalAlign; +use values::specified::box_ as specified; pub use values::specified::box_::{AnimationName, Appearance, Contain, Display, OverflowClipBox}; pub use values::specified::box_::{Clear as SpecifiedClear, Float as SpecifiedFloat}; @@ -139,3 +140,57 @@ impl ToComputedValue for SpecifiedClear { } } } + +/// A computed value for the `resize` property. +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, ToCss)] +pub enum Resize { + None, + Both, + Horizontal, + Vertical, +} + +impl ToComputedValue for specified::Resize { + type ComputedValue = Resize; + + #[inline] + fn to_computed_value(&self, context: &Context) -> Resize { + let is_vertical = context.style().writing_mode.is_vertical(); + match self { + specified::Resize::Inline => { + context.rule_cache_conditions.borrow_mut() + .set_writing_mode_dependency(context.builder.writing_mode); + if is_vertical { + Resize::Vertical + } else { + Resize::Horizontal + } + } + specified::Resize::Block => { + context.rule_cache_conditions.borrow_mut() + .set_writing_mode_dependency(context.builder.writing_mode); + if is_vertical { + Resize::Horizontal + } else { + Resize::Vertical + } + } + specified::Resize::None => Resize::None, + specified::Resize::Both => Resize::Both, + specified::Resize::Horizontal => Resize::Horizontal, + specified::Resize::Vertical => Resize::Vertical, + } + } + + #[inline] + fn from_computed_value(computed: &Resize) -> specified::Resize { + match computed { + Resize::None => specified::Resize::None, + Resize::Both => specified::Resize::Both, + Resize::Horizontal => specified::Resize::Horizontal, + Resize::Vertical => specified::Resize::Vertical, + } + } +} diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index fa2a9cdcf0d..9a6b5fd76b5 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -44,7 +44,7 @@ pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumer pub use self::font::{MozScriptLevel, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom}; pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display, TransitionProperty}; pub use self::box_::{Appearance, Clear, Float}; -pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective}; +pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize}; pub use self::box_::{ScrollSnapType, TouchAction, VerticalAlign, WillChange}; pub use self::color::{Color, ColorPropertyValue, RGBAColor}; pub use self::column::ColumnCount; diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index 11084ce7d44..6b474689df3 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -884,6 +884,21 @@ pub enum Clear { InlineEnd } +/// https://drafts.csswg.org/css-ui/#propdef-resize +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, + SpecifiedValueInfo, ToCss)] +pub enum Resize { + None, + Both, + Horizontal, + Vertical, + // https://drafts.csswg.org/css-logical-1/#resize + Inline, + Block, +} + /// The value for the `appearance` property. /// /// https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-appearance diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 1eb59617008..2da4c7e93d3 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -39,7 +39,7 @@ pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumer pub use self::font::{MozScriptLevel, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom}; pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display}; pub use self::box_::{Appearance, Clear, Float}; -pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective}; +pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize}; pub use self::box_::{ScrollSnapType, TouchAction, TransitionProperty, VerticalAlign, WillChange}; pub use self::color::{Color, ColorPropertyValue, RGBAColor}; pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset}; From f14a3edd7dbee441fc315580fa2f782eea7c84e2 Mon Sep 17 00:00:00 2001 From: Xidorn Quan Date: Thu, 9 Aug 2018 13:49:47 +0000 Subject: [PATCH 06/35] style: Use function pointer rather than Fn trait object for collect_property_completion_keywords. Differential Revision: https://phabricator.services.mozilla.com/D2985 --- components/style/properties/properties.mako.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index df0b4655702..64e99ba205d 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -603,16 +603,17 @@ impl NonCustomPropertyId { /// See PropertyId::collect_property_completion_keywords. fn collect_property_completion_keywords(&self, f: KeywordsCollectFn) { - const COLLECT_FUNCTIONS: [&Fn(KeywordsCollectFn); + fn do_nothing(_: KeywordsCollectFn) {} + const COLLECT_FUNCTIONS: [fn(KeywordsCollectFn); ${len(data.longhands) + len(data.shorthands)}] = [ % for prop in data.longhands: - &<${prop.specified_type()} as SpecifiedValueInfo>::collect_completion_keywords, + <${prop.specified_type()} as SpecifiedValueInfo>::collect_completion_keywords, % endfor % for prop in data.shorthands: % if prop.name == "all": - &|_f| {}, // 'all' accepts no value other than CSS-wide keywords + do_nothing, // 'all' accepts no value other than CSS-wide keywords % else: - &:: + :: collect_completion_keywords, % endif % endfor From 9206283f7ea67b32359e59e786ca742e4e0ba735 Mon Sep 17 00:00:00 2001 From: "L. David Baron" Date: Fri, 10 Aug 2018 02:20:53 +0000 Subject: [PATCH 07/35] style: Swap order of values in 'overflow' shorthand property. Differential Revision: https://phabricator.services.mozilla.com/D3069 --- components/style/properties/shorthands/box.mako.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/style/properties/shorthands/box.mako.rs b/components/style/properties/shorthands/box.mako.rs index 3886c7d2d3c..960e608cea7 100644 --- a/components/style/properties/shorthands/box.mako.rs +++ b/components/style/properties/shorthands/box.mako.rs @@ -7,7 +7,7 @@ <%helpers:shorthand name="overflow" flags="SHORTHAND_IN_GETCS" - sub_properties="overflow-x overflow-y" + sub_properties="overflow-y overflow-x" spec="https://drafts.csswg.org/css-overflow/#propdef-overflow" > use properties::longhands::overflow_x::parse as parse_overflow; @@ -52,9 +52,9 @@ } } % endif - let overflow_x = parse_overflow(context, input)?; - let overflow_y = - input.try(|i| parse_overflow(context, i)).unwrap_or(overflow_x); + let overflow_y = parse_overflow(context, input)?; + let overflow_x = + input.try(|i| parse_overflow(context, i)).unwrap_or(overflow_y); Ok(expanded! { overflow_x: overflow_x, overflow_y: overflow_y, @@ -63,10 +63,10 @@ impl<'a> ToCss for LonghandsToSerialize<'a> { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write { - self.overflow_x.to_css(dest)?; + self.overflow_y.to_css(dest)?; if self.overflow_x != self.overflow_y { dest.write_char(' ')?; - self.overflow_y.to_css(dest)?; + self.overflow_x.to_css(dest)?; } Ok(()) } From e305b5a1f816400468d5f20b42bdfc0ec6f3a392 Mon Sep 17 00:00:00 2001 From: Cameron McCormack Date: Sun, 12 Aug 2018 11:16:25 +0000 Subject: [PATCH 08/35] style: Remove unused BorrowedAtom. Differential Revision: https://phabricator.services.mozilla.com/D3159 --- components/style/gecko_string_cache/mod.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/components/style/gecko_string_cache/mod.rs b/components/style/gecko_string_cache/mod.rs index b0c8750265b..e70c7a47c36 100644 --- a/components/style/gecko_string_cache/mod.rs +++ b/components/style/gecko_string_cache/mod.rs @@ -49,10 +49,6 @@ pub struct Atom(*mut WeakAtom); /// where `'a` is the lifetime of something that holds a strong reference to that atom. pub struct WeakAtom(nsAtom); -/// A BorrowedAtom for Gecko is just a weak reference to a `nsAtom`, that -/// hasn't been bumped. -pub type BorrowedAtom<'a> = &'a WeakAtom; - impl Deref for Atom { type Target = WeakAtom; From 916dc79dc95dec669b45eb78ee299f1514fc4f5d Mon Sep 17 00:00:00 2001 From: Xidorn Quan Date: Tue, 14 Aug 2018 08:37:37 +0000 Subject: [PATCH 09/35] style: Rename StyleUserInterface to StyleUI. Differential Revision: https://phabricator.services.mozilla.com/D3276 --- components/style/properties/longhands/inherited_ui.mako.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/style/properties/longhands/inherited_ui.mako.rs b/components/style/properties/longhands/inherited_ui.mako.rs index 9718e33a84d..e597f97ce8c 100644 --- a/components/style/properties/longhands/inherited_ui.mako.rs +++ b/components/style/properties/longhands/inherited_ui.mako.rs @@ -4,7 +4,7 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% data.new_style_struct("InheritedUI", inherited=True, gecko_name="UserInterface") %> +<% data.new_style_struct("InheritedUI", inherited=True, gecko_name="UI") %> ${helpers.predefined_type("cursor", "Cursor", From 0ec5db90a6f9657775bdd3591704e83f48d61776 Mon Sep 17 00:00:00 2001 From: Henri Sivonen Date: Fri, 6 Jul 2018 10:44:43 +0300 Subject: [PATCH 10/35] Use encoding_rs for XPCOM string encoding conversions. Correctness improvements: * UTF errors are handled safely per spec instead of dangerously truncating strings. * There are fewer converter implementations. Performance improvements: * The old code did exact buffer length math, which meant doing UTF math twice on each input string (once for length calculation and another time for conversion). Exact length math is more complicated when handling errors properly, which the old code didn't do. The new code does UTF math on the string content only once (when converting) but risks allocating more than once. There are heuristics in place to lower the probability of reallocation in cases where the double math avoidance isn't enough of a saving to absorb an allocation and memcpy. * Previously, in UTF-16 <-> UTF-8 conversions, an ASCII prefix was optimized but a single non-ASCII code point pessimized the rest of the string. The new code tries to get back on the fast ASCII path. * UTF-16 to Latin1 conversion guarantees less about handling of out-of-range input to eliminate an operation from the inner loop on x86/x86_64. * When assigning to a pre-existing string, the new code tries to reuse the old buffer instead of first releasing the old buffer and then allocating a new one. * When reallocating from the new code, the memcpy covers only the data that is part of the logical length of the old string instead of memcpying the whole capacity. (For old callers old excess memcpy behavior is preserved due to bogus callers. See bug 1472113.) * UTF-8 strings in XPConnect that are in the Latin1 range are passed to SpiderMonkey as Latin1. New features: * Conversion between UTF-8 and Latin1 is added in order to enable faster future interop between Rust code (or otherwise UTF-8-using code) and text node and SpiderMonkey code that uses Latin1. Bug: 1402247 Reviewed-by: Nika,erahm,froydnj --- components/style/properties/gecko.mako.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index 81f9b2ec769..33a66498b2d 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -2124,7 +2124,7 @@ fn static_assert() { }; for (servo, gecko) in v.0.areas.into_iter().zip(refptr.mNamedAreas.iter_mut()) { - gecko.mName.assign_utf8(&*servo.name); + gecko.mName.assign_str(&*servo.name); gecko.mColumnStart = servo.columns.start; gecko.mColumnEnd = servo.columns.end; gecko.mRowStart = servo.rows.start; @@ -2132,7 +2132,7 @@ fn static_assert() { } for (servo, gecko) in v.0.strings.into_iter().zip(refptr.mTemplates.iter_mut()) { - gecko.assign_utf8(&*servo); + gecko.assign_str(&*servo); } self.gecko.mGridTemplateAreas.set_move(refptr.get()) @@ -4189,8 +4189,8 @@ fn static_assert() { }; for (servo, gecko) in other.0.into_iter().zip(refptr.mQuotePairs.iter_mut()) { - gecko.first.assign_utf8(&servo.0); - gecko.second.assign_utf8(&servo.1); + gecko.first.assign_str(&servo.0); + gecko.second.assign_str(&servo.1); } self.gecko.mQuotes.set_move(refptr.get()) @@ -4728,7 +4728,7 @@ fn static_assert() { (structs::NS_STYLE_TEXT_EMPHASIS_STYLE_STRING, &**s) }, }; - self.gecko.mTextEmphasisStyleString.assign_utf8(s); + self.gecko.mTextEmphasisStyleString.assign_str(s); self.gecko.mTextEmphasisStyle = te as u8; } @@ -4829,7 +4829,7 @@ fn static_assert() { TextOverflowSide::Clip => structs::NS_STYLE_TEXT_OVERFLOW_CLIP, TextOverflowSide::Ellipsis => structs::NS_STYLE_TEXT_OVERFLOW_ELLIPSIS, TextOverflowSide::String(ref s) => { - side.mString.assign_utf8(s); + side.mString.assign_str(s); structs::NS_STYLE_TEXT_OVERFLOW_STRING } }; @@ -5461,7 +5461,7 @@ clip-path }; counter_func.mIdent.assign(name.0.as_slice()); if content_type == StyleContentType::Counters { - counter_func.mSeparator.assign_utf8(sep); + counter_func.mSeparator.assign_str(sep); } style.to_gecko_value(&mut counter_func.mCounterStyle, device); } From 064252437bb1c28975833dc376c56ce3ab4e6035 Mon Sep 17 00:00:00 2001 From: Nathan Froyd Date: Tue, 14 Aug 2018 16:46:28 -0400 Subject: [PATCH 11/35] style: remove unused kernel32-sys dependency from style. Bug: 1483344 Reviewed-by: emilio --- components/style/Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index 8e1b5906158..40fcc390228 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -72,9 +72,6 @@ unicode-bidi = "0.3" unicode-segmentation = "1.0" void = "1.0.2" -[target.'cfg(windows)'.dependencies] -kernel32-sys = "0.2" - [build-dependencies] lazy_static = "1" log = "0.4" From cc3c059bfc2bffec05e200e62a8fd098e21e3c61 Mon Sep 17 00:00:00 2001 From: Cameron McCormack Date: Wed, 15 Aug 2018 15:46:00 +1000 Subject: [PATCH 12/35] style: Define atom type in nsGkAtomList.h. Differential Revision: https://phabricator.services.mozilla.com/D3282 --- components/style/gecko/regen_atoms.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/components/style/gecko/regen_atoms.py b/components/style/gecko/regen_atoms.py index 496c7be720a..6d812a4cf50 100755 --- a/components/style/gecko/regen_atoms.py +++ b/components/style/gecko/regen_atoms.py @@ -39,7 +39,7 @@ def msvc32_symbolify(source, ident): class GkAtomSource: - PATTERN = re.compile('^(GK_ATOM)\(([^,]*),[^"]*"([^"]*)"\)', + PATTERN = re.compile('^(GK_ATOM)\(([^,]*),[^"]*"([^"]*)",\s*([^)]*)\)', re.MULTILINE) FILE = "include/nsGkAtomList.h" CLASS = "nsGkAtoms" @@ -47,7 +47,7 @@ class GkAtomSource: class CSSPseudoElementsAtomSource: - PATTERN = re.compile('^(CSS_PSEUDO_ELEMENT)\(([^,]*),[^"]*"([^"]*)",', + PATTERN = re.compile('^(CSS_PSEUDO_ELEMENT)\(([^,]*),[^"]*"([^"]*)",()', re.MULTILINE) FILE = "include/nsCSSPseudoElementList.h" CLASS = "nsCSSPseudoElements" @@ -57,7 +57,7 @@ class CSSPseudoElementsAtomSource: class CSSAnonBoxesAtomSource: - PATTERN = re.compile('^(CSS_ANON_BOX|CSS_NON_INHERITING_ANON_BOX|CSS_WRAPPER_ANON_BOX)\(([^,]*),[^"]*"([^"]*)"\)', # NOQA: E501 + PATTERN = re.compile('^(CSS_ANON_BOX|CSS_NON_INHERITING_ANON_BOX|CSS_WRAPPER_ANON_BOX)\(([^,]*),[^"]*"([^"]*)"()\)', # NOQA: E501 re.MULTILINE) FILE = "include/nsCSSAnonBoxList.h" CLASS = "nsCSSAnonBoxes" @@ -79,12 +79,13 @@ def map_atom(ident): class Atom: - def __init__(self, source, macro_name, ident, value): + def __init__(self, source, macro_name, ident, value, ty): self.ident = "{}_{}".format(source.CLASS, ident) self.original_ident = ident self.value = value self.source = source self.macro = macro_name + self.ty = ty if self.is_anon_box(): assert self.is_inheriting_anon_box() or self.is_non_inheriting_anon_box() @@ -101,7 +102,7 @@ class Atom: return msvc64_symbolify(self.source, self.original_ident) def type(self): - return self.source.TYPE + return self.ty def capitalized(self): return self.original_ident[0].upper() + self.original_ident[1:] @@ -128,7 +129,8 @@ def collect_atoms(objdir): with open(path) as f: content = f.read() for result in source.PATTERN.finditer(content): - atoms.append(Atom(source, result.group(1), result.group(2), result.group(3))) + ty = result.group(4) or source.TYPE + atoms.append(Atom(source, result.group(1), result.group(2), result.group(3), ty)) return atoms @@ -219,9 +221,9 @@ def write_atom_macro(atoms, file_name): f.write(PRELUDE) f.write(IMPORTS) - for source in SOURCES: - if source.TYPE != "nsStaticAtom": - f.write("pub enum {} {{}}\n\n".format(source.TYPE)) + for ty in sorted(set([atom.type() for atom in atoms])): + if ty != "nsStaticAtom": + f.write("pub enum {} {{}}\n\n".format(ty)) f.write(UNSAFE_STATIC) From 99a292dd6d8dfdc4bd85cdd76e60fd9a0ea67c0e Mon Sep 17 00:00:00 2001 From: Cameron McCormack Date: Wed, 15 Aug 2018 15:46:00 +1000 Subject: [PATCH 13/35] style: Move CSS pseudo-element atoms to nsGkAtoms. Differential Revision: https://phabricator.services.mozilla.com/D3283 --- .../gecko/pseudo_element_definition.mako.rs | 22 +++++------ components/style/gecko/regen_atoms.py | 38 +++++++++---------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/components/style/gecko/pseudo_element_definition.mako.rs b/components/style/gecko/pseudo_element_definition.mako.rs index b507fafd865..be0318eadea 100644 --- a/components/style/gecko/pseudo_element_definition.mako.rs +++ b/components/style/gecko/pseudo_element_definition.mako.rs @@ -8,9 +8,9 @@ pub enum PseudoElement { % for pseudo in PSEUDOS: /// ${pseudo.value} % if pseudo.is_tree_pseudo_element(): - ${pseudo.capitalized()}(ThinBoxedSlice), + ${pseudo.capitalized_pseudo()}(ThinBoxedSlice), % else: - ${pseudo.capitalized()}, + ${pseudo.capitalized_pseudo()}, % endif % endfor } @@ -41,7 +41,7 @@ pub const EAGER_PSEUDOS: [PseudoElement; EAGER_PSEUDO_COUNT] = [ ]; <%def name="pseudo_element_variant(pseudo, tree_arg='..')">\ -PseudoElement::${pseudo.capitalized()}${"({})".format(tree_arg) if pseudo.is_tree_pseudo_element() else ""}\ +PseudoElement::${pseudo.capitalized_pseudo()}${"({})".format(tree_arg) if pseudo.is_tree_pseudo_element() else ""}\ impl PseudoElement { @@ -120,7 +120,7 @@ impl PseudoElement { % elif pseudo.is_anon_box(): structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS, % else: - structs::SERVO_CSS_PSEUDO_ELEMENT_FLAGS_${pseudo.original_ident}, + structs::SERVO_CSS_PSEUDO_ELEMENT_FLAGS_${pseudo.pseudo_ident}, % endif % endfor } @@ -132,7 +132,7 @@ impl PseudoElement { match type_ { % for pseudo in PSEUDOS: % if not pseudo.is_anon_box(): - CSSPseudoElementType::${pseudo.original_ident} => { + CSSPseudoElementType::${pseudo.pseudo_ident} => { Some(${pseudo_element_variant(pseudo)}) }, % endif @@ -149,13 +149,13 @@ impl PseudoElement { match *self { % for pseudo in PSEUDOS: % if not pseudo.is_anon_box(): - PseudoElement::${pseudo.capitalized()} => CSSPseudoElementType::${pseudo.original_ident}, + PseudoElement::${pseudo.capitalized_pseudo()} => CSSPseudoElementType::${pseudo.pseudo_ident}, % elif pseudo.is_tree_pseudo_element(): - PseudoElement::${pseudo.capitalized()}(..) => CSSPseudoElementType::XULTree, + PseudoElement::${pseudo.capitalized_pseudo()}(..) => CSSPseudoElementType::XULTree, % elif pseudo.is_inheriting_anon_box(): - PseudoElement::${pseudo.capitalized()} => CSSPseudoElementType_InheritingAnonBox, + PseudoElement::${pseudo.capitalized_pseudo()} => CSSPseudoElementType_InheritingAnonBox, % else: - PseudoElement::${pseudo.capitalized()} => CSSPseudoElementType::NonInheritingAnonBox, + PseudoElement::${pseudo.capitalized_pseudo()} => CSSPseudoElementType::NonInheritingAnonBox, % endif % endfor } @@ -171,7 +171,7 @@ impl PseudoElement { pub fn tree_pseudo_args(&self) -> Option<<&[Atom]> { match *self { % for pseudo in TREE_PSEUDOS: - PseudoElement::${pseudo.capitalized()}(ref args) => Some(args), + PseudoElement::${pseudo.capitalized_pseudo()}(ref args) => Some(args), % endfor _ => None, } @@ -213,7 +213,7 @@ impl PseudoElement { % for pseudo in PSEUDOS: % if pseudo.is_tree_pseudo_element(): if atom == &atom!("${pseudo.value}") { - return Some(PseudoElement::${pseudo.capitalized()}(args.into())); + return Some(PseudoElement::${pseudo.capitalized_pseudo()}(args.into())); } % endif % endfor diff --git a/components/style/gecko/regen_atoms.py b/components/style/gecko/regen_atoms.py index 6d812a4cf50..f4e87c32f8d 100755 --- a/components/style/gecko/regen_atoms.py +++ b/components/style/gecko/regen_atoms.py @@ -28,14 +28,14 @@ def gnu_symbolify(source, ident): return "_ZN{}{}{}{}E".format(len(source.CLASS), source.CLASS, len(ident), ident) -def msvc64_symbolify(source, ident): - return "?{}@{}@@2PEAV{}@@EA".format(ident, source.CLASS, source.TYPE) +def msvc64_symbolify(source, ident, ty): + return "?{}@{}@@2PEAV{}@@EA".format(ident, source.CLASS, ty) -def msvc32_symbolify(source, ident): +def msvc32_symbolify(source, ident, ty): # Prepend "\x01" to avoid LLVM prefixing the mangled name with "_". # See https://github.com/rust-lang/rust/issues/36097 - return "\\x01?{}@{}@@2PAV{}@@A".format(ident, source.CLASS, source.TYPE) + return "\\x01?{}@{}@@2PAV{}@@A".format(ident, source.CLASS, ty) class GkAtomSource: @@ -46,16 +46,6 @@ class GkAtomSource: TYPE = "nsStaticAtom" -class CSSPseudoElementsAtomSource: - PATTERN = re.compile('^(CSS_PSEUDO_ELEMENT)\(([^,]*),[^"]*"([^"]*)",()', - re.MULTILINE) - FILE = "include/nsCSSPseudoElementList.h" - CLASS = "nsCSSPseudoElements" - # NB: nsICSSPseudoElement is effectively the same as a nsStaticAtom, but we need - # this for MSVC name mangling. - TYPE = "nsICSSPseudoElement" - - class CSSAnonBoxesAtomSource: PATTERN = re.compile('^(CSS_ANON_BOX|CSS_NON_INHERITING_ANON_BOX|CSS_WRAPPER_ANON_BOX)\(([^,]*),[^"]*"([^"]*)"()\)', # NOQA: E501 re.MULTILINE) @@ -66,7 +56,6 @@ class CSSAnonBoxesAtomSource: SOURCES = [ GkAtomSource, - CSSPseudoElementsAtomSource, CSSAnonBoxesAtomSource, ] @@ -86,6 +75,11 @@ class Atom: self.source = source self.macro = macro_name self.ty = ty + if self.is_pseudo(): + if self.is_non_anon_box_pseudo(): + self.pseudo_ident = (ident.split("_", 1))[1] + else: + self.pseudo_ident = ident if self.is_anon_box(): assert self.is_inheriting_anon_box() or self.is_non_inheriting_anon_box() @@ -96,16 +90,22 @@ class Atom: return gnu_symbolify(self.source, self.original_ident) def msvc32_symbol(self): - return msvc32_symbolify(self.source, self.original_ident) + return msvc32_symbolify(self.source, self.original_ident, self.ty) def msvc64_symbol(self): - return msvc64_symbolify(self.source, self.original_ident) + return msvc64_symbolify(self.source, self.original_ident, self.ty) def type(self): return self.ty - def capitalized(self): - return self.original_ident[0].upper() + self.original_ident[1:] + def capitalized_pseudo(self): + return self.pseudo_ident[0].upper() + self.pseudo_ident[1:] + + def is_pseudo(self): + return self.is_non_anon_box_pseudo() or self.is_anon_box() + + def is_non_anon_box_pseudo(self): + return self.type() == "nsICSSPseudoElement" def is_anon_box(self): return self.type() == "nsICSSAnonBoxPseudo" From f75419dd7a73d52319084f4cafdc67bd171ecd36 Mon Sep 17 00:00:00 2001 From: Cameron McCormack Date: Wed, 15 Aug 2018 15:46:39 +1000 Subject: [PATCH 14/35] style: Move CSS anonymous box atoms to nsGkAtoms. Differential Revision: https://phabricator.services.mozilla.com/D3284 --- components/style/gecko/regen_atoms.py | 41 +++++++++------------------ 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/components/style/gecko/regen_atoms.py b/components/style/gecko/regen_atoms.py index f4e87c32f8d..9f9a353c629 100755 --- a/components/style/gecko/regen_atoms.py +++ b/components/style/gecko/regen_atoms.py @@ -39,24 +39,14 @@ def msvc32_symbolify(source, ident, ty): class GkAtomSource: - PATTERN = re.compile('^(GK_ATOM)\(([^,]*),[^"]*"([^"]*)",\s*([^)]*)\)', + PATTERN = re.compile('^GK_ATOM\(([^,]*),[^"]*"([^"]*)",\s*([^,]*),\s*([^)]*)\)', re.MULTILINE) FILE = "include/nsGkAtomList.h" CLASS = "nsGkAtoms" - TYPE = "nsStaticAtom" - - -class CSSAnonBoxesAtomSource: - PATTERN = re.compile('^(CSS_ANON_BOX|CSS_NON_INHERITING_ANON_BOX|CSS_WRAPPER_ANON_BOX)\(([^,]*),[^"]*"([^"]*)"()\)', # NOQA: E501 - re.MULTILINE) - FILE = "include/nsCSSAnonBoxList.h" - CLASS = "nsCSSAnonBoxes" - TYPE = "nsICSSAnonBoxPseudo" SOURCES = [ GkAtomSource, - CSSAnonBoxesAtomSource, ] @@ -68,18 +58,18 @@ def map_atom(ident): class Atom: - def __init__(self, source, macro_name, ident, value, ty): + def __init__(self, source, ident, value, ty, atom_type): self.ident = "{}_{}".format(source.CLASS, ident) self.original_ident = ident self.value = value self.source = source - self.macro = macro_name + # The Gecko type: "nsStaticAtom", "nsICSSPseudoElement", or "nsIAnonBoxPseudo" self.ty = ty - if self.is_pseudo(): - if self.is_non_anon_box_pseudo(): - self.pseudo_ident = (ident.split("_", 1))[1] - else: - self.pseudo_ident = ident + # The type of atom: "Atom", "PseudoElement", "NonInheritingAnonBox", + # or "InheritingAnonBox" + self.atom_type = atom_type + if self.is_pseudo() or self.is_anon_box(): + self.pseudo_ident = (ident.split("_", 1))[1] if self.is_anon_box(): assert self.is_inheriting_anon_box() or self.is_non_inheriting_anon_box() @@ -102,20 +92,16 @@ class Atom: return self.pseudo_ident[0].upper() + self.pseudo_ident[1:] def is_pseudo(self): - return self.is_non_anon_box_pseudo() or self.is_anon_box() - - def is_non_anon_box_pseudo(self): - return self.type() == "nsICSSPseudoElement" + return self.atom_type == "PseudoElementAtom" def is_anon_box(self): - return self.type() == "nsICSSAnonBoxPseudo" + return self.is_non_inheriting_anon_box() or self.is_inheriting_anon_box() def is_non_inheriting_anon_box(self): - return self.macro == "CSS_NON_INHERITING_ANON_BOX" + return self.atom_type == "NonInheritingAnonBoxAtom" def is_inheriting_anon_box(self): - return (self.macro == "CSS_ANON_BOX" or - self.macro == "CSS_WRAPPER_ANON_BOX") + return self.atom_type == "InheritingAnonBoxAtom" def is_tree_pseudo_element(self): return self.value.startswith(":-moz-tree-") @@ -129,8 +115,7 @@ def collect_atoms(objdir): with open(path) as f: content = f.read() for result in source.PATTERN.finditer(content): - ty = result.group(4) or source.TYPE - atoms.append(Atom(source, result.group(1), result.group(2), result.group(3), ty)) + atoms.append(Atom(source, result.group(1), result.group(2), result.group(3), result.group(4))) return atoms From f86e9a411ad60f1292a957d46797e260afa82cb4 Mon Sep 17 00:00:00 2001 From: Cameron McCormack Date: Wed, 15 Aug 2018 15:46:42 +1000 Subject: [PATCH 15/35] style: Remove support for multiple static atom sources. Differential Revision: https://phabricator.services.mozilla.com/D3285 --- components/style/gecko/regen_atoms.py | 59 +++++++++++---------------- 1 file changed, 24 insertions(+), 35 deletions(-) diff --git a/components/style/gecko/regen_atoms.py b/components/style/gecko/regen_atoms.py index 9f9a353c629..2cb754f7e5b 100755 --- a/components/style/gecko/regen_atoms.py +++ b/components/style/gecko/regen_atoms.py @@ -20,34 +20,28 @@ PRELUDE = """ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -/* Autogenerated file created by components/style/gecko/binding_tools/regen_atoms.py, DO NOT EDIT DIRECTLY */ +/* Autogenerated file created by components/style/gecko/regen_atoms.py, DO NOT EDIT DIRECTLY */ """[1:] # NOQA: E501 -def gnu_symbolify(source, ident): - return "_ZN{}{}{}{}E".format(len(source.CLASS), source.CLASS, len(ident), ident) +PATTERN = re.compile('^GK_ATOM\(([^,]*),[^"]*"([^"]*)",\s*([^,]*),\s*([^)]*)\)', + re.MULTILINE) +FILE = "include/nsGkAtomList.h" +CLASS = "nsGkAtoms" -def msvc64_symbolify(source, ident, ty): - return "?{}@{}@@2PEAV{}@@EA".format(ident, source.CLASS, ty) +def gnu_symbolify(ident): + return "_ZN{}{}{}{}E".format(len(CLASS), CLASS, len(ident), ident) -def msvc32_symbolify(source, ident, ty): +def msvc64_symbolify(ident, ty): + return "?{}@{}@@2PEAV{}@@EA".format(ident, CLASS, ty) + + +def msvc32_symbolify(ident, ty): # Prepend "\x01" to avoid LLVM prefixing the mangled name with "_". # See https://github.com/rust-lang/rust/issues/36097 - return "\\x01?{}@{}@@2PAV{}@@A".format(ident, source.CLASS, ty) - - -class GkAtomSource: - PATTERN = re.compile('^GK_ATOM\(([^,]*),[^"]*"([^"]*)",\s*([^,]*),\s*([^)]*)\)', - re.MULTILINE) - FILE = "include/nsGkAtomList.h" - CLASS = "nsGkAtoms" - - -SOURCES = [ - GkAtomSource, -] + return "\\x01?{}@{}@@2PAV{}@@A".format(ident, CLASS, ty) def map_atom(ident): @@ -58,11 +52,10 @@ def map_atom(ident): class Atom: - def __init__(self, source, ident, value, ty, atom_type): - self.ident = "{}_{}".format(source.CLASS, ident) + def __init__(self, ident, value, ty, atom_type): + self.ident = "{}_{}".format(CLASS, ident) self.original_ident = ident self.value = value - self.source = source # The Gecko type: "nsStaticAtom", "nsICSSPseudoElement", or "nsIAnonBoxPseudo" self.ty = ty # The type of atom: "Atom", "PseudoElement", "NonInheritingAnonBox", @@ -73,17 +66,14 @@ class Atom: if self.is_anon_box(): assert self.is_inheriting_anon_box() or self.is_non_inheriting_anon_box() - def cpp_class(self): - return self.source.CLASS - def gnu_symbol(self): - return gnu_symbolify(self.source, self.original_ident) + return gnu_symbolify(self.original_ident) def msvc32_symbol(self): - return msvc32_symbolify(self.source, self.original_ident, self.ty) + return msvc32_symbolify(self.original_ident, self.ty) def msvc64_symbol(self): - return msvc64_symbolify(self.source, self.original_ident, self.ty) + return msvc64_symbolify(self.original_ident, self.ty) def type(self): return self.ty @@ -109,13 +99,12 @@ class Atom: def collect_atoms(objdir): atoms = [] - for source in SOURCES: - path = os.path.abspath(os.path.join(objdir, source.FILE)) - print("cargo:rerun-if-changed={}".format(path)) - with open(path) as f: - content = f.read() - for result in source.PATTERN.finditer(content): - atoms.append(Atom(source, result.group(1), result.group(2), result.group(3), result.group(4))) + path = os.path.abspath(os.path.join(objdir, FILE)) + print("cargo:rerun-if-changed={}".format(path)) + with open(path) as f: + content = f.read() + for result in PATTERN.finditer(content): + atoms.append(Atom(result.group(1), result.group(2), result.group(3), result.group(4))) return atoms From cc1897597cc33a38ce66a6846bcc15a6ff4e001e Mon Sep 17 00:00:00 2001 From: Cameron McCormack Date: Wed, 15 Aug 2018 15:52:42 +1000 Subject: [PATCH 16/35] style: Generate static atom hash in StaticAtoms.py. Differential Revision: https://phabricator.services.mozilla.com/D3295 --- components/style/gecko/regen_atoms.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/components/style/gecko/regen_atoms.py b/components/style/gecko/regen_atoms.py index 2cb754f7e5b..13f0a8c0005 100755 --- a/components/style/gecko/regen_atoms.py +++ b/components/style/gecko/regen_atoms.py @@ -24,7 +24,8 @@ PRELUDE = """ """[1:] # NOQA: E501 -PATTERN = re.compile('^GK_ATOM\(([^,]*),[^"]*"([^"]*)",\s*([^,]*),\s*([^)]*)\)', +# Matches lines like `GK_ATOM(foo, "foo", 0x12345678, nsStaticAtom, PseudoElementAtom)`. +PATTERN = re.compile('^GK_ATOM\(([^,]*),[^"]*"([^"]*)",\s*(0x[0-9a-f]+),\s*([^,]*),\s*([^)]*)\)', re.MULTILINE) FILE = "include/nsGkAtomList.h" CLASS = "nsGkAtoms" @@ -52,10 +53,11 @@ def map_atom(ident): class Atom: - def __init__(self, ident, value, ty, atom_type): + def __init__(self, ident, value, hash, ty, atom_type): self.ident = "{}_{}".format(CLASS, ident) self.original_ident = ident self.value = value + self.hash = hash # The Gecko type: "nsStaticAtom", "nsICSSPseudoElement", or "nsIAnonBoxPseudo" self.ty = ty # The type of atom: "Atom", "PseudoElement", "NonInheritingAnonBox", @@ -104,7 +106,8 @@ def collect_atoms(objdir): with open(path) as f: content = f.read() for result in PATTERN.finditer(content): - atoms.append(Atom(result.group(1), result.group(2), result.group(3), result.group(4))) + atoms.append(Atom(result.group(1), result.group(2), result.group(3), + result.group(4), result.group(5))) return atoms From 87b1e1cdc9b380687564ddf0dc894a10e5df7221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 14 Aug 2018 10:47:25 +0200 Subject: [PATCH 17/35] style: no-op visited changes earlier if visited links are disabled. We force a repaint from ContentStateChangedInternal if visited links are disabled, and that's observable. Let's cut it off as early as we can to avoid timing attacks even when :visited is disabled. Differential Revision: https://phabricator.services.mozilla.com/D3304 --- components/style/gecko/data.rs | 48 ++++--------------- .../element/state_and_attributes.rs | 7 ++- 2 files changed, 14 insertions(+), 41 deletions(-) diff --git a/components/style/gecko/data.rs b/components/style/gecko/data.rs index 6b9005c7560..0bd938c67e8 100644 --- a/components/style/gecko/data.rs +++ b/components/style/gecko/data.rs @@ -8,7 +8,7 @@ use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; use context::QuirksMode; use dom::TElement; use gecko_bindings::bindings::{self, RawServoStyleSet}; -use gecko_bindings::structs::{self, RawGeckoPresContextOwned, ServoStyleSetSizes, StyleSheet as DomStyleSheet}; +use gecko_bindings::structs::{RawGeckoPresContextOwned, ServoStyleSetSizes, StyleSheet as DomStyleSheet}; use gecko_bindings::structs::{StyleSheetInfo, nsIDocument}; use gecko_bindings::sugar::ownership::{HasArcFFI, HasBoxFFI, HasFFI, HasSimpleFFI}; use invalidation::media_queries::{MediaListKey, ToMediaListKey}; @@ -185,52 +185,20 @@ impl PerDocumentStyleDataImpl { .flush(&StylesheetGuards::same(guard), document_element, snapshots) } - /// Returns whether private browsing is enabled. - fn is_private_browsing_enabled(&self) -> bool { - let doc = self.stylist - .device() - .pres_context() - .mDocument - .raw::(); - unsafe { bindings::Gecko_IsPrivateBrowsingEnabled(doc) } - } - - /// Returns whether the document is being used as an image. - fn is_being_used_as_an_image(&self) -> bool { - let doc = self.stylist - .device() - .pres_context() - .mDocument - .raw::(); - - unsafe { (*doc).mIsBeingUsedAsImage() } - } - /// Get the default computed values for this document. pub fn default_computed_values(&self) -> &Arc { self.stylist.device().default_computed_values_arc() } - /// Returns whether visited links are enabled. - fn visited_links_enabled(&self) -> bool { - unsafe { structs::StaticPrefs_sVarCache_layout_css_visited_links_enabled } - } - /// Returns whether visited styles are enabled. + #[inline] pub fn visited_styles_enabled(&self) -> bool { - if !self.visited_links_enabled() { - return false; - } - - if self.is_private_browsing_enabled() { - return false; - } - - if self.is_being_used_as_an_image() { - return false; - } - - true + let doc = self.stylist + .device() + .pres_context() + .mDocument + .raw::(); + unsafe { bindings::Gecko_VisitedStylesEnabled(doc) } } /// Measure heap usage. diff --git a/components/style/invalidation/element/state_and_attributes.rs b/components/style/invalidation/element/state_and_attributes.rs index 88b012a2524..0be44c1fc42 100644 --- a/components/style/invalidation/element/state_and_attributes.rs +++ b/components/style/invalidation/element/state_and_attributes.rs @@ -167,8 +167,13 @@ where // do for this case. if state_changes.intersects(ElementState::IN_VISITED_OR_UNVISITED_STATE) { trace!(" > visitedness change, force subtree restyle"); + // If we get here with visited links disabled, we should probably + // just avoid the restyle and remove the state change here, not only + // as an optimization, but also because it kind of would kill the + // point of disabling visited links. + debug_assert!(self.shared_context.visited_styles_enabled); // We can't just return here because there may also be attribute - // changes as well that imply additional hints. + // changes as well that imply additional hints for siblings. self.data.hint.insert(RestyleHint::restyle_subtree()); } From a0cb37d29d211f549a5afb7d00626d6df96b8f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 14 Aug 2018 10:49:21 +0200 Subject: [PATCH 18/35] style: Simplify visited-related code in invalidation. We match with AllLinksVisitedAndUnvisited for style invalidation, and we already do a subtree restyle because :visited matching doesn't depend on the actual element state. So all this stuff is just not needed. The comment points to the attribute tests in bug 1328509, but those still trivially pass with this change. I think this was unneeded since I introduced AllLinksVisitedAndUnvisited, or maybe since https://github.com/servo/servo/pull/19520. In any case it doesn't really matter, and I already had done this cleanup in my WIP patches for bug 1406622, but I guess this is a slightly more suitable place to land them :) Differential Revision: https://phabricator.services.mozilla.com/D3305 --- .../element/state_and_attributes.rs | 105 +++++------------- 1 file changed, 27 insertions(+), 78 deletions(-) diff --git a/components/style/invalidation/element/state_and_attributes.rs b/components/style/invalidation/element/state_and_attributes.rs index 0be44c1fc42..1eb022b298a 100644 --- a/components/style/invalidation/element/state_and_attributes.rs +++ b/components/style/invalidation/element/state_and_attributes.rs @@ -24,12 +24,6 @@ use selectors::matching::matches_selector; use smallvec::SmallVec; use stylesheets::origin::{Origin, OriginSet}; -#[derive(Debug, PartialEq)] -enum VisitedDependent { - Yes, - No, -} - /// The collector implementation. struct Collector<'a, 'b: 'a, 'selectors: 'a, E> where @@ -352,7 +346,7 @@ where if let Some(ref id) = removed_id { if let Some(deps) = map.id_to_selector.get(id, quirks_mode) { for dep in deps { - self.scan_dependency(dep, VisitedDependent::No); + self.scan_dependency(dep); } } } @@ -361,7 +355,7 @@ where if let Some(ref id) = added_id { if let Some(deps) = map.id_to_selector.get(id, quirks_mode) { for dep in deps { - self.scan_dependency(dep, VisitedDependent::No); + self.scan_dependency(dep); } } } @@ -369,7 +363,7 @@ where for class in self.classes_added.iter().chain(self.classes_removed.iter()) { if let Some(deps) = map.class_to_selector.get(class, quirks_mode) { for dep in deps { - self.scan_dependency(dep, VisitedDependent::No); + self.scan_dependency(dep); } } } @@ -395,7 +389,7 @@ where self.removed_id, self.classes_removed, |dependency| { - self.scan_dependency(dependency, VisitedDependent::No); + self.scan_dependency(dependency); true }, ); @@ -415,98 +409,53 @@ where if !dependency.state.intersects(state_changes) { return true; } - let visited_dependent = if dependency - .state - .intersects(ElementState::IN_VISITED_OR_UNVISITED_STATE) - { - VisitedDependent::Yes - } else { - VisitedDependent::No - }; - self.scan_dependency(&dependency.dep, visited_dependent); + self.scan_dependency(&dependency.dep); true }, ); } - /// Check whether a dependency should be taken into account, using a given - /// visited handling mode. + /// Check whether a dependency should be taken into account. fn check_dependency( &mut self, - visited_handling_mode: VisitedHandlingMode, dependency: &Dependency, ) -> bool { let element = &self.element; let wrapper = &self.wrapper; - self.matching_context - .with_visited_handling_mode(visited_handling_mode, |mut context| { - let matches_now = matches_selector( - &dependency.selector, - dependency.selector_offset, - None, - element, - &mut context, - &mut |_, _| {}, - ); + let matches_now = matches_selector( + &dependency.selector, + dependency.selector_offset, + None, + element, + &mut self.matching_context, + &mut |_, _| {}, + ); - let matched_then = matches_selector( - &dependency.selector, - dependency.selector_offset, - None, - wrapper, - &mut context, - &mut |_, _| {}, - ); + let matched_then = matches_selector( + &dependency.selector, + dependency.selector_offset, + None, + wrapper, + &mut self.matching_context, + &mut |_, _| {}, + ); - matched_then != matches_now - }) + matched_then != matches_now } - fn scan_dependency( - &mut self, - dependency: &'selectors Dependency, - is_visited_dependent: VisitedDependent, - ) { + fn scan_dependency(&mut self, dependency: &'selectors Dependency) { debug!( - "TreeStyleInvalidator::scan_dependency({:?}, {:?}, {:?})", - self.element, dependency, is_visited_dependent, + "TreeStyleInvalidator::scan_dependency({:?}, {:?})", + self.element, dependency ); if !self.dependency_may_be_relevant(dependency) { return; } - let should_account_for_dependency = - self.check_dependency(VisitedHandlingMode::AllLinksVisitedAndUnvisited, dependency); - - if should_account_for_dependency { + if self.check_dependency(dependency) { return self.note_dependency(dependency); } - - // If there is a relevant link, then we also matched in visited - // mode. - // - // Match again in this mode to ensure this also matches. - // - // Note that we never actually match directly against the element's true - // visited state at all, since that would expose us to timing attacks. - // - // The matching process only considers the relevant link state and - // visited handling mode when deciding if visited matches. Instead, we - // are rematching here in case there is some :visited selector whose - // matching result changed for some other state or attribute change of - // this element (for example, for things like [foo]:visited). - // - // NOTE: This thing is actually untested because testing it is flaky, - // see the tests that were added and then backed out in bug 1328509. - if is_visited_dependent == VisitedDependent::Yes && self.element.is_link() { - let should_account_for_dependency = - self.check_dependency(VisitedHandlingMode::RelevantLinkVisited, dependency); - - if should_account_for_dependency { - return self.note_dependency(dependency); - } - } } fn note_dependency(&mut self, dependency: &'selectors Dependency) { From dc0f937224ad21421270f8fddc9627931d2a19b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Wed, 15 Aug 2018 16:07:11 +0200 Subject: [PATCH 19/35] style: Rewrite media queries so that they work on an evaluator function. This moves most of the code to be Rust, except potentially some evaluator functions, and allows to unblock the use case from any-hover / any-pointer and remove nsMediaFeatures. Differential Revision: https://phabricator.services.mozilla.com/D2976 --- components/style/Cargo.toml | 1 + components/style/cbindgen.toml | 2 +- components/style/gecko/media_features.rs | 610 ++++++++++++++ components/style/gecko/media_queries.rs | 788 +----------------- components/style/gecko/mod.rs | 1 + components/style/lib.rs | 11 +- .../style/media_queries/media_condition.rs | 1 - .../style/media_queries/media_feature.rs | 172 ++++ .../media_queries/media_feature_expression.rs | 562 +++++++++++++ components/style/media_queries/mod.rs | 8 +- 10 files changed, 1374 insertions(+), 782 deletions(-) create mode 100644 components/style/gecko/media_features.rs create mode 100644 components/style/media_queries/media_feature.rs create mode 100644 components/style/media_queries/media_feature_expression.rs diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index 40fcc390228..d673dc5c610 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -49,6 +49,7 @@ matches = "0.1" num_cpus = {version = "1.1.0", optional = true} num-integer = "0.1.32" num-traits = "0.2" +num-derive = "0.2" new-ordered-float = "1.0" owning_ref = "0.3.3" parking_lot = "0.6" diff --git a/components/style/cbindgen.toml b/components/style/cbindgen.toml index 86c3dddec4c..5a0e5e19ea7 100644 --- a/components/style/cbindgen.toml +++ b/components/style/cbindgen.toml @@ -22,5 +22,5 @@ derive_helper_methods = true [export] prefix = "Style" -include = ["StyleDisplay", "StyleAppearance"] +include = ["StyleDisplay", "StyleAppearance", "StyleDisplayMode"] item_types = ["enums"] diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs new file mode 100644 index 00000000000..822a64d738b --- /dev/null +++ b/components/style/gecko/media_features.rs @@ -0,0 +1,610 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +//! Gecko's media feature list and evaluator. + +use Atom; +use app_units::Au; +use euclid::Size2D; +use gecko_bindings::bindings; +use values::computed::CSSPixelLength; +use values::computed::Resolution; + +use media_queries::Device; +use media_queries::media_feature::{MediaFeatureDescription, Evaluator}; +use media_queries::media_feature::{AllowsRanges, ParsingRequirements}; +use media_queries::media_feature_expression::{AspectRatio, RangeOrOperator}; + +macro_rules! feature { + ($name:expr, $allows_ranges:expr, $evaluator:expr, $reqs:expr,) => { + MediaFeatureDescription { + name: $name, + allows_ranges: $allows_ranges, + evaluator: $evaluator, + requirements: $reqs, + } + } +} + +fn viewport_size(device: &Device) -> Size2D { + let pc = device.pres_context(); + if pc.mIsRootPaginatedDocument() != 0 { + // We want the page size, including unprintable areas and margins. + // FIXME(emilio, bug 1414600): Not quite! + let area = &pc.mPageSize; + return Size2D::new(Au(area.width), Au(area.height)) + } + device.au_viewport_size() +} + +fn device_size(device: &Device) -> Size2D { + let mut width = 0; + let mut height = 0; + unsafe { + bindings::Gecko_MediaFeatures_GetDeviceSize( + device.document(), + &mut width, + &mut height, + ); + } + Size2D::new(Au(width), Au(height)) +} + +fn eval_width( + device: &Device, + value: Option, + range_or_operator: Option, +) -> bool { + RangeOrOperator::evaluate( + range_or_operator, + value.map(Au::from), + viewport_size(device).width, + ) +} + +fn eval_device_width( + device: &Device, + value: Option, + range_or_operator: Option, +) -> bool { + RangeOrOperator::evaluate( + range_or_operator, + value.map(Au::from), + device_size(device).width, + ) +} + +fn eval_height( + device: &Device, + value: Option, + range_or_operator: Option, +) -> bool { + RangeOrOperator::evaluate( + range_or_operator, + value.map(Au::from), + viewport_size(device).height, + ) +} + +fn eval_device_height( + device: &Device, + value: Option, + range_or_operator: Option, +) -> bool { + RangeOrOperator::evaluate( + range_or_operator, + value.map(Au::from), + device_size(device).height, + ) +} + +fn eval_aspect_ratio_for( + device: &Device, + query_value: Option, + range_or_operator: Option, + get_size: F, +) -> bool +where + F: FnOnce(&Device) -> Size2D, +{ + let query_value = match query_value { + Some(v) => v, + None => return true, + }; + + let size = get_size(device); + RangeOrOperator::evaluate( + range_or_operator, + Some(size.height.0 as u64 * query_value.0 as u64), + size.width.0 as u64 * query_value.1 as u64, + ) +} + +fn eval_aspect_ratio( + device: &Device, + query_value: Option, + range_or_operator: Option, +) -> bool { + eval_aspect_ratio_for(device, query_value, range_or_operator, viewport_size) +} + +fn eval_device_aspect_ratio( + device: &Device, + query_value: Option, + range_or_operator: Option, +) -> bool { + eval_aspect_ratio_for(device, query_value, range_or_operator, device_size) +} + +fn eval_device_pixel_ratio( + device: &Device, + query_value: Option, + range_or_operator: Option, +) -> bool { + let ratio = unsafe { + bindings::Gecko_MediaFeatures_GetDevicePixelRatio(device.document()) + }; + + RangeOrOperator::evaluate( + range_or_operator, + query_value, + ratio, + ) +} + +#[derive(Debug, Copy, Clone, FromPrimitive, ToCss, Parse)] +#[repr(u8)] +enum Orientation { + Landscape, + Portrait, +} + +fn eval_orientation_for( + device: &Device, + value: Option, + get_size: F, +) -> bool +where + F: FnOnce(&Device) -> Size2D, +{ + let query_orientation = match value { + Some(v) => v, + None => return true, + }; + + let size = get_size(device); + + // Per spec, square viewports should be 'portrait' + let is_landscape = size.width > size.height; + match query_orientation { + Orientation::Landscape => is_landscape, + Orientation::Portrait => !is_landscape, + } +} + +fn eval_orientation( + device: &Device, + value: Option, +) -> bool { + eval_orientation_for(device, value, viewport_size) +} + +fn eval_device_orientation( + device: &Device, + value: Option, +) -> bool { + eval_orientation_for(device, value, device_size) +} + +/// Values for the display-mode media feature. +#[derive(Debug, Copy, Clone, FromPrimitive, ToCss, Parse)] +#[repr(u8)] +#[allow(missing_docs)] +pub enum DisplayMode { + Browser = 0, + MinimalUi, + Standalone, + Fullscreen, +} + +fn eval_display_mode( + device: &Device, + query_value: Option, +) -> bool { + let query_value = match query_value { + Some(v) => v, + None => return true, + }; + + let gecko_display_mode = unsafe { + bindings::Gecko_MediaFeatures_GetDisplayMode(device.document()) + }; + + // NOTE: cbindgen guarantees the same representation. + gecko_display_mode as u8 == query_value as u8 +} + +fn eval_grid(_: &Device, query_value: Option, _: Option) -> bool { + // Gecko doesn't support grid devices (e.g., ttys), so the 'grid' feature + // is always 0. + let supports_grid = false; + query_value.map_or(supports_grid, |v| v == supports_grid) +} + +fn eval_transform_3d( + _: &Device, + query_value: Option, + _: Option, +) -> bool { + let supports_transforms = true; + query_value.map_or(supports_transforms, |v| v == supports_transforms) +} + +#[derive(Debug, Copy, Clone, FromPrimitive, ToCss, Parse)] +#[repr(u8)] +enum Scan { + Progressive, + Interlace, +} + +fn eval_scan(_: &Device, _: Option) -> bool { + // Since Gecko doesn't support the 'tv' media type, the 'scan' feature never + // matches. + false +} + +fn eval_color( + device: &Device, + query_value: Option, + range_or_operator: Option, +) -> bool { + let color_bits_per_channel = + unsafe { bindings::Gecko_MediaFeatures_GetColorDepth(device.document()) }; + RangeOrOperator::evaluate( + range_or_operator, + query_value, + color_bits_per_channel, + ) +} + +fn eval_color_index( + _: &Device, + query_value: Option, + range_or_operator: Option, +) -> bool { + // We should return zero if the device does not use a color lookup + // table. + let index = 0; + RangeOrOperator::evaluate( + range_or_operator, + query_value, + index, + ) +} + +fn eval_monochrome( + _: &Device, + query_value: Option, + range_or_operator: Option, +) -> bool { + // For color devices we should return 0. + // FIXME: On a monochrome device, return the actual color depth, not 0! + let depth = 0; + RangeOrOperator::evaluate( + range_or_operator, + query_value, + depth, + ) +} + +fn eval_resolution( + device: &Device, + query_value: Option, + range_or_operator: Option, +) -> bool { + let resolution_dppx = + unsafe { bindings::Gecko_MediaFeatures_GetResolution(device.document()) }; + RangeOrOperator::evaluate( + range_or_operator, + query_value.map(|r| r.dppx()), + resolution_dppx, + ) +} + +#[derive(Debug, Copy, Clone, FromPrimitive, ToCss, Parse)] +#[repr(u8)] +enum PrefersReducedMotion { + NoPreference, + Reduce, +} + +fn eval_prefers_reduced_motion( + device: &Device, + query_value: Option, +) -> bool { + let prefers_reduced = + unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(device.document()) }; + let query_value = match query_value { + Some(v) => v, + None => return prefers_reduced, + }; + + match query_value { + PrefersReducedMotion::NoPreference => !prefers_reduced, + PrefersReducedMotion::Reduce => prefers_reduced, + } +} + +fn eval_moz_is_glyph( + device: &Device, + query_value: Option, + _: Option, +) -> bool { + let is_glyph = unsafe { (*device.document()).mIsSVGGlyphsDocument() }; + query_value.map_or(is_glyph, |v| v == is_glyph) +} + +fn eval_moz_is_resource_document( + device: &Device, + query_value: Option, + _: Option, +) -> bool { + let is_resource_doc = unsafe { + bindings::Gecko_MediaFeatures_IsResourceDocument(device.document()) + }; + query_value.map_or(is_resource_doc, |v| v == is_resource_doc) +} + +fn eval_system_metric( + device: &Device, + query_value: Option, + metric: Atom, + accessible_from_content: bool, +) -> bool { + let supports_metric = unsafe { + bindings::Gecko_MediaFeatures_HasSystemMetric( + device.document(), + metric.as_ptr(), + accessible_from_content, + ) + }; + query_value.map_or(supports_metric, |v| v == supports_metric) +} + +fn eval_moz_touch_enabled( + device: &Device, + query_value: Option, + _: Option, +) -> bool { + eval_system_metric( + device, + query_value, + atom!("touch-enabled"), + /* accessible_from_content = */ true, + ) +} + +fn eval_moz_os_version( + device: &Device, + query_value: Option, + _: Option, +) -> bool { + let query_value = match query_value { + Some(v) => v, + None => return false, + }; + + let os_version = unsafe { + bindings::Gecko_MediaFeatures_GetOperatingSystemVersion(device.document()) + }; + + query_value.as_ptr() == os_version +} + +macro_rules! system_metric_feature { + ($feature_name:expr, $metric_name:expr) => { + { + fn __eval( + device: &Device, + query_value: Option, + _: Option, + ) -> bool { + eval_system_metric( + device, + query_value, + $metric_name, + /* accessible_from_content = */ false, + ) + } + + feature!( + $feature_name, + AllowsRanges::No, + Evaluator::BoolInteger(__eval), + ParsingRequirements::CHROME_AND_UA_ONLY, + ) + } + } +} + +lazy_static! { + /// Adding new media features requires (1) adding the new feature to this + /// array, with appropriate entries (and potentially any new code needed + /// to support new types in these entries and (2) ensuring that either + /// nsPresContext::MediaFeatureValuesChanged is called when the value that + /// would be returned by the evaluator function could change. + pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 43] = [ + feature!( + atom!("width"), + AllowsRanges::Yes, + Evaluator::Length(eval_width), + ParsingRequirements::empty(), + ), + feature!( + atom!("height"), + AllowsRanges::Yes, + Evaluator::Length(eval_height), + ParsingRequirements::empty(), + ), + feature!( + atom!("aspect-ratio"), + AllowsRanges::Yes, + Evaluator::IntRatio(eval_aspect_ratio), + ParsingRequirements::empty(), + ), + feature!( + atom!("orientation"), + AllowsRanges::No, + keyword_evaluator!(eval_orientation, Orientation), + ParsingRequirements::empty(), + ), + feature!( + atom!("device-width"), + AllowsRanges::Yes, + Evaluator::Length(eval_device_width), + ParsingRequirements::empty(), + ), + feature!( + atom!("device-height"), + AllowsRanges::Yes, + Evaluator::Length(eval_device_height), + ParsingRequirements::empty(), + ), + feature!( + atom!("device-aspect-ratio"), + AllowsRanges::Yes, + Evaluator::IntRatio(eval_device_aspect_ratio), + ParsingRequirements::empty(), + ), + feature!( + atom!("-moz-device-orientation"), + AllowsRanges::No, + keyword_evaluator!(eval_device_orientation, Orientation), + ParsingRequirements::empty(), + ), + // Webkit extensions that we support for de-facto web compatibility. + // -webkit-{min|max}-device-pixel-ratio (controlled with its own pref): + feature!( + atom!("device-pixel-ratio"), + AllowsRanges::Yes, + Evaluator::Float(eval_device_pixel_ratio), + ParsingRequirements::WEBKIT_PREFIX | + ParsingRequirements::WEBKIT_DEVICE_PIXEL_RATIO_PREF_ENABLED, + ), + // -webkit-transform-3d. + feature!( + atom!("transform-3d"), + AllowsRanges::No, + Evaluator::BoolInteger(eval_transform_3d), + ParsingRequirements::WEBKIT_PREFIX, + ), + feature!( + atom!("-moz-device-pixel-ratio"), + AllowsRanges::Yes, + Evaluator::Float(eval_device_pixel_ratio), + ParsingRequirements::empty(), + ), + feature!( + atom!("resolution"), + AllowsRanges::Yes, + Evaluator::Resolution(eval_resolution), + ParsingRequirements::empty(), + ), + feature!( + atom!("display-mode"), + AllowsRanges::No, + keyword_evaluator!(eval_display_mode, DisplayMode), + ParsingRequirements::empty(), + ), + feature!( + atom!("grid"), + AllowsRanges::No, + Evaluator::BoolInteger(eval_grid), + ParsingRequirements::empty(), + ), + feature!( + atom!("scan"), + AllowsRanges::No, + keyword_evaluator!(eval_scan, Scan), + ParsingRequirements::empty(), + ), + feature!( + atom!("color"), + AllowsRanges::Yes, + Evaluator::Integer(eval_color), + ParsingRequirements::empty(), + ), + feature!( + atom!("color-index"), + AllowsRanges::Yes, + Evaluator::Integer(eval_color_index), + ParsingRequirements::empty(), + ), + feature!( + atom!("monochrome"), + AllowsRanges::Yes, + Evaluator::Integer(eval_monochrome), + ParsingRequirements::empty(), + ), + feature!( + atom!("prefers-reduced-motion"), + AllowsRanges::No, + keyword_evaluator!(eval_prefers_reduced_motion, PrefersReducedMotion), + ParsingRequirements::empty(), + ), + + // Internal -moz-is-glyph media feature: applies only inside SVG glyphs. + // Internal because it is really only useful in the user agent anyway + // and therefore not worth standardizing. + feature!( + atom!("-moz-is-glyph"), + AllowsRanges::No, + Evaluator::BoolInteger(eval_moz_is_glyph), + ParsingRequirements::CHROME_AND_UA_ONLY, + ), + feature!( + atom!("-moz-is-resource-document"), + AllowsRanges::No, + Evaluator::BoolInteger(eval_moz_is_resource_document), + ParsingRequirements::CHROME_AND_UA_ONLY, + ), + feature!( + atom!("-moz-os-version"), + AllowsRanges::No, + Evaluator::Ident(eval_moz_os_version), + ParsingRequirements::CHROME_AND_UA_ONLY, + ), + // FIXME(emilio): make system metrics store the -moz- atom, and remove + // some duplication here. + system_metric_feature!(atom!("-moz-scrollbar-start-backward"), atom!("scrollbar-start-backward")), + system_metric_feature!(atom!("-moz-scrollbar-start-forward"), atom!("scrollbar-start-forward")), + system_metric_feature!(atom!("-moz-scrollbar-end-backward"), atom!("scrollbar-end-backward")), + system_metric_feature!(atom!("-moz-scrollbar-end-forward"), atom!("scrollbar-end-forward")), + system_metric_feature!(atom!("-moz-scrollbar-thumb-proportional"), atom!("scrollbar-thumb-proportional")), + system_metric_feature!(atom!("-moz-overlay-scrollbars"), atom!("overlay-scrollbars")), + system_metric_feature!(atom!("-moz-windows-default-theme"), atom!("windows-default-theme")), + system_metric_feature!(atom!("-moz-mac-graphite-theme"), atom!("mac-graphite-theme")), + system_metric_feature!(atom!("-moz-mac-yosemite-theme"), atom!("mac-yosemite-theme")), + system_metric_feature!(atom!("-moz-windows-accent-color-in-titlebar"), atom!("windows-accent-color-in-titlebar")), + system_metric_feature!(atom!("-moz-windows-compositor"), atom!("windows-compositor")), + system_metric_feature!(atom!("-moz-windows-classic"), atom!("windows-classic")), + system_metric_feature!(atom!("-moz-windows-glass"), atom!("windows-glass")), + system_metric_feature!(atom!("-moz-menubar-drag"), atom!("menubar-drag")), + system_metric_feature!(atom!("-moz-swipe-animation-enabled"), atom!("swipe-animation-enabled")), + system_metric_feature!(atom!("-moz-gtk-csd-available"), atom!("gtk-csd-available")), + system_metric_feature!(atom!("-moz-gtk-csd-minimize-button"), atom!("gtk-csd-minimize-button")), + system_metric_feature!(atom!("-moz-gtk-csd-maximize-button"), atom!("gtk-csd-maximize-button")), + system_metric_feature!(atom!("-moz-gtk-csd-close-button"), atom!("gtk-csd-close-button")), + system_metric_feature!(atom!("-moz-system-dark-theme"), atom!("system-dark-theme")), + // This is the only system-metric media feature that's accessible to + // content as of today. + // FIXME(emilio): Restrict (or remove?) when bug 1035774 lands. + feature!( + atom!("-moz-touch-enabled"), + AllowsRanges::No, + Evaluator::BoolInteger(eval_moz_touch_enabled), + ParsingRequirements::empty(), + ), + ]; +} diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index ff4ca2b47cd..c41bc4ffd73 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -6,34 +6,23 @@ use app_units::AU_PER_PX; use app_units::Au; -use context::QuirksMode; -use cssparser::{Parser, RGBA, Token}; +use cssparser::RGBA; use euclid::Size2D; use euclid::TypedScale; use gecko::values::{convert_nscolor_to_rgba, convert_rgba_to_nscolor}; use gecko_bindings::bindings; use gecko_bindings::structs; -use gecko_bindings::structs::{nsCSSKTableEntry, nsCSSKeyword, nsCSSUnit, nsCSSValue}; -use gecko_bindings::structs::{nsMediaFeature, nsMediaFeature_RangeType}; -use gecko_bindings::structs::{nsMediaFeature_ValueType, nsPresContext}; -use gecko_bindings::structs::RawGeckoPresContextOwned; -use gecko_bindings::structs::nsCSSKeywordAndBoolTableEntry; +use gecko_bindings::structs::{nsPresContext, RawGeckoPresContextOwned}; use media_queries::MediaType; -use parser::{Parse, ParserContext}; use properties::ComputedValues; use servo_arc::Arc; -use std::fmt::{self, Write}; +use std::fmt; use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering}; -use str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase}; use string_cache::Atom; -use style_traits::{CSSPixel, CssWriter, DevicePixel}; -use style_traits::{ParseError, StyleParseErrorKind, ToCss}; +use style_traits::{CSSPixel, DevicePixel}; use style_traits::viewport::ViewportConstraints; -use stylesheets::Origin; -use values::{serialize_atom_identifier, CSSFloat, CustomIdent, KeyframesName}; -use values::computed::{self, ToComputedValue}; +use values::{CustomIdent, KeyframesName}; use values::computed::font::FontSize; -use values::specified::{Integer, Length, Number, Resolution}; /// The `Device` in Gecko wraps a pres context, has a default values computed, /// and contains all the viewport rule state. @@ -71,11 +60,8 @@ impl fmt::Debug for Device { let mut doc_uri = nsCString::new(); unsafe { - let doc = - &*self.pres_context().mDocument.raw::(); - bindings::Gecko_nsIURI_Debug( - doc.mDocumentURI.raw::(), + (*self.document()).mDocumentURI.raw::(), &mut doc_uri, ) }; @@ -157,10 +143,17 @@ impl Device { } /// Gets the pres context associated with this document. + #[inline] pub fn pres_context(&self) -> &nsPresContext { unsafe { &*self.pres_context } } + /// Gets the document pointer. + #[inline] + pub fn document(&self) -> *mut structs::nsIDocument { + self.pres_context().mDocument.raw::() + } + /// Recreates the default computed values. pub fn reset_computed_values(&mut self) { self.default_values = ComputedValues::default_values(self.pres_context()); @@ -248,758 +241,3 @@ impl Device { size.scale_by(1. / self.pres_context().mEffectiveTextZoom) } } - -/// The kind of matching that should be performed on a media feature value. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] -pub enum Range { - /// At least the specified value. - Min, - /// At most the specified value. - Max, -} - -/// The operator that was specified in this media feature. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] -enum Operator { - Equal, - GreaterThan, - GreaterThanEqual, - LessThan, - LessThanEqual, -} - -impl ToCss for Operator { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: fmt::Write, - { - dest.write_str(match *self { - Operator::Equal => "=", - Operator::LessThan => "<", - Operator::LessThanEqual => "<=", - Operator::GreaterThan => ">", - Operator::GreaterThanEqual => ">=", - }) - } -} - -/// Either a `Range` or an `Operator`. -/// -/// Ranged media features are not allowed with operations (that'd make no -/// sense). -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] -enum RangeOrOperator { - Range(Range), - Operator(Operator), -} - -/// A feature expression for gecko contains a reference to the media feature, -/// the value the media query contained, and the range to evaluate. -#[derive(Clone, Debug, MallocSizeOf)] -pub struct MediaFeatureExpression { - feature: &'static nsMediaFeature, - value: Option, - range_or_operator: Option, -} - -impl ToCss for MediaFeatureExpression { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: fmt::Write, - { - dest.write_str("(")?; - - if (self.feature.mReqFlags & structs::nsMediaFeature_RequirementFlags_eHasWebkitPrefix) != 0 - { - dest.write_str("-webkit-")?; - } - - if let Some(RangeOrOperator::Range(range)) = self.range_or_operator { - match range { - Range::Min => dest.write_str("min-")?, - Range::Max => dest.write_str("max-")?, - } - } - - // NB: CssStringWriter not needed, feature names are under control. - write!(dest, "{}", unsafe { - Atom::from_static(*self.feature.mName) - })?; - - if let Some(RangeOrOperator::Operator(op)) = self.range_or_operator { - dest.write_char(' ')?; - op.to_css(dest)?; - dest.write_char(' ')?; - } else if self.value.is_some() { - dest.write_str(": ")?; - } - - if let Some(ref val) = self.value { - val.to_css(dest, self)?; - } - - dest.write_str(")") - } -} - -impl PartialEq for MediaFeatureExpression { - fn eq(&self, other: &Self) -> bool { - self.feature.mName == other.feature.mName && self.value == other.value && - self.range_or_operator == other.range_or_operator - } -} - -/// A value found or expected in a media expression. -/// -/// FIXME(emilio): How should calc() serialize in the Number / Integer / -/// BoolInteger / IntRatio case, as computed or as specified value? -/// -/// If the first, this would need to store the relevant values. -/// -/// See: https://github.com/w3c/csswg-drafts/issues/1968 -#[derive(Clone, Debug, MallocSizeOf, PartialEq)] -pub enum MediaExpressionValue { - /// A length. - Length(Length), - /// A (non-negative) integer. - Integer(u32), - /// A floating point value. - Float(CSSFloat), - /// A boolean value, specified as an integer (i.e., either 0 or 1). - BoolInteger(bool), - /// Two integers separated by '/', with optional whitespace on either side - /// of the '/'. - IntRatio(u32, u32), - /// A resolution. - Resolution(Resolution), - /// An enumerated value, defined by the variant keyword table in the - /// feature's `mData` member. - Enumerated(i16), - /// Similar to the above Enumerated but the variant keyword table has an - /// additional field what the keyword value means in the Boolean Context. - BoolEnumerated(i16), - /// An identifier. - Ident(Atom), -} - -impl MediaExpressionValue { - fn from_css_value( - for_expr: &MediaFeatureExpression, - css_value: &nsCSSValue, - ) -> Option { - // NB: If there's a null value, that means that we don't support the - // feature. - if css_value.mUnit == nsCSSUnit::eCSSUnit_Null { - return None; - } - - match for_expr.feature.mValueType { - nsMediaFeature_ValueType::eLength => { - debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_Pixel); - let pixels = css_value.float_unchecked(); - Some(MediaExpressionValue::Length(Length::from_px(pixels))) - }, - nsMediaFeature_ValueType::eInteger => { - let i = css_value.integer_unchecked(); - debug_assert!(i >= 0); - Some(MediaExpressionValue::Integer(i as u32)) - }, - nsMediaFeature_ValueType::eFloat => { - debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_Number); - Some(MediaExpressionValue::Float(css_value.float_unchecked())) - }, - nsMediaFeature_ValueType::eBoolInteger => { - debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_Integer); - let i = css_value.integer_unchecked(); - debug_assert!(i == 0 || i == 1); - Some(MediaExpressionValue::BoolInteger(i == 1)) - }, - nsMediaFeature_ValueType::eResolution => { - debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_Pixel); - Some(MediaExpressionValue::Resolution(Resolution::Dppx( - css_value.float_unchecked(), - ))) - }, - nsMediaFeature_ValueType::eEnumerated => { - let value = css_value.integer_unchecked() as i16; - Some(MediaExpressionValue::Enumerated(value)) - }, - nsMediaFeature_ValueType::eBoolEnumerated => { - let value = css_value.integer_unchecked() as i16; - Some(MediaExpressionValue::BoolEnumerated(value)) - }, - nsMediaFeature_ValueType::eIdent => { - debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_AtomIdent); - Some(MediaExpressionValue::Ident(unsafe { - Atom::from_raw(*css_value.mValue.mAtom.as_ref()) - })) - }, - nsMediaFeature_ValueType::eIntRatio => { - let array = unsafe { css_value.array_unchecked() }; - debug_assert_eq!(array.len(), 2); - let first = array[0].integer_unchecked(); - let second = array[1].integer_unchecked(); - - debug_assert!(first >= 0 && second >= 0); - Some(MediaExpressionValue::IntRatio(first as u32, second as u32)) - }, - } - } -} - -impl MediaExpressionValue { - fn to_css(&self, dest: &mut CssWriter, for_expr: &MediaFeatureExpression) -> fmt::Result - where - W: fmt::Write, - { - match *self { - MediaExpressionValue::Length(ref l) => l.to_css(dest), - MediaExpressionValue::Integer(v) => v.to_css(dest), - MediaExpressionValue::Float(v) => v.to_css(dest), - MediaExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }), - MediaExpressionValue::IntRatio(a, b) => { - a.to_css(dest)?; - dest.write_char('/')?; - b.to_css(dest) - }, - MediaExpressionValue::Resolution(ref r) => r.to_css(dest), - MediaExpressionValue::Ident(ref ident) => serialize_atom_identifier(ident, dest), - MediaExpressionValue::Enumerated(value) => unsafe { - let keyword = find_in_table( - *for_expr.feature.mData.mKeywordTable.as_ref(), - |_kw, val| val == value, - |e| e.keyword(), - ).expect("Value not found in the keyword table?"); - - MediaExpressionValue::keyword_to_css(keyword, dest) - }, - MediaExpressionValue::BoolEnumerated(value) => unsafe { - let keyword = find_in_table( - *for_expr.feature.mData.mKeywordAndBoolTable.as_ref(), - |_kw, val| val == value, - |e| e.keyword(), - ).expect("Value not found in the keyword table?"); - - MediaExpressionValue::keyword_to_css(keyword, dest) - } - } - } - - fn keyword_to_css(keyword: nsCSSKeyword, dest: &mut CssWriter) -> fmt::Result - where - W: fmt::Write, - { - use std::{slice, str}; - use std::os::raw::c_char; - - // NB: All the keywords on nsMediaFeatures are static, - // well-formed utf-8. - let mut length = 0; - unsafe { - let buffer: *const c_char = bindings::Gecko_CSSKeywordString(keyword, &mut length); - let buffer = slice::from_raw_parts(buffer as *const u8, length as usize); - - let string = str::from_utf8_unchecked(buffer); - - dest.write_str(string) - } - } -} - -fn find_feature(mut f: F) -> Option<&'static nsMediaFeature> -where - F: FnMut(&'static nsMediaFeature) -> bool, -{ - unsafe { - let mut features = structs::nsMediaFeatures_features.as_ptr(); - while !(*features).mName.is_null() { - if f(&*features) { - return Some(&*features); - } - features = features.offset(1); - } - } - None -} - -trait TableEntry { - fn value(&self) -> i16; - fn keyword(&self) -> nsCSSKeyword; -} - -impl TableEntry for nsCSSKTableEntry { - fn value(&self) -> i16 { - self.mValue - } - fn keyword(&self) -> nsCSSKeyword { - self.mKeyword - } -} - -impl TableEntry for nsCSSKeywordAndBoolTableEntry { - fn value(&self) -> i16 { - self._base.mValue - } - fn keyword(&self) -> nsCSSKeyword { - self._base.mKeyword - } -} - -unsafe fn find_in_table( - current_entry: *const T, - find: FindFunc, - result_func: ResultFunc, -) -> Option -where - T: TableEntry, - FindFunc: Fn(nsCSSKeyword, i16) -> bool, - ResultFunc: Fn(&T) -> R, -{ - let mut current_entry = current_entry; - - loop { - let value = (*current_entry).value(); - let keyword = (*current_entry).keyword(); - - if value == -1 { - return None; // End of the table. - } - - if find(keyword, value) { - return Some(result_func(&*current_entry)); - } - - current_entry = current_entry.offset(1); - } -} - -fn parse_feature_value<'i, 't>( - feature: &nsMediaFeature, - feature_value_type: nsMediaFeature_ValueType, - context: &ParserContext, - input: &mut Parser<'i, 't>, -) -> Result> { - let value = match feature_value_type { - nsMediaFeature_ValueType::eLength => { - let length = Length::parse_non_negative(context, input)?; - MediaExpressionValue::Length(length) - }, - nsMediaFeature_ValueType::eInteger => { - let integer = Integer::parse_non_negative(context, input)?; - MediaExpressionValue::Integer(integer.value() as u32) - }, - nsMediaFeature_ValueType::eBoolInteger => { - let integer = Integer::parse_non_negative(context, input)?; - let value = integer.value(); - if value > 1 { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - MediaExpressionValue::BoolInteger(value == 1) - }, - nsMediaFeature_ValueType::eFloat => { - let number = Number::parse(context, input)?; - MediaExpressionValue::Float(number.get()) - }, - nsMediaFeature_ValueType::eIntRatio => { - let a = Integer::parse_positive(context, input)?; - input.expect_delim('/')?; - let b = Integer::parse_positive(context, input)?; - MediaExpressionValue::IntRatio(a.value() as u32, b.value() as u32) - }, - nsMediaFeature_ValueType::eResolution => { - MediaExpressionValue::Resolution(Resolution::parse(context, input)?) - }, - nsMediaFeature_ValueType::eEnumerated => { - let first_table_entry: *const nsCSSKTableEntry = - unsafe { *feature.mData.mKeywordTable.as_ref() }; - - let value = parse_keyword(input, first_table_entry)?; - - MediaExpressionValue::Enumerated(value) - }, - nsMediaFeature_ValueType::eBoolEnumerated => { - let first_table_entry: *const nsCSSKeywordAndBoolTableEntry = - unsafe { *feature.mData.mKeywordAndBoolTable.as_ref() }; - - let value = parse_keyword(input, first_table_entry)?; - - MediaExpressionValue::BoolEnumerated(value) - }, - nsMediaFeature_ValueType::eIdent => { - MediaExpressionValue::Ident(Atom::from(input.expect_ident()?.as_ref())) - }, - }; - - Ok(value) -} - -/// Parse a keyword and returns the corresponding i16 value. -fn parse_keyword<'i, 't, T>( - input: &mut Parser<'i, 't>, - first_table_entry: *const T, -) -> Result> -where - T: TableEntry, -{ - let location = input.current_source_location(); - let keyword = input.expect_ident()?; - let keyword = unsafe { - bindings::Gecko_LookupCSSKeyword(keyword.as_bytes().as_ptr(), keyword.len() as u32) - }; - - let value = unsafe { - find_in_table(first_table_entry, |kw, _| kw == keyword, |e| e.value()) - }; - - match value { - Some(value) => Ok(value), - None => Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)), - } -} - -/// Consumes an operation or a colon, or returns an error. -fn consume_operation_or_colon( - input: &mut Parser, -) -> Result, ()> { - let first_delim = { - let next_token = match input.next() { - Ok(t) => t, - Err(..) => return Err(()), - }; - - match *next_token { - Token::Colon => return Ok(None), - Token::Delim(oper) => oper, - _ => return Err(()), - } - }; - Ok(Some(match first_delim { - '=' => Operator::Equal, - '>' => { - if input.try(|i| i.expect_delim('=')).is_ok() { - Operator::GreaterThanEqual - } else { - Operator::GreaterThan - } - } - '<' => { - if input.try(|i| i.expect_delim('=')).is_ok() { - Operator::LessThanEqual - } else { - Operator::LessThan - } - } - _ => return Err(()), - })) -} - -impl MediaFeatureExpression { - /// Trivially construct a new expression. - fn new( - feature: &'static nsMediaFeature, - value: Option, - range_or_operator: Option, - ) -> Self { - Self { - feature, - value, - range_or_operator, - } - } - - /// Parse a media expression of the form: - /// - /// ``` - /// (media-feature: media-value) - /// ``` - pub fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - input.expect_parenthesis_block()?; - input.parse_nested_block(|input| { - Self::parse_in_parenthesis_block(context, input) - }) - } - - /// Parse a media feature expression where we've already consumed the - /// parenthesis. - pub fn parse_in_parenthesis_block<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - // FIXME: remove extra indented block when lifetimes are non-lexical - let feature; - let range; - { - let location = input.current_source_location(); - let ident = input.expect_ident()?; - - let mut flags = 0; - - if context.chrome_rules_enabled() || context.stylesheet_origin == Origin::UserAgent - { - flags |= structs::nsMediaFeature_RequirementFlags_eUserAgentAndChromeOnly; - } - - let result = { - let mut feature_name = &**ident; - - if unsafe { structs::StaticPrefs_sVarCache_layout_css_prefixes_webkit } && - starts_with_ignore_ascii_case(feature_name, "-webkit-") - { - feature_name = &feature_name[8..]; - flags |= structs::nsMediaFeature_RequirementFlags_eHasWebkitPrefix; - if unsafe { - structs::StaticPrefs_sVarCache_layout_css_prefixes_device_pixel_ratio_webkit - } { - flags |= structs::nsMediaFeature_RequirementFlags_eWebkitDevicePixelRatioPrefEnabled; - } - } - - let range = if starts_with_ignore_ascii_case(feature_name, "min-") { - feature_name = &feature_name[4..]; - Some(Range::Min) - } else if starts_with_ignore_ascii_case(feature_name, "max-") { - feature_name = &feature_name[4..]; - Some(Range::Max) - } else { - None - }; - - let atom = Atom::from(string_as_ascii_lowercase(feature_name)); - match find_feature(|f| atom.as_ptr() == unsafe { *f.mName as *mut _ }) { - Some(f) => Ok((f, range)), - None => Err(()), - } - }; - - match result { - Ok((f, r)) => { - feature = f; - range = r; - }, - Err(()) => { - return Err(location.new_custom_error( - StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()), - )) - }, - } - - if (feature.mReqFlags & !flags) != 0 { - return Err(location.new_custom_error( - StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()), - )); - } - - if range.is_some() && - feature.mRangeType != nsMediaFeature_RangeType::eMinMaxAllowed - { - return Err(location.new_custom_error( - StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()), - )); - } - } - - let feature_allows_ranges = - feature.mRangeType == nsMediaFeature_RangeType::eMinMaxAllowed; - - let operator = input.try(consume_operation_or_colon); - let operator = match operator { - Err(..) => { - // If there's no colon, this is a media query of the - // form '()', that is, there's no value - // specified. - // - // Gecko doesn't allow ranged expressions without a - // value, so just reject them here too. - if range.is_some() { - return Err(input.new_custom_error( - StyleParseErrorKind::RangedExpressionWithNoValue - )); - } - - return Ok(Self::new(feature, None, None)); - } - Ok(operator) => operator, - }; - - let range_or_operator = match range { - Some(range) => { - if operator.is_some() { - return Err(input.new_custom_error( - StyleParseErrorKind::MediaQueryUnexpectedOperator - )); - } - Some(RangeOrOperator::Range(range)) - } - None => { - match operator { - Some(operator) => { - if !feature_allows_ranges { - return Err(input.new_custom_error( - StyleParseErrorKind::MediaQueryUnexpectedOperator - )); - } - Some(RangeOrOperator::Operator(operator)) - } - None => None, - } - } - }; - - let value = - parse_feature_value(feature, feature.mValueType, context, input).map_err(|err| { - err.location - .new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue) - })?; - - Ok(Self::new(feature, Some(value), range_or_operator)) - } - - /// Returns whether this media query evaluates to true for the given device. - pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool { - let mut css_value = nsCSSValue::null(); - unsafe { - (self.feature.mGetter.unwrap())( - device - .pres_context() - .mDocument - .raw::(), - self.feature, - &mut css_value, - ) - }; - - let value = match MediaExpressionValue::from_css_value(self, &css_value) { - Some(v) => v, - None => return false, - }; - - self.evaluate_against(device, &value, quirks_mode) - } - - fn evaluate_against( - &self, - device: &Device, - actual_value: &MediaExpressionValue, - quirks_mode: QuirksMode, - ) -> bool { - use self::MediaExpressionValue::*; - use std::cmp::Ordering; - - debug_assert!( - self.feature.mRangeType == nsMediaFeature_RangeType::eMinMaxAllowed || - self.range_or_operator.is_none(), - "Whoops, wrong range" - ); - - // http://dev.w3.org/csswg/mediaqueries3/#units - // em units are relative to the initial font-size. - let required_value = match self.value { - Some(ref v) => v, - None => { - // If there's no value, always match unless it's a zero length - // or a zero integer or boolean. - return match *actual_value { - BoolInteger(v) => v, - Integer(v) => v != 0, - Length(ref l) => computed::Context::for_media_query_evaluation( - device, - quirks_mode, - |context| l.to_computed_value(&context).px() != 0., - ), - BoolEnumerated(value) => { - let value = unsafe { - find_in_table( - *self.feature.mData.mKeywordAndBoolTable.as_ref(), - |_kw, val| val == value, - |e| e.mValueInBooleanContext, - ) - }; - value.expect("Value not found in the keyword table?") - }, - _ => true, - }; - }, - }; - - // FIXME(emilio): Handle the possible floating point errors? - let cmp = match (actual_value, required_value) { - (&Length(ref one), &Length(ref other)) => { - computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { - one.to_computed_value(&context) - .to_i32_au() - .cmp(&other.to_computed_value(&context).to_i32_au()) - }) - }, - (&Integer(one), &Integer(ref other)) => one.cmp(other), - (&BoolInteger(one), &BoolInteger(ref other)) => one.cmp(other), - (&Float(one), &Float(ref other)) => one.partial_cmp(other).unwrap(), - (&IntRatio(one_num, one_den), &IntRatio(other_num, other_den)) => { - // Extend to avoid overflow. - (one_num as u64 * other_den as u64).cmp(&(other_num as u64 * one_den as u64)) - }, - (&Resolution(ref one), &Resolution(ref other)) => { - let actual_dpi = unsafe { - if (*device.pres_context).mOverrideDPPX > 0.0 { - self::Resolution::Dppx((*device.pres_context).mOverrideDPPX).to_dpi() - } else { - one.to_dpi() - } - }; - - actual_dpi.partial_cmp(&other.to_dpi()).unwrap() - }, - (&Ident(ref one), &Ident(ref other)) => { - debug_assert_ne!( - self.feature.mRangeType, - nsMediaFeature_RangeType::eMinMaxAllowed - ); - return one == other; - }, - (&Enumerated(one), &Enumerated(other)) => { - debug_assert_ne!( - self.feature.mRangeType, - nsMediaFeature_RangeType::eMinMaxAllowed - ); - return one == other; - }, - (&BoolEnumerated(one), &BoolEnumerated(other)) => { - debug_assert_ne!( - self.feature.mRangeType, - nsMediaFeature_RangeType::eMinMaxAllowed - ); - return one == other; - }, - _ => unreachable!(), - }; - - let range_or_op = match self.range_or_operator { - Some(r) => r, - None => return cmp == Ordering::Equal, - }; - - match range_or_op { - RangeOrOperator::Range(range) => { - cmp == Ordering::Equal || match range { - Range::Min => cmp == Ordering::Greater, - Range::Max => cmp == Ordering::Less, - } - } - RangeOrOperator::Operator(op) => { - match op { - Operator::Equal => cmp == Ordering::Equal, - Operator::GreaterThan => cmp == Ordering::Greater, - Operator::GreaterThanEqual => { - cmp == Ordering::Equal || cmp == Ordering::Greater - } - Operator::LessThan => cmp == Ordering::Less, - Operator::LessThanEqual => { - cmp == Ordering::Equal || cmp == Ordering::Less - } - } - } - } - } -} diff --git a/components/style/gecko/mod.rs b/components/style/gecko/mod.rs index cfdf45d43b8..b9b6886518c 100644 --- a/components/style/gecko/mod.rs +++ b/components/style/gecko/mod.rs @@ -11,6 +11,7 @@ pub mod arc_types; pub mod conversions; pub mod data; pub mod global_style_data; +pub mod media_features; pub mod media_queries; pub mod pseudo_element; pub mod restyle_damage; diff --git a/components/style/lib.rs b/components/style/lib.rs index a43f3f87c45..1c3630b0986 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -67,6 +67,8 @@ extern crate matches; pub extern crate nsstring; #[cfg(feature = "gecko")] extern crate num_cpus; +#[macro_use] +extern crate num_derive; extern crate num_integer; extern crate num_traits; extern crate ordered_float; @@ -128,15 +130,13 @@ pub mod font_face; pub mod font_metrics; #[cfg(feature = "gecko")] #[allow(unsafe_code)] -pub mod gecko; -#[cfg(feature = "gecko")] -#[allow(unsafe_code)] pub mod gecko_bindings; pub mod hash; pub mod invalidation; #[allow(missing_docs)] // TODO. pub mod logical_geometry; pub mod matching; +#[macro_use] pub mod media_queries; pub mod parallel; pub mod parser; @@ -190,11 +190,16 @@ pub mod properties { include!(concat!(env!("OUT_DIR"), "/properties.rs")); } +#[cfg(feature = "gecko")] +#[allow(unsafe_code)] +pub mod gecko; + // uses a macro from properties #[cfg(feature = "servo")] #[allow(unsafe_code)] pub mod servo; + #[cfg(feature = "gecko")] #[allow(unsafe_code, missing_docs)] pub mod gecko_properties { diff --git a/components/style/media_queries/media_condition.rs b/components/style/media_queries/media_condition.rs index 4b80794af39..dbd677d0aee 100644 --- a/components/style/media_queries/media_condition.rs +++ b/components/style/media_queries/media_condition.rs @@ -13,7 +13,6 @@ use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; use super::{Device, MediaFeatureExpression}; - /// A binary `and` or `or` operator. #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss)] #[allow(missing_docs)] diff --git a/components/style/media_queries/media_feature.rs b/components/style/media_queries/media_feature.rs new file mode 100644 index 00000000000..7c0bfc12bef --- /dev/null +++ b/components/style/media_queries/media_feature.rs @@ -0,0 +1,172 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +//! Media features. + +use super::Device; +use super::media_feature_expression::{AspectRatio, RangeOrOperator}; + +use Atom; +use cssparser::Parser; +use parser::ParserContext; +use std::fmt; +use style_traits::ParseError; +use values::computed::{CSSPixelLength, Resolution}; + +/// A generic discriminant for an enum value. +pub type KeywordDiscriminant = u8; + +type MediaFeatureEvaluator = fn( + device: &Device, + // null == no value was given in the query. + value: Option, + range_or_operator: Option, +) -> bool; + +/// Serializes a given discriminant. +/// +/// FIXME(emilio): we could prevent this allocation if the ToCss code would +/// generate a method for keywords to get the static string or something. +pub type KeywordSerializer = fn(KeywordDiscriminant) -> String; + +/// Parses a given identifier. +pub type KeywordParser = for <'a, 'i, 't> fn( + context: &'a ParserContext, + input: &'a mut Parser<'i, 't>, +) -> Result>; + +/// An evaluator for a given media feature. +/// +/// This determines the kind of values that get parsed, too. +#[allow(missing_docs)] +pub enum Evaluator { + Length(MediaFeatureEvaluator), + Integer(MediaFeatureEvaluator), + Float(MediaFeatureEvaluator), + BoolInteger(MediaFeatureEvaluator), + /// An integer ratio, such as the one from device-pixel-ratio. + IntRatio(MediaFeatureEvaluator), + /// A resolution. + Resolution(MediaFeatureEvaluator), + /// A keyword value. + Enumerated { + /// The parser to get a discriminant given a string. + parser: KeywordParser, + /// The serializer to get a string from a discriminant. + /// + /// This is guaranteed to be called with a keyword that `parser` has + /// produced. + serializer: KeywordSerializer, + /// The evaluator itself. This is guaranteed to be called with a + /// keyword that `parser` has produced. + evaluator: MediaFeatureEvaluator, + }, + Ident(MediaFeatureEvaluator), +} + +/// A simple helper macro to create a keyword evaluator. +/// +/// This assumes that keyword feature expressions don't accept ranges, and +/// asserts if that's not true. As of today there's nothing like that (does that +/// even make sense?). +macro_rules! keyword_evaluator { + ($actual_evaluator:ident, $keyword_type:ty) => { + { + fn __parse<'i, 't>( + context: &$crate::parser::ParserContext, + input: &mut $crate::cssparser::Parser<'i, 't>, + ) -> Result< + $crate::media_queries::media_feature::KeywordDiscriminant, + ::style_traits::ParseError<'i>, + > { + let kw = <$keyword_type as $crate::parser::Parse>::parse(context, input)?; + Ok(kw as $crate::media_queries::media_feature::KeywordDiscriminant) + } + + fn __serialize(kw: $crate::media_queries::media_feature::KeywordDiscriminant) -> String { + // This unwrap is ok because the only discriminants that get + // back to us is the ones that `parse` produces. + let value: $keyword_type = + ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap(); + <$keyword_type as ::style_traits::ToCss>::to_css_string(&value) + } + + fn __evaluate( + device: &$crate::media_queries::Device, + value: Option<$crate::media_queries::media_feature::KeywordDiscriminant>, + range_or_operator: Option<$crate::media_queries::media_feature_expression::RangeOrOperator>, + ) -> bool { + debug_assert!( + range_or_operator.is_none(), + "Since when do keywords accept ranges?" + ); + // This unwrap is ok because the only discriminants that get + // back to us is the ones that `parse` produces. + let value: Option<$keyword_type> = + value.map(|kw| ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap()); + $actual_evaluator(device, value) + } + + $crate::media_queries::media_feature::Evaluator::Enumerated { + parser: __parse, + serializer: __serialize, + evaluator: __evaluate, + } + } + } +} + +bitflags! { + /// Different requirements or toggles that change how a expression is + /// parsed. + pub struct ParsingRequirements: u8 { + /// The feature should only be parsed in chrome and ua sheets. + const CHROME_AND_UA_ONLY = 1 << 0; + /// The feature requires a -webkit- prefix. + const WEBKIT_PREFIX = 1 << 1; + /// The feature requires the webkit-device-pixel-ratio preference to be + /// enabled. + const WEBKIT_DEVICE_PIXEL_RATIO_PREF_ENABLED = 1 << 2; + } +} + +/// Whether a media feature allows ranges or not. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[allow(missing_docs)] +pub enum AllowsRanges { + Yes, + No, +} + +/// A description of a media feature. +pub struct MediaFeatureDescription { + /// The media feature name, in ascii lowercase. + pub name: Atom, + /// Whether min- / max- prefixes are allowed or not. + pub allows_ranges: AllowsRanges, + /// The evaluator, which we also use to determine which kind of value to + /// parse. + pub evaluator: Evaluator, + /// Different requirements that need to hold for the feature to be + /// successfully parsed. + pub requirements: ParsingRequirements, +} + +impl MediaFeatureDescription { + /// Whether this media feature allows ranges. + #[inline] + pub fn allows_ranges(&self) -> bool { + self.allows_ranges == AllowsRanges::Yes + } +} + +impl fmt::Debug for MediaFeatureDescription { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("MediaFeatureExpression") + .field("name", &self.name) + .field("allows_ranges", &self.allows_ranges) + .field("requirements", &self.requirements) + .finish() + } +} diff --git a/components/style/media_queries/media_feature_expression.rs b/components/style/media_queries/media_feature_expression.rs new file mode 100644 index 00000000000..60fb2b2a087 --- /dev/null +++ b/components/style/media_queries/media_feature_expression.rs @@ -0,0 +1,562 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +//! Parsing for media feature expressions, like `(foo: bar)` or +//! `(width >= 400px)`. + +use super::Device; +use super::media_feature::{Evaluator, MediaFeatureDescription}; +use super::media_feature::{ParsingRequirements, KeywordDiscriminant}; + +use Atom; +use cssparser::{Parser, Token}; +use context::QuirksMode; +use num_traits::Zero; +use parser::{Parse, ParserContext}; +use std::fmt::{self, Write}; +use str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase}; +use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; +use stylesheets::Origin; +use values::{serialize_atom_identifier, CSSFloat}; +use values::specified::{Integer, Length, Number, Resolution}; +use values::computed::{self, ToComputedValue}; + +#[cfg(feature = "gecko")] +use gecko_bindings::structs; + +#[cfg(feature = "gecko")] +use gecko::media_features::MEDIA_FEATURES; +#[cfg(feature = "servo")] +use servo::media_queries::MEDIA_FEATURES; + +/// An aspect ratio, with a numerator and denominator. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] +pub struct AspectRatio(pub u32, pub u32); + +impl ToCss for AspectRatio { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: fmt::Write, + { + self.0.to_css(dest)?; + dest.write_char('/')?; + self.1.to_css(dest) + } +} + +/// The kind of matching that should be performed on a media feature value. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] +pub enum Range { + /// At least the specified value. + Min, + /// At most the specified value. + Max, +} + +/// The operator that was specified in this media feature. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] +pub enum Operator { + /// = + Equal, + /// > + GreaterThan, + /// >= + GreaterThanEqual, + /// < + LessThan, + /// <= + LessThanEqual, +} + +impl ToCss for Operator { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: fmt::Write, + { + dest.write_str(match *self { + Operator::Equal => "=", + Operator::LessThan => "<", + Operator::LessThanEqual => "<=", + Operator::GreaterThan => ">", + Operator::GreaterThanEqual => ">=", + }) + } +} + +/// Either a `Range` or an `Operator`. +/// +/// Ranged media features are not allowed with operations (that'd make no +/// sense). +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] +pub enum RangeOrOperator { + /// A `Range`. + Range(Range), + /// An `Operator`. + Operator(Operator), +} + +impl RangeOrOperator { + /// Evaluate a given range given a query value and a value from the browser. + pub fn evaluate( + range_or_op: Option, + query_value: Option, + value: T, + ) -> bool + where + T: PartialOrd + Zero + { + use std::cmp::Ordering; + + let query_value = match query_value { + Some(v) => v, + None => return value != Zero::zero(), + }; + + let cmp = match value.partial_cmp(&query_value) { + Some(c) => c, + None => return false, + }; + + let range_or_op = match range_or_op { + Some(r) => r, + None => return cmp == Ordering::Equal, + }; + + match range_or_op { + RangeOrOperator::Range(range) => { + cmp == Ordering::Equal || match range { + Range::Min => cmp == Ordering::Greater, + Range::Max => cmp == Ordering::Less, + } + } + RangeOrOperator::Operator(op) => { + match op { + Operator::Equal => cmp == Ordering::Equal, + Operator::GreaterThan => cmp == Ordering::Greater, + Operator::GreaterThanEqual => { + cmp == Ordering::Equal || cmp == Ordering::Greater + } + Operator::LessThan => cmp == Ordering::Less, + Operator::LessThanEqual => { + cmp == Ordering::Equal || cmp == Ordering::Less + } + } + } + } + } +} + +/// A feature expression contains a reference to the media feature, the value +/// the media query contained, and the range to evaluate. +#[derive(Clone, Debug, MallocSizeOf)] +pub struct MediaFeatureExpression { + feature: &'static MediaFeatureDescription, + value: Option, + range_or_operator: Option, +} + +impl PartialEq for MediaFeatureExpression { + fn eq(&self, other: &Self) -> bool { + self.feature as *const _ == other.feature as *const _ && + self.value == other.value && + self.range_or_operator == other.range_or_operator + } +} + +impl ToCss for MediaFeatureExpression { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: fmt::Write, + { + dest.write_str("(")?; + + if self.feature.requirements.contains(ParsingRequirements::WEBKIT_PREFIX) { + dest.write_str("-webkit-")?; + } + + if let Some(RangeOrOperator::Range(range)) = self.range_or_operator { + match range { + Range::Min => dest.write_str("min-")?, + Range::Max => dest.write_str("max-")?, + } + } + + // NB: CssStringWriter not needed, feature names are under control. + write!(dest, "{}", self.feature.name)?; + + if let Some(RangeOrOperator::Operator(op)) = self.range_or_operator { + dest.write_char(' ')?; + op.to_css(dest)?; + dest.write_char(' ')?; + } else if self.value.is_some() { + dest.write_str(": ")?; + } + + if let Some(ref val) = self.value { + val.to_css(dest, self)?; + } + + dest.write_str(")") + } +} + +/// Consumes an operation or a colon, or returns an error. +fn consume_operation_or_colon( + input: &mut Parser, +) -> Result, ()> { + let first_delim = { + let next_token = match input.next() { + Ok(t) => t, + Err(..) => return Err(()), + }; + + match *next_token { + Token::Colon => return Ok(None), + Token::Delim(oper) => oper, + _ => return Err(()), + } + }; + Ok(Some(match first_delim { + '=' => Operator::Equal, + '>' => { + if input.try(|i| i.expect_delim('=')).is_ok() { + Operator::GreaterThanEqual + } else { + Operator::GreaterThan + } + } + '<' => { + if input.try(|i| i.expect_delim('=')).is_ok() { + Operator::LessThanEqual + } else { + Operator::LessThan + } + } + _ => return Err(()), + })) +} + +impl MediaFeatureExpression { + fn new( + feature: &'static MediaFeatureDescription, + value: Option, + range_or_operator: Option, + ) -> Self { + Self { feature, value, range_or_operator } + } + + /// Parse a media expression of the form: + /// + /// ``` + /// (media-feature: media-value) + /// ``` + pub fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + input.expect_parenthesis_block()?; + input.parse_nested_block(|input| { + Self::parse_in_parenthesis_block(context, input) + }) + } + + /// Parse a media feature expression where we've already consumed the + /// parenthesis. + pub fn parse_in_parenthesis_block<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + // FIXME: remove extra indented block when lifetimes are non-lexical + let feature; + let range; + { + let location = input.current_source_location(); + let ident = input.expect_ident()?; + + let mut requirements = ParsingRequirements::empty(); + + if context.chrome_rules_enabled() || + context.stylesheet_origin == Origin::UserAgent + { + requirements.insert(ParsingRequirements::CHROME_AND_UA_ONLY); + } + + let result = { + let mut feature_name = &**ident; + + #[cfg(feature = "gecko")] + { + if unsafe { structs::StaticPrefs_sVarCache_layout_css_prefixes_webkit } && + starts_with_ignore_ascii_case(feature_name, "-webkit-") + { + feature_name = &feature_name[8..]; + requirements.insert(ParsingRequirements::WEBKIT_PREFIX); + if unsafe { + structs::StaticPrefs_sVarCache_layout_css_prefixes_device_pixel_ratio_webkit + } { + requirements.insert(ParsingRequirements::WEBKIT_DEVICE_PIXEL_RATIO_PREF_ENABLED); + } + } + } + + let range = if starts_with_ignore_ascii_case(feature_name, "min-") { + feature_name = &feature_name[4..]; + Some(Range::Min) + } else if starts_with_ignore_ascii_case(feature_name, "max-") { + feature_name = &feature_name[4..]; + Some(Range::Max) + } else { + None + }; + + let atom = Atom::from(string_as_ascii_lowercase(feature_name)); + match MEDIA_FEATURES.iter().find(|f| f.name == atom) { + Some(f) => Ok((f, range)), + None => Err(()), + } + }; + + match result { + Ok((f, r)) => { + feature = f; + range = r; + }, + Err(()) => { + return Err(location.new_custom_error( + StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()), + )) + }, + } + + if !(feature.requirements & !requirements).is_empty() { + return Err(location.new_custom_error( + StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()), + )); + } + + if range.is_some() && !feature.allows_ranges() { + return Err(location.new_custom_error( + StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()), + )); + } + } + + let operator = input.try(consume_operation_or_colon); + let operator = match operator { + Err(..) => { + // If there's no colon, this is a media query of the + // form '()', that is, there's no value + // specified. + // + // Gecko doesn't allow ranged expressions without a + // value, so just reject them here too. + if range.is_some() { + return Err(input.new_custom_error( + StyleParseErrorKind::RangedExpressionWithNoValue + )); + } + + return Ok(Self::new(feature, None, None)); + } + Ok(operator) => operator, + }; + + let range_or_operator = match range { + Some(range) => { + if operator.is_some() { + return Err(input.new_custom_error( + StyleParseErrorKind::MediaQueryUnexpectedOperator + )); + } + Some(RangeOrOperator::Range(range)) + } + None => { + match operator { + Some(operator) => { + if !feature.allows_ranges() { + return Err(input.new_custom_error( + StyleParseErrorKind::MediaQueryUnexpectedOperator + )); + } + Some(RangeOrOperator::Operator(operator)) + } + None => None, + } + } + }; + + let value = + MediaExpressionValue::parse(feature, context, input).map_err(|err| { + err.location + .new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue) + })?; + + Ok(Self::new(feature, Some(value), range_or_operator)) + } + + /// Returns whether this media query evaluates to true for the given device. + pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool { + let value = self.value.as_ref(); + + macro_rules! expect { + ($variant:ident) => { + value.map(|value| { + match *value { + MediaExpressionValue::$variant(ref v) => v, + _ => unreachable!("Unexpected MediaExpressionValue"), + } + }) + } + } + + match self.feature.evaluator { + Evaluator::Length(eval) => { + let computed = expect!(Length).map(|specified| { + computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { + specified.to_computed_value(context) + }) + }); + eval(device, computed, self.range_or_operator) + } + Evaluator::Integer(eval) => { + eval(device, expect!(Integer).cloned(), self.range_or_operator) + } + Evaluator::Float(eval) => { + eval(device, expect!(Float).cloned(), self.range_or_operator) + } + Evaluator::IntRatio(eval) => { + eval(device, expect!(IntRatio).cloned(), self.range_or_operator) + }, + Evaluator::Resolution(eval) => { + let computed = expect!(Resolution).map(|specified| { + computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { + specified.to_computed_value(context) + }) + }); + eval(device, computed, self.range_or_operator) + } + Evaluator::Enumerated { evaluator, .. } => { + evaluator( + device, + expect!(Enumerated).cloned(), + self.range_or_operator, + ) + } + Evaluator::Ident(eval) => { + eval(device, expect!(Ident).cloned(), self.range_or_operator) + } + Evaluator::BoolInteger(eval) => { + eval(device, expect!(BoolInteger).cloned(), self.range_or_operator) + } + } + } +} + +/// A value found or expected in a media expression. +/// +/// FIXME(emilio): How should calc() serialize in the Number / Integer / +/// BoolInteger / IntRatio case, as computed or as specified value? +/// +/// If the first, this would need to store the relevant values. +/// +/// See: https://github.com/w3c/csswg-drafts/issues/1968 +#[derive(Clone, Debug, MallocSizeOf, PartialEq)] +pub enum MediaExpressionValue { + /// A length. + Length(Length), + /// A (non-negative) integer. + Integer(u32), + /// A floating point value. + Float(CSSFloat), + /// A boolean value, specified as an integer (i.e., either 0 or 1). + BoolInteger(bool), + /// Two integers separated by '/', with optional whitespace on either side + /// of the '/'. + IntRatio(AspectRatio), + /// A resolution. + Resolution(Resolution), + /// An enumerated value, defined by the variant keyword table in the + /// feature's `mData` member. + Enumerated(KeywordDiscriminant), + /// An identifier. + Ident(Atom), +} + +impl MediaExpressionValue { + fn to_css( + &self, + dest: &mut CssWriter, + for_expr: &MediaFeatureExpression, + ) -> fmt::Result + where + W: fmt::Write, + { + match *self { + MediaExpressionValue::Length(ref l) => l.to_css(dest), + MediaExpressionValue::Integer(v) => v.to_css(dest), + MediaExpressionValue::Float(v) => v.to_css(dest), + MediaExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }), + MediaExpressionValue::IntRatio(ratio) => { + ratio.to_css(dest) + }, + MediaExpressionValue::Resolution(ref r) => r.to_css(dest), + MediaExpressionValue::Ident(ref ident) => serialize_atom_identifier(ident, dest), + MediaExpressionValue::Enumerated(value) => { + match for_expr.feature.evaluator { + Evaluator::Enumerated { serializer, .. } => { + dest.write_str(&*serializer(value)) + } + _ => unreachable!(), + } + }, + } + } + + fn parse<'i, 't>( + for_feature: &MediaFeatureDescription, + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + Ok(match for_feature.evaluator { + Evaluator::Length(..) => { + let length = Length::parse_non_negative(context, input)?; + MediaExpressionValue::Length(length) + } + Evaluator::Integer(..) => { + let integer = Integer::parse_non_negative(context, input)?; + MediaExpressionValue::Integer(integer.value() as u32) + } + Evaluator::BoolInteger(..) => { + let integer = Integer::parse_non_negative(context, input)?; + let value = integer.value(); + if value > 1 { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + MediaExpressionValue::BoolInteger(value == 1) + } + Evaluator::Float(..) => { + let number = Number::parse(context, input)?; + MediaExpressionValue::Float(number.get()) + } + Evaluator::IntRatio(..) => { + let a = Integer::parse_positive(context, input)?; + input.expect_delim('/')?; + let b = Integer::parse_positive(context, input)?; + MediaExpressionValue::IntRatio(AspectRatio( + a.value() as u32, + b.value() as u32 + )) + } + Evaluator::Resolution(..) => { + MediaExpressionValue::Resolution(Resolution::parse(context, input)?) + } + Evaluator::Enumerated { parser, .. } => { + MediaExpressionValue::Enumerated(parser(context, input)?) + } + Evaluator::Ident(..) => { + MediaExpressionValue::Ident(Atom::from(input.expect_ident()?.as_ref())) + } + }) + } +} diff --git a/components/style/media_queries/mod.rs b/components/style/media_queries/mod.rs index d27e33cc64c..b59ec32443d 100644 --- a/components/style/media_queries/mod.rs +++ b/components/style/media_queries/mod.rs @@ -9,12 +9,16 @@ mod media_condition; mod media_list; mod media_query; +#[macro_use] +pub mod media_feature; +pub mod media_feature_expression; pub use self::media_condition::MediaCondition; pub use self::media_list::MediaList; pub use self::media_query::{MediaQuery, MediaQueryType, MediaType, Qualifier}; +pub use self::media_feature_expression::MediaFeatureExpression; #[cfg(feature = "servo")] -pub use servo::media_queries::{Device, MediaFeatureExpression}; +pub use servo::media_queries::Device; #[cfg(feature = "gecko")] -pub use gecko::media_queries::{Device, MediaFeatureExpression}; +pub use gecko::media_queries::Device; From f1fe15981af4dccdfbc6876a9e5cdad3651ec303 Mon Sep 17 00:00:00 2001 From: Xidorn Quan Date: Thu, 16 Aug 2018 09:34:43 +0000 Subject: [PATCH 20/35] style: Simplify some code in NoCalcLength::parse_dimension. Differential Revision: https://phabricator.services.mozilla.com/D3473 --- components/style/values/specified/length.rs | 64 +++++++++------------ 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index 27206a40969..b6fe9bc2346 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -421,46 +421,34 @@ impl NoCalcLength { value: CSSFloat, unit: &str, ) -> Result { - match_ignore_ascii_case! { unit, - "px" => Ok(NoCalcLength::Absolute(AbsoluteLength::Px(value))), - "in" => Ok(NoCalcLength::Absolute(AbsoluteLength::In(value))), - "cm" => Ok(NoCalcLength::Absolute(AbsoluteLength::Cm(value))), - "mm" => Ok(NoCalcLength::Absolute(AbsoluteLength::Mm(value))), - "q" => Ok(NoCalcLength::Absolute(AbsoluteLength::Q(value))), - "pt" => Ok(NoCalcLength::Absolute(AbsoluteLength::Pt(value))), - "pc" => Ok(NoCalcLength::Absolute(AbsoluteLength::Pc(value))), + Ok(match_ignore_ascii_case! { unit, + "px" => NoCalcLength::Absolute(AbsoluteLength::Px(value)), + "in" => NoCalcLength::Absolute(AbsoluteLength::In(value)), + "cm" => NoCalcLength::Absolute(AbsoluteLength::Cm(value)), + "mm" => NoCalcLength::Absolute(AbsoluteLength::Mm(value)), + "q" => NoCalcLength::Absolute(AbsoluteLength::Q(value)), + "pt" => NoCalcLength::Absolute(AbsoluteLength::Pt(value)), + "pc" => NoCalcLength::Absolute(AbsoluteLength::Pc(value)), // font-relative - "em" => Ok(NoCalcLength::FontRelative(FontRelativeLength::Em(value))), - "ex" => Ok(NoCalcLength::FontRelative(FontRelativeLength::Ex(value))), - "ch" => Ok(NoCalcLength::FontRelative(FontRelativeLength::Ch(value))), - "rem" => Ok(NoCalcLength::FontRelative(FontRelativeLength::Rem(value))), + "em" => NoCalcLength::FontRelative(FontRelativeLength::Em(value)), + "ex" => NoCalcLength::FontRelative(FontRelativeLength::Ex(value)), + "ch" => NoCalcLength::FontRelative(FontRelativeLength::Ch(value)), + "rem" => NoCalcLength::FontRelative(FontRelativeLength::Rem(value)), // viewport percentages - "vw" => { - if context.in_page_rule() { - return Err(()) - } - Ok(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(value))) - }, - "vh" => { - if context.in_page_rule() { - return Err(()) - } - Ok(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vh(value))) - }, - "vmin" => { - if context.in_page_rule() { - return Err(()) - } - Ok(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmin(value))) - }, - "vmax" => { - if context.in_page_rule() { - return Err(()) - } - Ok(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmax(value))) - }, - _ => Err(()) - } + "vw" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(value)) + } + "vh" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vh(value)) + } + "vmin" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmin(value)) + } + "vmax" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmax(value)) + } + _ => return Err(()) + }) } #[inline] From fe05c8ecadcc999eb9cb3995033c9f70c4cc36c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Thu, 16 Aug 2018 10:05:52 +0000 Subject: [PATCH 21/35] style: Add some spec links to media queries. Differential Revision: https://phabricator.services.mozilla.com/D3489 --- components/style/gecko/media_features.rs | 25 ++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index 822a64d738b..8a0eaf7aa63 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -51,6 +51,7 @@ fn device_size(device: &Device) -> Size2D { Size2D::new(Au(width), Au(height)) } +/// https://drafts.csswg.org/mediaqueries-4/#width fn eval_width( device: &Device, value: Option, @@ -63,6 +64,7 @@ fn eval_width( ) } +/// https://drafts.csswg.org/mediaqueries-4/#device-width fn eval_device_width( device: &Device, value: Option, @@ -75,6 +77,7 @@ fn eval_device_width( ) } +/// https://drafts.csswg.org/mediaqueries-4/#height fn eval_height( device: &Device, value: Option, @@ -87,6 +90,7 @@ fn eval_height( ) } +/// https://drafts.csswg.org/mediaqueries-4/#device-height fn eval_device_height( device: &Device, value: Option, @@ -121,6 +125,7 @@ where ) } +/// https://drafts.csswg.org/mediaqueries-4/#aspect-ratio fn eval_aspect_ratio( device: &Device, query_value: Option, @@ -129,6 +134,7 @@ fn eval_aspect_ratio( eval_aspect_ratio_for(device, query_value, range_or_operator, viewport_size) } +/// https://drafts.csswg.org/mediaqueries-4/#device-aspect-ratio fn eval_device_aspect_ratio( device: &Device, query_value: Option, @@ -137,6 +143,11 @@ fn eval_device_aspect_ratio( eval_aspect_ratio_for(device, query_value, range_or_operator, device_size) } +/// https://compat.spec.whatwg.org/#css-media-queries-webkit-device-pixel-ratio +/// +/// FIXME(emilio): This should be an alias of `resolution`, according to the +/// spec, and also according to the code in Chromium. Unify with +/// `eval_resolution`. fn eval_device_pixel_ratio( device: &Device, query_value: Option, @@ -183,6 +194,7 @@ where } } +/// https://drafts.csswg.org/mediaqueries-4/#orientation fn eval_orientation( device: &Device, value: Option, @@ -190,6 +202,7 @@ fn eval_orientation( eval_orientation_for(device, value, viewport_size) } +/// FIXME: There's no spec for `-moz-device-orientation`. fn eval_device_orientation( device: &Device, value: Option, @@ -208,6 +221,7 @@ pub enum DisplayMode { Fullscreen, } +/// https://w3c.github.io/manifest/#the-display-mode-media-feature fn eval_display_mode( device: &Device, query_value: Option, @@ -225,6 +239,7 @@ fn eval_display_mode( gecko_display_mode as u8 == query_value as u8 } +/// https://drafts.csswg.org/mediaqueries-4/#grid fn eval_grid(_: &Device, query_value: Option, _: Option) -> bool { // Gecko doesn't support grid devices (e.g., ttys), so the 'grid' feature // is always 0. @@ -232,6 +247,7 @@ fn eval_grid(_: &Device, query_value: Option, _: Option) query_value.map_or(supports_grid, |v| v == supports_grid) } +/// https://compat.spec.whatwg.org/#css-media-queries-webkit-transform-3d fn eval_transform_3d( _: &Device, query_value: Option, @@ -248,12 +264,14 @@ enum Scan { Interlace, } +/// https://drafts.csswg.org/mediaqueries-4/#scan fn eval_scan(_: &Device, _: Option) -> bool { // Since Gecko doesn't support the 'tv' media type, the 'scan' feature never // matches. false } +/// https://drafts.csswg.org/mediaqueries-4/#color fn eval_color( device: &Device, query_value: Option, @@ -268,13 +286,13 @@ fn eval_color( ) } +/// https://drafts.csswg.org/mediaqueries-4/#color-index fn eval_color_index( _: &Device, query_value: Option, range_or_operator: Option, ) -> bool { - // We should return zero if the device does not use a color lookup - // table. + // We should return zero if the device does not use a color lookup table. let index = 0; RangeOrOperator::evaluate( range_or_operator, @@ -283,6 +301,7 @@ fn eval_color_index( ) } +/// https://drafts.csswg.org/mediaqueries-4/#monochrome fn eval_monochrome( _: &Device, query_value: Option, @@ -298,6 +317,7 @@ fn eval_monochrome( ) } +/// https://drafts.csswg.org/mediaqueries-4/#resolution fn eval_resolution( device: &Device, query_value: Option, @@ -319,6 +339,7 @@ enum PrefersReducedMotion { Reduce, } +/// https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion fn eval_prefers_reduced_motion( device: &Device, query_value: Option, From e9a99b2a7f1fd76b1beaadea1355074d0806eac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Wed, 15 Aug 2018 01:29:40 +0200 Subject: [PATCH 22/35] style: Manually inline class and ID getters. Somewhat ugly but hopefully not too much. Somehow it ends up removing more lines than adding. Differential Revision: https://phabricator.services.mozilla.com/D3536 --- components/style/gecko/snapshot.rs | 26 +---- components/style/gecko/snapshot_helpers.rs | 124 +++++++++++++++------ components/style/gecko/wrapper.rs | 70 +++++++----- components/style/gecko_string_cache/mod.rs | 2 +- 4 files changed, 138 insertions(+), 84 deletions(-) diff --git a/components/style/gecko/snapshot.rs b/components/style/gecko/snapshot.rs index d70c6259bbf..fc4a9121ae4 100644 --- a/components/style/gecko/snapshot.rs +++ b/components/style/gecko/snapshot.rs @@ -51,10 +51,6 @@ impl GeckoElementSnapshot { (self.mContains as u8 & flags as u8) != 0 } - fn as_ptr(&self) -> *const Self { - self - } - /// Returns true if the snapshot has stored state for pseudo-classes /// that depend on things other than `ElementState`. #[inline] @@ -184,14 +180,7 @@ impl ElementSnapshot for GeckoElementSnapshot { return None; } - let ptr = unsafe { bindings::Gecko_SnapshotAtomAttrValue(self, atom!("id").as_ptr()) }; - - // FIXME(emilio): This should assert, since this flag is exact. - if ptr.is_null() { - None - } else { - Some(unsafe { WeakAtom::new(ptr) }) - } + snapshot_helpers::get_id(&*self.mAttrs) } #[inline] @@ -200,12 +189,7 @@ impl ElementSnapshot for GeckoElementSnapshot { return false; } - snapshot_helpers::has_class( - self.as_ptr(), - name, - case_sensitivity, - bindings::Gecko_SnapshotHasClass, - ) + snapshot_helpers::has_class(name, case_sensitivity, &self.mClass) } #[inline] @@ -217,11 +201,7 @@ impl ElementSnapshot for GeckoElementSnapshot { return; } - snapshot_helpers::each_class( - self.as_ptr(), - callback, - bindings::Gecko_SnapshotClassOrClassList, - ) + snapshot_helpers::each_class(&self.mClass, callback) } #[inline] diff --git a/components/style/gecko/snapshot_helpers.rs b/components/style/gecko/snapshot_helpers.rs index 3f84de7fd50..6c40f242d5f 100644 --- a/components/style/gecko/snapshot_helpers.rs +++ b/components/style/gecko/snapshot_helpers.rs @@ -4,58 +4,114 @@ //! Element an snapshot common logic. -use gecko_bindings::structs::nsAtom; +use CaseSensitivityExt; +use gecko_bindings::bindings; +use gecko_bindings::structs::{self, nsAtom}; use selectors::attr::CaseSensitivity; -use std::{ptr, slice}; -use string_cache::Atom; +use string_cache::{Atom, WeakAtom}; /// A function that, given an element of type `T`, allows you to get a single /// class or a class list. -pub type ClassOrClassList = - unsafe extern "C" fn(T, *mut *mut nsAtom, *mut *mut *mut nsAtom) -> u32; +enum Class<'a> { + None, + One(*const nsAtom), + More(&'a [structs::RefPtr]), +} -/// A function to return whether an element of type `T` has a given class. -/// -/// The `bool` argument represents whether it should compare case-insensitively -/// or not. -pub type HasClass = unsafe extern "C" fn(T, *mut nsAtom, bool) -> bool; - -/// Given an item `T`, a class name, and a getter function, return whether that -/// element has the class that `name` represents. #[inline(always)] -pub fn has_class( - item: T, +fn base_type(attr: &structs::nsAttrValue) -> structs::nsAttrValue_ValueBaseType { + (attr.mBits & structs::NS_ATTRVALUE_BASETYPE_MASK) as structs::nsAttrValue_ValueBaseType +} + +#[inline(always)] +unsafe fn ptr(attr: &structs::nsAttrValue) -> *const T { + (attr.mBits & !structs::NS_ATTRVALUE_BASETYPE_MASK) as *const T +} + +#[inline(always)] +unsafe fn get_class_from_attr(attr: &structs::nsAttrValue) -> Class { + debug_assert!(bindings::Gecko_AssertClassAttrValueIsSane(attr)); + let base_type = base_type(attr); + if base_type == structs::nsAttrValue_ValueBaseType_eStringBase { + return Class::None; + } + if base_type == structs::nsAttrValue_ValueBaseType_eAtomBase { + return Class::One(ptr::(attr)); + } + debug_assert_eq!(base_type, structs::nsAttrValue_ValueBaseType_eOtherBase); + + let container = ptr::(attr); + debug_assert_eq!((*container).mType, structs::nsAttrValue_ValueType_eAtomArray); + let array = + (*container).__bindgen_anon_1.mValue.as_ref().__bindgen_anon_1.mAtomArray.as_ref(); + Class::More(&***array) +} + +#[inline(always)] +unsafe fn get_id_from_attr(attr: &structs::nsAttrValue) -> &WeakAtom { + debug_assert_eq!(base_type(attr), structs::nsAttrValue_ValueBaseType_eAtomBase); + WeakAtom::new(ptr::(attr)) +} + +/// Find an attribute value with a given name and no namespace. +#[inline(always)] +pub fn find_attr<'a>( + attrs: &'a [structs::AttrArray_InternalAttr], + name: &Atom, +) -> Option<&'a structs::nsAttrValue> { + attrs.iter() + .find(|attr| attr.mName.mBits == name.as_ptr() as usize) + .map(|attr| &attr.mValue) +} + +/// Finds the id attribute from a list of attributes. +#[inline(always)] +pub fn get_id(attrs: &[structs::AttrArray_InternalAttr]) -> Option<&WeakAtom> { + Some(unsafe { get_id_from_attr(find_attr(attrs, &atom!("id"))?) }) +} + +/// Given a class name, a case sensitivity, and an array of attributes, returns +/// whether the class has the attribute that name represents. +#[inline(always)] +pub fn has_class( name: &Atom, case_sensitivity: CaseSensitivity, - getter: HasClass, + attr: &structs::nsAttrValue, ) -> bool { - let ignore_case = match case_sensitivity { - CaseSensitivity::CaseSensitive => false, - CaseSensitivity::AsciiCaseInsensitive => true, - }; - - unsafe { getter(item, name.as_ptr(), ignore_case) } + match unsafe { get_class_from_attr(attr) } { + Class::None => false, + Class::One(atom) => unsafe { + case_sensitivity.eq_atom(name, WeakAtom::new(atom)) + }, + Class::More(atoms) => { + match case_sensitivity { + CaseSensitivity::CaseSensitive => { + atoms.iter().any(|atom| atom.mRawPtr == name.as_ptr()) + } + CaseSensitivity::AsciiCaseInsensitive => unsafe { + atoms.iter().any(|atom| WeakAtom::new(atom.mRawPtr).eq_ignore_ascii_case(name)) + } + } + } + } } /// Given an item, a callback, and a getter, execute `callback` for each class /// this `item` has. -pub fn each_class(item: T, mut callback: F, getter: ClassOrClassList) +#[inline(always)] +pub fn each_class(attr: &structs::nsAttrValue, mut callback: F) where F: FnMut(&Atom), { unsafe { - let mut class: *mut nsAtom = ptr::null_mut(); - let mut list: *mut *mut nsAtom = ptr::null_mut(); - let length = getter(item, &mut class, &mut list); - match length { - 0 => {}, - 1 => Atom::with(class, callback), - n => { - let classes = slice::from_raw_parts(list, n as usize); - for c in classes { - Atom::with(*c, &mut callback) + match get_class_from_attr(attr) { + Class::None => {}, + Class::One(atom) => Atom::with(atom, callback), + Class::More(atoms) => { + for atom in atoms { + Atom::with(atom.mRawPtr, &mut callback) } - }, + } } } } diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index cf1a2b56ab6..1ba850696d1 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -32,7 +32,6 @@ use gecko_bindings::bindings; use gecko_bindings::bindings::{Gecko_ElementState, Gecko_GetDocumentLWTheme}; use gecko_bindings::bindings::{Gecko_GetLastChild, Gecko_GetPreviousSibling, Gecko_GetNextStyleChild}; use gecko_bindings::bindings::{Gecko_SetNodeFlags, Gecko_UnsetNodeFlags}; -use gecko_bindings::bindings::Gecko_ClassOrClassList; use gecko_bindings::bindings::Gecko_ElementHasAnimations; use gecko_bindings::bindings::Gecko_ElementHasCSSAnimations; use gecko_bindings::bindings::Gecko_ElementHasCSSTransitions; @@ -581,6 +580,34 @@ impl<'le> fmt::Debug for GeckoElement<'le> { } impl<'le> GeckoElement<'le> { + #[inline(always)] + fn attrs(&self) -> &[structs::AttrArray_InternalAttr] { + unsafe { + let attrs = match self.0._base.mAttrs.mImpl.mPtr.as_ref() { + Some(attrs) => attrs, + None => return &[], + }; + + attrs.mBuffer.as_slice(attrs.mAttrCount as usize) + } + } + + #[inline(always)] + fn get_class_attr(&self) -> Option<&structs::nsAttrValue> { + if !self.may_have_class() { + return None; + } + + if self.is_svg_element() { + let svg_class = unsafe { bindings::Gecko_GetSVGAnimatedClass(self.0).as_ref() }; + if let Some(c) = svg_class { + return Some(c) + } + } + + snapshot_helpers::find_attr(self.attrs(), &atom!("class")) + } + #[inline] fn closest_anon_subtree_root_parent(&self) -> Option { debug_assert!(self.is_in_native_anonymous_subtree()); @@ -1281,26 +1308,19 @@ impl<'le> TElement for GeckoElement<'le> { return None; } - let ptr = unsafe { bindings::Gecko_AtomAttrValue(self.0, atom!("id").as_ptr()) }; - - // FIXME(emilio): Pretty sure the has_id flag is exact and we could - // assert here. - if ptr.is_null() { - None - } else { - Some(unsafe { WeakAtom::new(ptr) }) - } + snapshot_helpers::get_id(self.attrs()) } fn each_class(&self, callback: F) where F: FnMut(&Atom), { - if !self.may_have_class() { - return; - } + let attr = match self.get_class_attr() { + Some(c) => c, + None => return, + }; - snapshot_helpers::each_class(self.0, callback, Gecko_ClassOrClassList) + snapshot_helpers::each_class(attr, callback) } #[inline] @@ -2265,24 +2285,22 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { return false; } - unsafe { - let ptr = bindings::Gecko_AtomAttrValue(self.0, atom!("id").as_ptr()); + let element_id = match snapshot_helpers::get_id(self.attrs()) { + Some(id) => id, + None => return false, + }; - if ptr.is_null() { - false - } else { - case_sensitivity.eq_atom(WeakAtom::new(ptr), id) - } - } + case_sensitivity.eq_atom(element_id, id) } #[inline(always)] fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool { - if !self.may_have_class() { - return false; - } + let attr = match self.get_class_attr() { + Some(c) => c, + None => return false, + }; - snapshot_helpers::has_class(self.0, name, case_sensitivity, bindings::Gecko_HasClass) + snapshot_helpers::has_class(name, case_sensitivity, attr) } #[inline] diff --git a/components/style/gecko_string_cache/mod.rs b/components/style/gecko_string_cache/mod.rs index e70c7a47c36..70217f233de 100644 --- a/components/style/gecko_string_cache/mod.rs +++ b/components/style/gecko_string_cache/mod.rs @@ -263,7 +263,7 @@ impl fmt::Display for WeakAtom { impl Atom { /// Execute a callback with the atom represented by `ptr`. - pub unsafe fn with(ptr: *mut nsAtom, callback: F) -> R + pub unsafe fn with(ptr: *const nsAtom, callback: F) -> R where F: FnOnce(&Atom) -> R, { From 9350fa4c55843d145a97d0db17e1595ad0d9fb71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 17 Aug 2018 23:26:24 +0200 Subject: [PATCH 23/35] style: Remove an inaccurate and useless debug message. --- components/style/stylist.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 52feb19c411..dbddca725e1 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -1131,11 +1131,6 @@ impl Stylist { let rule_hash_target = element.rule_hash_target(); - debug!( - "Determining if style is shareable: pseudo: {}", - pseudo_element.is_some() - ); - let matches_user_rules = rule_hash_target.matches_user_and_author_rules(); let matches_author_rules = matches_user_rules && self.author_styles_enabled == AuthorStylesEnabled::Yes; From cd4d2819848404e0eaa94cb3cec46a413d3907a7 Mon Sep 17 00:00:00 2001 From: Xidorn Quan Date: Wed, 1 Aug 2018 14:13:41 +1000 Subject: [PATCH 24/35] style: Add scrollbar-width property. Bug: 1475033 Reviewed-by: heycam --- components/style/properties/longhands/ui.mako.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/components/style/properties/longhands/ui.mako.rs b/components/style/properties/longhands/ui.mako.rs index 13a940b2b96..419024862d4 100644 --- a/components/style/properties/longhands/ui.mako.rs +++ b/components/style/properties/longhands/ui.mako.rs @@ -16,6 +16,17 @@ ${helpers.single_keyword("ime-mode", "auto normal active disabled inactive", animation_value_type="discrete", spec="https://drafts.csswg.org/css-ui/#input-method-editor")} +${helpers.single_keyword( + "scrollbar-width", + "auto thin none", + products="gecko", + gecko_enum_prefix="StyleScrollbarWidth", + animation_value_type="discrete", + gecko_pref="layout.css.scrollbar-width.enabled", + enabled_in="chrome", + spec="https://drafts.csswg.org/css-scrollbars-1/#scrollbar-width" +)} + ${helpers.single_keyword("-moz-user-select", "auto text none all element elements" + " toggle tri-state -moz-all -moz-text", products="gecko", From d63ce552f7b876cb1a308e812f68810e5198048a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 17 Aug 2018 01:07:22 +0000 Subject: [PATCH 25/35] style: Deduplicate system metric atoms. Now that :-moz-system-metric is gone, there's no real reason for the atoms to be separate. Differential Revision: https://phabricator.services.mozilla.com/D3497 --- components/style/gecko/media_features.rs | 48 ++++++++++++------------ 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index 8a0eaf7aa63..0ff420a1612 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -401,7 +401,7 @@ fn eval_moz_touch_enabled( eval_system_metric( device, query_value, - atom!("touch-enabled"), + atom!("-moz-touch-enabled"), /* accessible_from_content = */ true, ) } @@ -424,7 +424,7 @@ fn eval_moz_os_version( } macro_rules! system_metric_feature { - ($feature_name:expr, $metric_name:expr) => { + ($feature_name:expr) => { { fn __eval( device: &Device, @@ -434,7 +434,7 @@ macro_rules! system_metric_feature { eval_system_metric( device, query_value, - $metric_name, + $feature_name, /* accessible_from_content = */ false, ) } @@ -596,28 +596,26 @@ lazy_static! { Evaluator::Ident(eval_moz_os_version), ParsingRequirements::CHROME_AND_UA_ONLY, ), - // FIXME(emilio): make system metrics store the -moz- atom, and remove - // some duplication here. - system_metric_feature!(atom!("-moz-scrollbar-start-backward"), atom!("scrollbar-start-backward")), - system_metric_feature!(atom!("-moz-scrollbar-start-forward"), atom!("scrollbar-start-forward")), - system_metric_feature!(atom!("-moz-scrollbar-end-backward"), atom!("scrollbar-end-backward")), - system_metric_feature!(atom!("-moz-scrollbar-end-forward"), atom!("scrollbar-end-forward")), - system_metric_feature!(atom!("-moz-scrollbar-thumb-proportional"), atom!("scrollbar-thumb-proportional")), - system_metric_feature!(atom!("-moz-overlay-scrollbars"), atom!("overlay-scrollbars")), - system_metric_feature!(atom!("-moz-windows-default-theme"), atom!("windows-default-theme")), - system_metric_feature!(atom!("-moz-mac-graphite-theme"), atom!("mac-graphite-theme")), - system_metric_feature!(atom!("-moz-mac-yosemite-theme"), atom!("mac-yosemite-theme")), - system_metric_feature!(atom!("-moz-windows-accent-color-in-titlebar"), atom!("windows-accent-color-in-titlebar")), - system_metric_feature!(atom!("-moz-windows-compositor"), atom!("windows-compositor")), - system_metric_feature!(atom!("-moz-windows-classic"), atom!("windows-classic")), - system_metric_feature!(atom!("-moz-windows-glass"), atom!("windows-glass")), - system_metric_feature!(atom!("-moz-menubar-drag"), atom!("menubar-drag")), - system_metric_feature!(atom!("-moz-swipe-animation-enabled"), atom!("swipe-animation-enabled")), - system_metric_feature!(atom!("-moz-gtk-csd-available"), atom!("gtk-csd-available")), - system_metric_feature!(atom!("-moz-gtk-csd-minimize-button"), atom!("gtk-csd-minimize-button")), - system_metric_feature!(atom!("-moz-gtk-csd-maximize-button"), atom!("gtk-csd-maximize-button")), - system_metric_feature!(atom!("-moz-gtk-csd-close-button"), atom!("gtk-csd-close-button")), - system_metric_feature!(atom!("-moz-system-dark-theme"), atom!("system-dark-theme")), + system_metric_feature!(atom!("-moz-scrollbar-start-backward")), + system_metric_feature!(atom!("-moz-scrollbar-start-forward")), + system_metric_feature!(atom!("-moz-scrollbar-end-backward")), + system_metric_feature!(atom!("-moz-scrollbar-end-forward")), + system_metric_feature!(atom!("-moz-scrollbar-thumb-proportional")), + system_metric_feature!(atom!("-moz-overlay-scrollbars")), + system_metric_feature!(atom!("-moz-windows-default-theme")), + system_metric_feature!(atom!("-moz-mac-graphite-theme")), + system_metric_feature!(atom!("-moz-mac-yosemite-theme")), + system_metric_feature!(atom!("-moz-windows-accent-color-in-titlebar")), + system_metric_feature!(atom!("-moz-windows-compositor")), + system_metric_feature!(atom!("-moz-windows-classic")), + system_metric_feature!(atom!("-moz-windows-glass")), + system_metric_feature!(atom!("-moz-menubar-drag")), + system_metric_feature!(atom!("-moz-swipe-animation-enabled")), + system_metric_feature!(atom!("-moz-gtk-csd-available")), + system_metric_feature!(atom!("-moz-gtk-csd-minimize-button")), + system_metric_feature!(atom!("-moz-gtk-csd-maximize-button")), + system_metric_feature!(atom!("-moz-gtk-csd-close-button")), + system_metric_feature!(atom!("-moz-system-dark-theme")), // This is the only system-metric media feature that's accessible to // content as of today. // FIXME(emilio): Restrict (or remove?) when bug 1035774 lands. From 07ffc0955f608c2f9a5098494c6ee02f59bd3b94 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 17 Aug 2018 15:33:22 +1000 Subject: [PATCH 26/35] style: Remove use of `fnv` in bloom.rs. To support that, this patch also does the following. - Removes the insert(), remove() and might_contain() methods, because they are specialized versions of insert_hash(), remove_hash(), and might_contain_hash(), and they are only used by tests within this file. - Moves hash() from the top level into create_and_insert_some_stuff(). - Changes create_and_insert_some_stuff() so that instead of hashing consecutive integers, it instead hashes stringified consecutive integers, which matches real usage a little better. - Raises the false_positives limit a little to account for the above changes. Bug: 1484096 Reviewed-by: heycam --- components/selectors/Cargo.toml | 1 - components/selectors/bloom.rs | 65 ++++++++++++--------------------- components/selectors/lib.rs | 1 - 3 files changed, 24 insertions(+), 43 deletions(-) diff --git a/components/selectors/Cargo.toml b/components/selectors/Cargo.toml index 20ca61fb784..6d5a93b3485 100644 --- a/components/selectors/Cargo.toml +++ b/components/selectors/Cargo.toml @@ -24,7 +24,6 @@ bitflags = "1.0" matches = "0.1" cssparser = "0.24.0" log = "0.4" -fnv = "1.0" fxhash = "0.2" phf = "0.7.18" precomputed-hash = "0.1" diff --git a/components/selectors/bloom.rs b/components/selectors/bloom.rs index 16dabf498f9..d9056665ac4 100644 --- a/components/selectors/bloom.rs +++ b/components/selectors/bloom.rs @@ -5,9 +5,7 @@ //! Counting and non-counting Bloom filters tuned for use as ancestor filters //! for selector matching. -use fnv::FnvHasher; use std::fmt::{self, Debug}; -use std::hash::{Hash, Hasher}; // The top 8 bits of the 32-bit hash value are not used by the bloom filter. // Consumers may rely on this to pack hashes more efficiently. @@ -108,43 +106,27 @@ where unreachable!() } + /// Inserts an item with a particular hash into the bloom filter. #[inline] pub fn insert_hash(&mut self, hash: u32) { self.storage.adjust_first_slot(hash, true); self.storage.adjust_second_slot(hash, true); } - /// Inserts an item into the bloom filter. - #[inline] - pub fn insert(&mut self, elem: &T) { - self.insert_hash(hash(elem)) - } - + /// Removes an item with a particular hash from the bloom filter. #[inline] pub fn remove_hash(&mut self, hash: u32) { self.storage.adjust_first_slot(hash, false); self.storage.adjust_second_slot(hash, false); } - /// Removes an item from the bloom filter. - #[inline] - pub fn remove(&mut self, elem: &T) { - self.remove_hash(hash(elem)) - } - + /// Check whether the filter might contain an item with the given hash. + /// This can sometimes return true even if the item is not in the filter, + /// but will never return false for items that are actually in the filter. #[inline] pub fn might_contain_hash(&self, hash: u32) -> bool { !self.storage.first_slot_is_empty(hash) && !self.storage.second_slot_is_empty(hash) } - - /// Check whether the filter might contain an item. This can - /// sometimes return true even if the item is not in the filter, - /// but will never return false for items that are actually in the - /// filter. - #[inline] - pub fn might_contain(&self, elem: &T) -> bool { - self.might_contain_hash(hash(elem)) - } } impl Debug for CountingBloomFilter @@ -296,16 +278,6 @@ impl Clone for BloomStorageBool { } } -fn hash(elem: &T) -> u32 { - // We generally use FxHasher in Stylo because it's faster than FnvHasher, - // but the increased collision rate has outsized effect on the bloom - // filter, so we use FnvHasher instead here. - let mut hasher = FnvHasher::default(); - elem.hash(&mut hasher); - let hash: u64 = hasher.finish(); - (hash >> 32) as u32 ^ (hash as u32) -} - #[inline] fn hash1(hash: u32) -> u32 { hash & KEY_MASK @@ -318,8 +290,18 @@ fn hash2(hash: u32) -> u32 { #[test] fn create_and_insert_some_stuff() { + use fxhash::FxHasher; + use std::hash::{Hash, Hasher}; use std::mem::transmute; + fn hash_as_str(i: usize) -> u32 { + let mut hasher = FxHasher::default(); + let s = i.to_string(); + s.hash(&mut hasher); + let hash: u64 = hasher.finish(); + (hash >> 32) as u32 ^ (hash as u32) + } + let mut bf = BloomFilter::new(); // Statically assert that ARRAY_SIZE is a multiple of 8, which @@ -329,33 +311,34 @@ fn create_and_insert_some_stuff() { } for i in 0_usize..1000 { - bf.insert(&i); + bf.insert_hash(hash_as_str(i)); } for i in 0_usize..1000 { - assert!(bf.might_contain(&i)); + assert!(bf.might_contain_hash(hash_as_str(i))); } - let false_positives = (1001_usize..2000).filter(|i| bf.might_contain(i)).count(); + let false_positives = + (1001_usize..2000).filter(|i| bf.might_contain_hash(hash_as_str(*i))).count(); - assert!(false_positives < 160, "{} is not < 160", false_positives); // 16%. + assert!(false_positives < 190, "{} is not < 190", false_positives); // 19%. for i in 0_usize..100 { - bf.remove(&i); + bf.remove_hash(hash_as_str(i)); } for i in 100_usize..1000 { - assert!(bf.might_contain(&i)); + assert!(bf.might_contain_hash(hash_as_str(i))); } - let false_positives = (0_usize..100).filter(|i| bf.might_contain(i)).count(); + let false_positives = (0_usize..100).filter(|i| bf.might_contain_hash(hash_as_str(*i))).count(); assert!(false_positives < 20, "{} is not < 20", false_positives); // 20%. bf.clear(); for i in 0_usize..2000 { - assert!(!bf.might_contain(&i)); + assert!(!bf.might_contain_hash(hash_as_str(i))); } } diff --git a/components/selectors/lib.rs b/components/selectors/lib.rs index aa30604bbd1..43e274915cb 100644 --- a/components/selectors/lib.rs +++ b/components/selectors/lib.rs @@ -9,7 +9,6 @@ extern crate bitflags; #[macro_use] extern crate cssparser; -extern crate fnv; extern crate fxhash; #[macro_use] extern crate log; From c9c5e560791591c02bd1dda0648898229830ad45 Mon Sep 17 00:00:00 2001 From: Xidorn Quan Date: Fri, 17 Aug 2018 12:46:02 +0000 Subject: [PATCH 27/35] style: Use AspectRatio directly for RangeOrOperator::evaluate. Differential Revision: https://phabricator.services.mozilla.com/D3587 --- components/style/gecko/media_features.rs | 7 ++-- .../media_queries/media_feature_expression.rs | 34 +++++++++++++++---- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index 0ff420a1612..58afc901f48 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -118,11 +118,8 @@ where }; let size = get_size(device); - RangeOrOperator::evaluate( - range_or_operator, - Some(size.height.0 as u64 * query_value.0 as u64), - size.width.0 as u64 * query_value.1 as u64, - ) + let value = AspectRatio(size.width.0 as u32, size.height.0 as u32); + RangeOrOperator::evaluate_with_query_value(range_or_operator, query_value, value) } /// https://drafts.csswg.org/mediaqueries-4/#aspect-ratio diff --git a/components/style/media_queries/media_feature_expression.rs b/components/style/media_queries/media_feature_expression.rs index 60fb2b2a087..81bbb69c1a1 100644 --- a/components/style/media_queries/media_feature_expression.rs +++ b/components/style/media_queries/media_feature_expression.rs @@ -14,6 +14,7 @@ use cssparser::{Parser, Token}; use context::QuirksMode; use num_traits::Zero; use parser::{Parse, ParserContext}; +use std::cmp::{PartialOrd, Ordering}; use std::fmt::{self, Write}; use str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase}; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; @@ -45,6 +46,15 @@ impl ToCss for AspectRatio { } } +impl PartialOrd for AspectRatio { + fn partial_cmp(&self, other: &AspectRatio) -> Option { + u64::partial_cmp( + &(self.0 as u64 * other.1 as u64), + &(self.1 as u64 * other.0 as u64), + ) + } +} + /// The kind of matching that should be performed on a media feature value. #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] pub enum Range { @@ -97,7 +107,8 @@ pub enum RangeOrOperator { } impl RangeOrOperator { - /// Evaluate a given range given a query value and a value from the browser. + /// Evaluate a given range given an optional query value and a value from + /// the browser. pub fn evaluate( range_or_op: Option, query_value: Option, @@ -106,13 +117,22 @@ impl RangeOrOperator { where T: PartialOrd + Zero { - use std::cmp::Ordering; - - let query_value = match query_value { - Some(v) => v, - None => return value != Zero::zero(), - }; + match query_value { + Some(v) => Self::evaluate_with_query_value(range_or_op, v, value), + None => !value.is_zero(), + } + } + /// Evaluate a given range given a non-optional query value and a value from + /// the browser. + pub fn evaluate_with_query_value( + range_or_op: Option, + query_value: T, + value: T, + ) -> bool + where + T: PartialOrd, + { let cmp = match value.partial_cmp(&query_value) { Some(c) => c, None => return false, From 67f2185f547efae56c881d6d028fcb36a1c6a9bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 17 Aug 2018 21:25:37 +0000 Subject: [PATCH 28/35] style: Make webkit device-pixel-ratio media queries a proper alias to resolution. According to the spec: https://compat.spec.whatwg.org/#css-media-queries-webkit-device-pixel-ratio And to the Chromium implementation: https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/css/media_query_evaluator.cc?l=366&rcl=1d7328865bcf06a687aafc18ff95d55317030672 They're no different than resolution. In our implementation `resolution` does slightly different stuff. Given we still haven't shipped -webkit-device-pixel-ratio, making this match resolution looks better than the opposite. Differential Revision: https://phabricator.services.mozilla.com/D3588 --- components/style/gecko/media_features.rs | 10 +++------- components/style/values/computed/resolution.rs | 6 ++++++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index 58afc901f48..3595133f55c 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -150,14 +150,10 @@ fn eval_device_pixel_ratio( query_value: Option, range_or_operator: Option, ) -> bool { - let ratio = unsafe { - bindings::Gecko_MediaFeatures_GetDevicePixelRatio(device.document()) - }; - - RangeOrOperator::evaluate( + eval_resolution( + device, + query_value.map(Resolution::from_dppx), range_or_operator, - query_value, - ratio, ) } diff --git a/components/style/values/computed/resolution.rs b/components/style/values/computed/resolution.rs index 817ba082236..d90bdf4867d 100644 --- a/components/style/values/computed/resolution.rs +++ b/components/style/values/computed/resolution.rs @@ -21,6 +21,12 @@ impl Resolution { pub fn dppx(&self) -> CSSFloat { self.0 } + + /// Return a computed `resolution` value from a dppx float value. + #[inline] + pub fn from_dppx(dppx: CSSFloat) -> Self { + Resolution(dppx) + } } impl ToComputedValue for specified::Resolution { From d93119fc94a114ce221e0978b3d5647e8072ebda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 18 Aug 2018 17:58:35 +0200 Subject: [PATCH 29/35] Update Cargo.lock --- Cargo.lock | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b2c1e05e6e..55d5c936045 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3082,7 +3082,6 @@ version = "0.19.0" dependencies = [ "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "cssparser 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3485,7 +3484,6 @@ dependencies = [ "html5ever 0.22.3 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", "itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "malloc_size_of 0.0.1", @@ -3493,6 +3491,7 @@ dependencies = [ "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "new-ordered-float 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "new_debug_unreachable 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-derive 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-integer 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", From 4d3f96f562bdb95f878262bf64444a6b8ca4ee04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 18 Aug 2018 17:58:44 +0200 Subject: [PATCH 30/35] style: Relax the version requirement of num-integer. --- components/style/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index d673dc5c610..0795bb7803f 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -47,7 +47,7 @@ malloc_size_of = { path = "../malloc_size_of" } malloc_size_of_derive = { path = "../malloc_size_of_derive" } matches = "0.1" num_cpus = {version = "1.1.0", optional = true} -num-integer = "0.1.32" +num-integer = "0.1" num-traits = "0.2" num-derive = "0.2" new-ordered-float = "1.0" From eccea52093d3e3b4192e66637f5e88d708b62153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 18 Aug 2018 18:17:03 +0200 Subject: [PATCH 31/35] Add `width` as a static atom. --- components/atoms/static_atoms.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt index f60c497cf2e..7f514703d05 100644 --- a/components/atoms/static_atoms.txt +++ b/components/atoms/static_atoms.txt @@ -86,3 +86,4 @@ url waiting webglcontextcreationerror week +width From 8ae1322fb38ab325dee44ab0ea370a6bf0993d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 18 Aug 2018 18:19:27 +0200 Subject: [PATCH 32/35] Add `scan` as a static atom. --- components/atoms/static_atoms.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt index 7f514703d05..0798179c151 100644 --- a/components/atoms/static_atoms.txt +++ b/components/atoms/static_atoms.txt @@ -66,6 +66,7 @@ reftest-wait reset right sans-serif +scan screen scroll-position search From 935b5393a9af765798bb0ad9e423fdf7394ec5b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 18 Aug 2018 18:19:34 +0200 Subject: [PATCH 33/35] Port servo to the new media query system. Port `width`, and also add the `scan` media feature so I don't need to add ugliness just to workaround the unused keyword_evaluator macro. --- components/style/gecko/media_features.rs | 11 -- .../style/media_queries/media_feature.rs | 12 ++ components/style/servo/media_queries.rs | 168 +++++------------- 3 files changed, 57 insertions(+), 134 deletions(-) diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index 3595133f55c..8d7147f6ad2 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -16,17 +16,6 @@ use media_queries::media_feature::{MediaFeatureDescription, Evaluator}; use media_queries::media_feature::{AllowsRanges, ParsingRequirements}; use media_queries::media_feature_expression::{AspectRatio, RangeOrOperator}; -macro_rules! feature { - ($name:expr, $allows_ranges:expr, $evaluator:expr, $reqs:expr,) => { - MediaFeatureDescription { - name: $name, - allows_ranges: $allows_ranges, - evaluator: $evaluator, - requirements: $reqs, - } - } -} - fn viewport_size(device: &Device) -> Size2D { let pc = device.pres_context(); if pc.mIsRootPaginatedDocument() != 0 { diff --git a/components/style/media_queries/media_feature.rs b/components/style/media_queries/media_feature.rs index 7c0bfc12bef..fc486f0e627 100644 --- a/components/style/media_queries/media_feature.rs +++ b/components/style/media_queries/media_feature.rs @@ -161,6 +161,18 @@ impl MediaFeatureDescription { } } +/// A simple helper to construct a `MediaFeatureDescription`. +macro_rules! feature { + ($name:expr, $allows_ranges:expr, $evaluator:expr, $reqs:expr,) => { + $crate::media_queries::media_feature::MediaFeatureDescription { + name: $name, + allows_ranges: $allows_ranges, + evaluator: $evaluator, + requirements: $reqs, + } + } +} + impl fmt::Debug for MediaFeatureDescription { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("MediaFeatureExpression") diff --git a/components/style/servo/media_queries.rs b/components/style/servo/media_queries.rs index ba31cfcaf4b..ab4240815b4 100644 --- a/components/style/servo/media_queries.rs +++ b/components/style/servo/media_queries.rs @@ -5,21 +5,21 @@ //! Servo's media-query device and expression representation. use app_units::Au; -use context::QuirksMode; -use cssparser::{Parser, RGBA}; +use cssparser::RGBA; use euclid::{Size2D, TypedScale, TypedSize2D}; use media_queries::MediaType; -use parser::ParserContext; use properties::ComputedValues; -use selectors::parser::SelectorParseErrorKind; -use std::fmt::{self, Write}; use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering}; -use style_traits::{CSSPixel, CssWriter, DevicePixel, ParseError, ToCss}; +use style_traits::{CSSPixel, DevicePixel}; use style_traits::viewport::ViewportConstraints; -use values::{specified, KeyframesName}; -use values::computed::{self, ToComputedValue}; +use values::KeyframesName; +use values::computed::CSSPixelLength; use values::computed::font::FontSize; +use media_queries::media_feature::{MediaFeatureDescription, Evaluator}; +use media_queries::media_feature::{AllowsRanges, ParsingRequirements}; +use media_queries::media_feature_expression::RangeOrOperator; + /// A device is a structure that represents the current media a given document /// is displayed in. /// @@ -155,125 +155,47 @@ impl Device { } } -/// A expression kind servo understands and parses. -/// -/// Only `pub` for unit testing, please don't use it directly! -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub enum ExpressionKind { - /// - Width(Range), +/// https://drafts.csswg.org/mediaqueries-4/#width +fn eval_width( + device: &Device, + value: Option, + range_or_operator: Option, +) -> bool { + RangeOrOperator::evaluate( + range_or_operator, + value.map(Au::from), + device.au_viewport_size().width, + ) } -/// A single expression a per: -/// -/// -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct MediaFeatureExpression(pub ExpressionKind); - -impl MediaFeatureExpression { - /// The kind of expression we're, just for unit testing. - /// - /// Eventually this will become servo-only. - pub fn kind_for_testing(&self) -> &ExpressionKind { - &self.0 - } - - /// Parse a media expression of the form: - /// - /// ``` - /// media-feature: media-value - /// ``` - /// - /// Only supports width ranges for now. - pub fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - input.expect_parenthesis_block()?; - input.parse_nested_block(|input| { - Self::parse_in_parenthesis_block(context, input) - }) - } - - /// Parse a media range expression where we've already consumed the - /// parenthesis. - pub fn parse_in_parenthesis_block<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - let name = input.expect_ident_cloned()?; - input.expect_colon()?; - // TODO: Handle other media features - Ok(MediaFeatureExpression(match_ignore_ascii_case! { &name, - "min-width" => { - ExpressionKind::Width(Range::Min(specified::Length::parse_non_negative(context, input)?)) - }, - "max-width" => { - ExpressionKind::Width(Range::Max(specified::Length::parse_non_negative(context, input)?)) - }, - "width" => { - ExpressionKind::Width(Range::Eq(specified::Length::parse_non_negative(context, input)?)) - }, - _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) - })) - } - - /// Evaluate this expression and return whether it matches the current - /// device. - pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool { - let viewport_size = device.au_viewport_size(); - let value = viewport_size.width; - match self.0 { - ExpressionKind::Width(ref range) => { - match range.to_computed_range(device, quirks_mode) { - Range::Min(ref width) => value >= *width, - Range::Max(ref width) => value <= *width, - Range::Eq(ref width) => value == *width, - } - }, - } - } +#[derive(Debug, Copy, Clone, FromPrimitive, ToCss, Parse)] +#[repr(u8)] +enum Scan { + Progressive, + Interlace, } -impl ToCss for MediaFeatureExpression { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: Write, - { - let (s, l) = match self.0 { - ExpressionKind::Width(Range::Min(ref l)) => ("(min-width: ", l), - ExpressionKind::Width(Range::Max(ref l)) => ("(max-width: ", l), - ExpressionKind::Width(Range::Eq(ref l)) => ("(width: ", l), - }; - dest.write_str(s)?; - l.to_css(dest)?; - dest.write_char(')') - } +/// https://drafts.csswg.org/mediaqueries-4/#scan +fn eval_scan(_: &Device, _: Option) -> bool { + // Since we doesn't support the 'tv' media type, the 'scan' feature never + // matches. + false } -/// An enumeration that represents a ranged value. -/// -/// Only public for testing, implementation details of `MediaFeatureExpression` -/// may change for Stylo. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub enum Range { - /// At least the inner value. - Min(T), - /// At most the inner value. - Max(T), - /// Exactly the inner value. - Eq(T), -} - -impl Range { - fn to_computed_range(&self, device: &Device, quirks_mode: QuirksMode) -> Range { - computed::Context::for_media_query_evaluation(device, quirks_mode, |context| match *self { - Range::Min(ref width) => Range::Min(Au::from(width.to_computed_value(&context))), - Range::Max(ref width) => Range::Max(Au::from(width.to_computed_value(&context))), - Range::Eq(ref width) => Range::Eq(Au::from(width.to_computed_value(&context))), - }) - } +lazy_static! { + /// A list with all the media features that Servo supports. + pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 2] = [ + feature!( + atom!("width"), + AllowsRanges::Yes, + Evaluator::Length(eval_width), + ParsingRequirements::empty(), + ), + feature!( + atom!("scan"), + AllowsRanges::No, + keyword_evaluator!(eval_scan, Scan), + ParsingRequirements::empty(), + ), + ]; } From 38a00745e2d0faedeb8464e08e346cd347d370ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 18 Aug 2018 18:41:40 +0200 Subject: [PATCH 34/35] Appease tidy. --- components/style/gecko/media_features.rs | 17 ++++++------ .../style/media_queries/media_feature.rs | 7 +++-- .../media_queries/media_feature_expression.rs | 26 +++++++++---------- components/style/servo/media_queries.rs | 9 +++---- 4 files changed, 27 insertions(+), 32 deletions(-) diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index 8d7147f6ad2..386d0ad5b43 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -8,14 +8,13 @@ use Atom; use app_units::Au; use euclid::Size2D; use gecko_bindings::bindings; +use media_queries::Device; +use media_queries::media_feature::{AllowsRanges, ParsingRequirements}; +use media_queries::media_feature::{MediaFeatureDescription, Evaluator}; +use media_queries::media_feature_expression::{AspectRatio, RangeOrOperator}; use values::computed::CSSPixelLength; use values::computed::Resolution; -use media_queries::Device; -use media_queries::media_feature::{MediaFeatureDescription, Evaluator}; -use media_queries::media_feature::{AllowsRanges, ParsingRequirements}; -use media_queries::media_feature_expression::{AspectRatio, RangeOrOperator}; - fn viewport_size(device: &Device) -> Size2D { let pc = device.pres_context(); if pc.mIsRootPaginatedDocument() != 0 { @@ -146,7 +145,7 @@ fn eval_device_pixel_ratio( ) } -#[derive(Debug, Copy, Clone, FromPrimitive, ToCss, Parse)] +#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] #[repr(u8)] enum Orientation { Landscape, @@ -193,7 +192,7 @@ fn eval_device_orientation( } /// Values for the display-mode media feature. -#[derive(Debug, Copy, Clone, FromPrimitive, ToCss, Parse)] +#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] #[repr(u8)] #[allow(missing_docs)] pub enum DisplayMode { @@ -239,7 +238,7 @@ fn eval_transform_3d( query_value.map_or(supports_transforms, |v| v == supports_transforms) } -#[derive(Debug, Copy, Clone, FromPrimitive, ToCss, Parse)] +#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] #[repr(u8)] enum Scan { Progressive, @@ -314,7 +313,7 @@ fn eval_resolution( ) } -#[derive(Debug, Copy, Clone, FromPrimitive, ToCss, Parse)] +#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] #[repr(u8)] enum PrefersReducedMotion { NoPreference, diff --git a/components/style/media_queries/media_feature.rs b/components/style/media_queries/media_feature.rs index fc486f0e627..5a5bc88863e 100644 --- a/components/style/media_queries/media_feature.rs +++ b/components/style/media_queries/media_feature.rs @@ -4,14 +4,13 @@ //! Media features. -use super::Device; -use super::media_feature_expression::{AspectRatio, RangeOrOperator}; - use Atom; use cssparser::Parser; use parser::ParserContext; use std::fmt; use style_traits::ParseError; +use super::Device; +use super::media_feature_expression::{AspectRatio, RangeOrOperator}; use values::computed::{CSSPixelLength, Resolution}; /// A generic discriminant for an enum value. @@ -132,7 +131,7 @@ bitflags! { } /// Whether a media feature allows ranges or not. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[allow(missing_docs)] pub enum AllowsRanges { Yes, diff --git a/components/style/media_queries/media_feature_expression.rs b/components/style/media_queries/media_feature_expression.rs index 81bbb69c1a1..f07b8cc27e4 100644 --- a/components/style/media_queries/media_feature_expression.rs +++ b/components/style/media_queries/media_feature_expression.rs @@ -5,13 +5,11 @@ //! Parsing for media feature expressions, like `(foo: bar)` or //! `(width >= 400px)`. -use super::Device; -use super::media_feature::{Evaluator, MediaFeatureDescription}; -use super::media_feature::{ParsingRequirements, KeywordDiscriminant}; - use Atom; -use cssparser::{Parser, Token}; use context::QuirksMode; +use cssparser::{Parser, Token}; +#[cfg(feature = "gecko")] +use gecko_bindings::structs; use num_traits::Zero; use parser::{Parse, ParserContext}; use std::cmp::{PartialOrd, Ordering}; @@ -19,17 +17,12 @@ use std::fmt::{self, Write}; use str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase}; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; use stylesheets::Origin; +use super::Device; +use super::media_feature::{Evaluator, MediaFeatureDescription}; +use super::media_feature::{ParsingRequirements, KeywordDiscriminant}; use values::{serialize_atom_identifier, CSSFloat}; -use values::specified::{Integer, Length, Number, Resolution}; use values::computed::{self, ToComputedValue}; - -#[cfg(feature = "gecko")] -use gecko_bindings::structs; - -#[cfg(feature = "gecko")] -use gecko::media_features::MEDIA_FEATURES; -#[cfg(feature = "servo")] -use servo::media_queries::MEDIA_FEATURES; +use values::specified::{Integer, Length, Number, Resolution}; /// An aspect ratio, with a numerator and denominator. #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] @@ -287,6 +280,11 @@ impl MediaFeatureExpression { context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { + #[cfg(feature = "gecko")] + use gecko::media_features::MEDIA_FEATURES; + #[cfg(feature = "servo")] + use servo::media_queries::MEDIA_FEATURES; + // FIXME: remove extra indented block when lifetimes are non-lexical let feature; let range; diff --git a/components/style/servo/media_queries.rs b/components/style/servo/media_queries.rs index ab4240815b4..98b1ec7ae6f 100644 --- a/components/style/servo/media_queries.rs +++ b/components/style/servo/media_queries.rs @@ -8,6 +8,9 @@ use app_units::Au; use cssparser::RGBA; use euclid::{Size2D, TypedScale, TypedSize2D}; use media_queries::MediaType; +use media_queries::media_feature::{AllowsRanges, ParsingRequirements}; +use media_queries::media_feature::{MediaFeatureDescription, Evaluator}; +use media_queries::media_feature_expression::RangeOrOperator; use properties::ComputedValues; use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering}; use style_traits::{CSSPixel, DevicePixel}; @@ -16,10 +19,6 @@ use values::KeyframesName; use values::computed::CSSPixelLength; use values::computed::font::FontSize; -use media_queries::media_feature::{MediaFeatureDescription, Evaluator}; -use media_queries::media_feature::{AllowsRanges, ParsingRequirements}; -use media_queries::media_feature_expression::RangeOrOperator; - /// A device is a structure that represents the current media a given document /// is displayed in. /// @@ -168,7 +167,7 @@ fn eval_width( ) } -#[derive(Debug, Copy, Clone, FromPrimitive, ToCss, Parse)] +#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] #[repr(u8)] enum Scan { Progressive, From 142cdcd3fd27b9abdee994368b98c7adbcf22e96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sun, 19 Aug 2018 13:56:21 +0200 Subject: [PATCH 35/35] Update test expectations. --- tests/wpt/metadata/css/cssom/overflow-serialization.html.ini | 4 ---- tests/wpt/metadata/css/cssom/shorthand-values.html.ini | 3 --- 2 files changed, 7 deletions(-) delete mode 100644 tests/wpt/metadata/css/cssom/overflow-serialization.html.ini diff --git a/tests/wpt/metadata/css/cssom/overflow-serialization.html.ini b/tests/wpt/metadata/css/cssom/overflow-serialization.html.ini deleted file mode 100644 index 3a51b8caa02..00000000000 --- a/tests/wpt/metadata/css/cssom/overflow-serialization.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[overflow-serialization.html] - [CSSOM - Overflow shorthand serialization] - expected: FAIL - diff --git a/tests/wpt/metadata/css/cssom/shorthand-values.html.ini b/tests/wpt/metadata/css/cssom/shorthand-values.html.ini index 427f39aa3e7..e0b5a48bdb5 100644 --- a/tests/wpt/metadata/css/cssom/shorthand-values.html.ini +++ b/tests/wpt/metadata/css/cssom/shorthand-values.html.ini @@ -29,6 +29,3 @@ [The serialization of list-style-type: circle; list-style-position: inside; list-style-image: initial; should be canonical.] expected: FAIL - [The serialization of overflow-x: scroll; overflow-y: hidden; should be canonical.] - expected: FAIL -