diff --git a/components/style/cbindgen.toml b/components/style/cbindgen.toml index 06c1730622a..84bf2eb4301 100644 --- a/components/style/cbindgen.toml +++ b/components/style/cbindgen.toml @@ -39,6 +39,8 @@ derive_helper_methods = true prefix = "Style" include = [ "Appearance", + "BreakBetween", + "BreakWithin", "ComputedFontStretchRange", "ComputedFontStyleDescriptor", "ComputedFontWeightRange", @@ -53,5 +55,6 @@ include = [ "TimingFunction", "PathCommand", "UnicodeRange", + "UserSelect", ] item_types = ["enums", "structs", "typedefs"] diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index 31c3dc207f5..c9d4ab0b753 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -109,7 +109,7 @@ pub struct VariableValue { references_environment: bool, /// Custom property names in var() functions. - references: PrecomputedHashSet, + references: Box<[Name]>, } impl ToCss for SpecifiedValue { @@ -278,7 +278,7 @@ impl VariableValue { css: String::new(), last_token_type: TokenSerializationType::nothing(), first_token_type: TokenSerializationType::nothing(), - references: PrecomputedHashSet::default(), + references: Default::default(), references_environment: false, } } @@ -335,11 +335,17 @@ impl VariableValue { let (first_token_type, css, last_token_type) = parse_self_contained_declaration_value(input, Some(&mut references))?; + let custom_property_references = references + .custom_property_references + .into_iter() + .collect::>() + .into_boxed_slice(); + Ok(Arc::new(VariableValue { css: css.into_owned(), first_token_type, last_token_type, - references: references.custom_property_references, + references: custom_property_references, references_environment: references.references_environment, })) } diff --git a/components/style/gecko/non_ts_pseudo_class_list.rs b/components/style/gecko/non_ts_pseudo_class_list.rs index f7d8be418de..cf21cbf8999 100644 --- a/components/style/gecko/non_ts_pseudo_class_list.rs +++ b/components/style/gecko/non_ts_pseudo_class_list.rs @@ -37,7 +37,7 @@ macro_rules! apply_non_ts_list { ("visited", Visited, visited, IN_VISITED_STATE, _), ("active", Active, active, IN_ACTIVE_STATE, _), ("checked", Checked, checked, IN_CHECKED_STATE, _), - ("defined", Defined, defined, IN_DEFINED_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), + ("defined", Defined, defined, IN_DEFINED_STATE, _), ("disabled", Disabled, disabled, IN_DISABLED_STATE, _), ("enabled", Enabled, enabled, IN_ENABLED_STATE, _), ("focus", Focus, focus, IN_FOCUS_STATE, _), diff --git a/components/style/gecko/regen_atoms.py b/components/style/gecko/regen_atoms.py index 36ec0f70434..18c28a13b49 100755 --- a/components/style/gecko/regen_atoms.py +++ b/components/style/gecko/regen_atoms.py @@ -16,8 +16,8 @@ sys.path.insert(0, os.path.join(os.path.dirname(GECKO_DIR), "properties")) import build -# Matches lines like `GK_ATOM(foo, "foo", 0x12345678, nsStaticAtom, PseudoElementAtom)`. -PATTERN = re.compile('^GK_ATOM\(([^,]*),[^"]*"([^"]*)",\s*(0x[0-9a-f]+),\s*([^,]*),\s*([^)]*)\)', +# Matches lines like `GK_ATOM(foo, "foo", 0x12345678, true, nsStaticAtom, PseudoElementAtom)`. +PATTERN = re.compile('^GK_ATOM\(([^,]*),[^"]*"([^"]*)",\s*(0x[0-9a-f]+),\s*[^,]*,\s*([^,]*),\s*([^)]*)\)', re.MULTILINE) FILE = "include/nsGkAtomList.h" diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index b8fd780b716..cde467b8335 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -5,7 +5,6 @@ //! Gecko-specific bits for selector-parsing. use crate::element_state::{DocumentState, ElementState}; -use crate::gecko_bindings::structs; use crate::gecko_bindings::structs::RawServoSelectorList; use crate::gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI}; use crate::invalidation::element::document_state::InvalidationMatchingData; @@ -178,9 +177,6 @@ impl NonTSPseudoClass { NonTSPseudoClass::Fullscreen => unsafe { mozilla::StaticPrefs_sVarCache_full_screen_api_unprefix_enabled }, - NonTSPseudoClass::Defined => unsafe { - structs::nsContentUtils_sIsCustomElementsEnabled - }, // Otherwise, a pseudo-class is enabled in content when it // doesn't have any enabled flag. _ => !self @@ -347,10 +343,7 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { #[inline] fn parse_slotted(&self) -> bool { - // NOTE(emilio): Slot assignment and such works per-document, but - // getting a document around here is not trivial, and it's not worth - // anyway to handle this in a per-doc basis. - unsafe { structs::nsContentUtils_sIsShadowDOMEnabled } + true } #[inline] diff --git a/components/style/gecko_string_cache/mod.rs b/components/style/gecko_string_cache/mod.rs index 6d9e5c60f80..ed0cc19dae2 100644 --- a/components/style/gecko_string_cache/mod.rs +++ b/components/style/gecko_string_cache/mod.rs @@ -175,13 +175,19 @@ impl WeakAtom { /// Returns whether this atom is static. #[inline] pub fn is_static(&self) -> bool { - unsafe { (*self.as_ptr()).mIsStatic() != 0 } + self.0.mIsStatic() != 0 + } + + /// Returns whether this atom is ascii lowercase. + #[inline] + fn is_ascii_lowercase(&self) -> bool { + self.0.mIsAsciiLowercase() != 0 } /// Returns the length of the atom string. #[inline] pub fn len(&self) -> u32 { - unsafe { (*self.as_ptr()).mLength() } + self.0.mLength() } /// Returns whether this atom is the empty string. @@ -199,41 +205,54 @@ impl WeakAtom { /// Convert this atom to ASCII lower-case pub fn to_ascii_lowercase(&self) -> Atom { - let slice = self.as_slice(); - match slice - .iter() - .position(|&char16| (b'A' as u16) <= char16 && char16 <= (b'Z' as u16)) - { - None => self.clone(), - Some(i) => { - let mut buffer: [u16; 64] = unsafe { mem::uninitialized() }; - let mut vec; - let mutable_slice = if let Some(buffer_prefix) = buffer.get_mut(..slice.len()) { - buffer_prefix.copy_from_slice(slice); - buffer_prefix - } else { - vec = slice.to_vec(); - &mut vec - }; - for char16 in &mut mutable_slice[i..] { - if *char16 <= 0x7F { - *char16 = (*char16 as u8).to_ascii_lowercase() as u16 - } - } - Atom::from(&*mutable_slice) - }, + if self.is_ascii_lowercase() { + return self.clone(); } + + let slice = self.as_slice(); + let mut buffer: [u16; 64] = unsafe { mem::uninitialized() }; + let mut vec; + let mutable_slice = if let Some(buffer_prefix) = buffer.get_mut(..slice.len()) { + buffer_prefix.copy_from_slice(slice); + buffer_prefix + } else { + vec = slice.to_vec(); + &mut vec + }; + for char16 in &mut *mutable_slice { + if *char16 <= 0x7F { + *char16 = (*char16 as u8).to_ascii_lowercase() as u16 + } + } + Atom::from(&*mutable_slice) } /// Return whether two atoms are ASCII-case-insensitive matches + #[inline] pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool { if self == other { return true; } + // If we know both atoms are ascii-lowercase, then we can stick with + // pointer equality. + if self.is_ascii_lowercase() && other.is_ascii_lowercase() { + debug_assert!(!self.eq_ignore_ascii_case_slow(other)); + return false; + } + + self.eq_ignore_ascii_case_slow(other) + } + + fn eq_ignore_ascii_case_slow(&self, other: &Self) -> bool { let a = self.as_slice(); let b = other.as_slice(); - a.len() == b.len() && a.iter().zip(b).all(|(&a16, &b16)| { + + if a.len() != b.len() { + return false; + } + + a.iter().zip(b).all(|(&a16, &b16)| { if a16 <= 0x7F && b16 <= 0x7F { (a16 as u8).eq_ignore_ascii_case(&(b16 as u8)) } else { @@ -241,13 +260,6 @@ impl WeakAtom { } }) } - - /// Return whether this atom is an ASCII-case-insensitive match for the given string - pub fn eq_str_ignore_ascii_case(&self, other: &str) -> bool { - self.chars() - .map(|r| r.map(|c: char| c.to_ascii_lowercase())) - .eq(other.chars().map(|c: char| Ok(c.to_ascii_lowercase()))) - } } impl fmt::Debug for WeakAtom { diff --git a/components/style/media_queries/media_feature_expression.rs b/components/style/media_queries/media_feature_expression.rs index b6bfc03179d..0b093931fad 100644 --- a/components/style/media_queries/media_feature_expression.rs +++ b/components/style/media_queries/media_feature_expression.rs @@ -10,8 +10,12 @@ use super::media_feature::{KeywordDiscriminant, ParsingRequirements}; use super::Device; use crate::context::QuirksMode; #[cfg(feature = "gecko")] +use crate::gecko::media_features::MEDIA_FEATURES; +#[cfg(feature = "gecko")] use crate::gecko_bindings::structs; use crate::parser::{Parse, ParserContext}; +#[cfg(feature = "servo")] +use crate::servo::media_queries::MEDIA_FEATURES; use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase}; use crate::stylesheets::Origin; use crate::values::computed::{self, ToComputedValue}; @@ -150,14 +154,14 @@ impl RangeOrOperator { /// the media query contained, and the range to evaluate. #[derive(Clone, Debug, MallocSizeOf)] pub struct MediaFeatureExpression { - feature: &'static MediaFeatureDescription, + feature_index: usize, 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.feature_index == other.feature_index && self.value == other.value && self.range_or_operator == other.range_or_operator } @@ -170,8 +174,9 @@ impl ToCss for MediaFeatureExpression { { dest.write_str("(")?; - if self - .feature + let feature = self.feature(); + + if feature .requirements .contains(ParsingRequirements::WEBKIT_PREFIX) { @@ -186,7 +191,7 @@ impl ToCss for MediaFeatureExpression { } // NB: CssStringWriter not needed, feature names are under control. - write!(dest, "{}", self.feature.name)?; + write!(dest, "{}", feature.name)?; if let Some(RangeOrOperator::Operator(op)) = self.range_or_operator { dest.write_char(' ')?; @@ -240,17 +245,22 @@ fn consume_operation_or_colon(input: &mut Parser) -> Result, () impl MediaFeatureExpression { fn new( - feature: &'static MediaFeatureDescription, + feature_index: usize, value: Option, range_or_operator: Option, ) -> Self { + debug_assert!(feature_index < MEDIA_FEATURES.len()); Self { - feature, + feature_index, value, range_or_operator, } } + fn feature(&self) -> &'static MediaFeatureDescription { + &MEDIA_FEATURES[self.feature_index] + } + /// Parse a media expression of the form: /// /// ``` @@ -270,12 +280,8 @@ impl MediaFeatureExpression { context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { - #[cfg(feature = "gecko")] - use crate::gecko::media_features::MEDIA_FEATURES; - #[cfg(feature = "servo")] - use crate::servo::media_queries::MEDIA_FEATURES; - // FIXME: remove extra indented block when lifetimes are non-lexical + let feature_index; let feature; let range; { @@ -319,14 +325,19 @@ impl MediaFeatureExpression { }; let atom = Atom::from(string_as_ascii_lowercase(feature_name)); - match MEDIA_FEATURES.iter().find(|f| f.name == atom) { - Some(f) => Ok((f, range)), + match MEDIA_FEATURES + .iter() + .enumerate() + .find(|(_, f)| f.name == atom) + { + Some((i, f)) => Ok((i, f, range)), None => Err(()), } }; match result { - Ok((f, r)) => { + Ok((i, f, r)) => { + feature_index = i; feature = f; range = r; }, @@ -365,7 +376,7 @@ impl MediaFeatureExpression { ); } - return Ok(Self::new(feature, None, None)); + return Ok(Self::new(feature_index, None, None)); }, Ok(operator) => operator, }; @@ -396,7 +407,7 @@ impl MediaFeatureExpression { .new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue) })?; - Ok(Self::new(feature, Some(value), range_or_operator)) + Ok(Self::new(feature_index, Some(value), range_or_operator)) } /// Returns whether this media query evaluates to true for the given device. @@ -412,7 +423,7 @@ impl MediaFeatureExpression { }; } - match self.feature.evaluator { + match self.feature().evaluator { Evaluator::Length(eval) => { let computed = expect!(Length).map(|specified| { computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { @@ -492,7 +503,7 @@ impl MediaExpressionValue { 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 { + MediaExpressionValue::Enumerated(value) => match for_expr.feature().evaluator { Evaluator::Enumerated { serializer, .. } => dest.write_str(&*serializer(value)), _ => unreachable!(), }, diff --git a/components/style/properties/data.py b/components/style/properties/data.py index 10f64484693..22d5337fe67 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -291,6 +291,8 @@ class Longhand(object): "AlignItems", "AlignSelf", "Appearance", + "BreakBetween", + "BreakWithin", "BackgroundRepeat", "BorderImageRepeat", "BorderStyle", @@ -336,6 +338,7 @@ class Longhand(object): "TextEmphasisPosition", "TouchAction", "TransformStyle", + "UserSelect", "XSpan", "XTextZoom", "ZIndex", diff --git a/components/style/properties/declaration_block.rs b/components/style/properties/declaration_block.rs index a6d736fb49f..dfe619fc79b 100644 --- a/components/style/properties/declaration_block.rs +++ b/components/style/properties/declaration_block.rs @@ -309,7 +309,9 @@ impl PropertyDeclarationBlock { .find(|(declaration, _)| declaration.id() == property) } - fn shorthand_to_css( + /// Tries to serialize a given shorthand from the declarations in this + /// block. + pub fn shorthand_to_css( &self, shorthand: ShorthandId, dest: &mut CssStringWriter, diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index b845d382623..f0b8cef22ac 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -1396,6 +1396,8 @@ impl Clone for ${style_struct.gecko_struct_name} { # Types used with predefined_type()-defined properties that we can auto-generate. predefined_types = { + "BreakBetween": impl_simple, + "BreakWithin": impl_simple, "Color": impl_color, "ColorOrAuto": impl_color, "GreaterThanOrEqualToOneNumber": impl_simple, @@ -1429,6 +1431,7 @@ impl Clone for ${style_struct.gecko_struct_name} { "SVGWidth": impl_svg_length, "Transform": impl_transform, "TransformOrigin": impl_transform_origin, + "UserSelect": impl_simple, "url::UrlOrNone": impl_css_url, } @@ -3028,8 +3031,7 @@ fn static_assert() { animation-iteration-count animation-timing-function transition-duration transition-delay transition-timing-function transition-property - page-break-before page-break-after rotate - scroll-snap-points-x scroll-snap-points-y + rotate scroll-snap-points-x scroll-snap-points-y scroll-snap-type-x scroll-snap-type-y scroll-snap-coordinate perspective-origin -moz-binding will-change offset-path overscroll-behavior-x overscroll-behavior-y @@ -3148,35 +3150,6 @@ fn static_assert() { <%call expr="impl_coord_copy('vertical_align', 'mVerticalAlign')"> - % for kind in ["before", "after"]: - // Temp fix for Bugzilla bug 24000. - // Map 'auto' and 'avoid' to false, and 'always', 'left', and 'right' to true. - // "A conforming user agent may interpret the values 'left' and 'right' - // as 'always'." - CSS2.1, section 13.3.1 - pub fn set_page_break_${kind}(&mut self, v: longhands::page_break_${kind}::computed_value::T) { - use crate::computed_values::page_break_${kind}::T; - - let result = match v { - T::Auto => false, - T::Always => true, - T::Avoid => false, - T::Left => true, - T::Right => true - }; - self.gecko.mBreak${kind.title()} = result; - } - - ${impl_simple_copy('page_break_' + kind, 'mBreak' + kind.title())} - - // Temp fix for Bugzilla bug 24000. - // See set_page_break_before/after for detail. - pub fn clone_page_break_${kind}(&self) -> longhands::page_break_${kind}::computed_value::T { - use crate::computed_values::page_break_${kind}::T; - - if self.gecko.mBreak${kind.title()} { T::Always } else { T::Auto } - } - % endfor - ${impl_style_coord("scroll_snap_points_x", "mScrollSnapPointsX")} ${impl_style_coord("scroll_snap_points_y", "mScrollSnapPointsY")} diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index 5ff4a10c3c0..4efab0bc60d 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -2108,7 +2108,7 @@ impl ComputedRotate { // // If the axis is unspecified, it defaults to "0 0 1" match *self { - Rotate::None => (0., 0., 1., Angle::zero()), + Rotate::None => unreachable!("None is handled by the caller"), Rotate::Rotate3D(rx, ry, rz, angle) => (rx, ry, rz, angle), Rotate::Rotate(angle) => (0., 0., 1., angle), } @@ -2122,41 +2122,58 @@ impl Animate for ComputedRotate { other: &Self, procedure: Procedure, ) -> Result { - let (from, to) = (self.resolve(), other.resolve()); + match (self, other) { + (&Rotate::None, &Rotate::None) => Ok(Rotate::None), + (&Rotate::Rotate3D(fx, fy, fz, fa), &Rotate::None) => { + // No need to normalize `none`, so animate angle directly. + Ok(Rotate::Rotate3D(fx, fy, fz, fa.animate(&Angle::zero(), procedure)?)) + }, + (&Rotate::None, &Rotate::Rotate3D(tx, ty, tz, ta)) => { + // No need to normalize `none`, so animate angle directly. + Ok(Rotate::Rotate3D(tx, ty, tz, Angle::zero().animate(&ta, procedure)?)) + }, + (&Rotate::Rotate3D(_, ..), _) | (_, &Rotate::Rotate3D(_, ..)) => { + let (from, to) = (self.resolve(), other.resolve()); + let (mut fx, mut fy, mut fz, fa) = + transform::get_normalized_vector_and_angle(from.0, from.1, from.2, from.3); + let (mut tx, mut ty, mut tz, ta) = + transform::get_normalized_vector_and_angle(to.0, to.1, to.2, to.3); - let (mut fx, mut fy, mut fz, fa) = - transform::get_normalized_vector_and_angle(from.0, from.1, from.2, from.3); - let (mut tx, mut ty, mut tz, ta) = - transform::get_normalized_vector_and_angle(to.0, to.1, to.2, to.3); + if fa == Angle::from_degrees(0.) { + fx = tx; + fy = ty; + fz = tz; + } else if ta == Angle::from_degrees(0.) { + tx = fx; + ty = fy; + tz = fz; + } - if fa == Angle::from_degrees(0.) { - fx = tx; - fy = ty; - fz = tz; - } else if ta == Angle::from_degrees(0.) { - tx = fx; - ty = fy; - tz = fz; + if (fx, fy, fz) == (tx, ty, tz) { + return Ok(Rotate::Rotate3D(fx, fy, fz, fa.animate(&ta, procedure)?)); + } + + let fv = DirectionVector::new(fx, fy, fz); + let tv = DirectionVector::new(tx, ty, tz); + let fq = Quaternion::from_direction_and_angle(&fv, fa.radians64()); + let tq = Quaternion::from_direction_and_angle(&tv, ta.radians64()); + + let rq = Quaternion::animate(&fq, &tq, procedure)?; + let (x, y, z, angle) = transform::get_normalized_vector_and_angle( + rq.0 as f32, + rq.1 as f32, + rq.2 as f32, + rq.3.acos() as f32 * 2.0, + ); + + Ok(Rotate::Rotate3D(x, y, z, Angle::from_radians(angle))) + }, + (&Rotate::Rotate(_), _) | (_, &Rotate::Rotate(_)) => { + // If this is a 2D rotation, we just animate the + let (from, to) = (self.resolve().3, other.resolve().3); + Ok(Rotate::Rotate(from.animate(&to, procedure)?)) + }, } - - if (fx, fy, fz) == (tx, ty, tz) { - return Ok(Rotate::Rotate3D(fx, fy, fz, fa.animate(&ta, procedure)?)); - } - - let fv = DirectionVector::new(fx, fy, fz); - let tv = DirectionVector::new(tx, ty, tz); - let fq = Quaternion::from_direction_and_angle(&fv, fa.radians64()); - let tq = Quaternion::from_direction_and_angle(&tv, ta.radians64()); - - let rq = Quaternion::animate(&fq, &tq, procedure)?; - let (x, y, z, angle) = transform::get_normalized_vector_and_angle( - rq.0 as f32, - rq.1 as f32, - rq.2 as f32, - rq.3.acos() as f32 * 2.0, - ); - - Ok(Rotate::Rotate3D(x, y, z, Angle::from_radians(angle))) } } diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs index 685e97aa3b0..f83b7928dc9 100644 --- a/components/style/properties/longhands/box.mako.rs +++ b/components/style/properties/longhands/box.mako.rs @@ -437,29 +437,33 @@ ${helpers.single_keyword( animation_value_type="discrete", )} -// TODO add support for logical values recto and verso -${helpers.single_keyword( +${helpers.predefined_type( "page-break-after", - "auto always avoid left right", + "BreakBetween", + "computed::BreakBetween::Auto", + needs_context=False, products="gecko", spec="https://drafts.csswg.org/css2/page.html#propdef-page-break-after", animation_value_type="discrete", )} -${helpers.single_keyword( +${helpers.predefined_type( "page-break-before", - "auto always avoid left right", + "BreakBetween", + "computed::BreakBetween::Auto", + needs_context=False, products="gecko", spec="https://drafts.csswg.org/css2/page.html#propdef-page-break-before", animation_value_type="discrete", )} -${helpers.single_keyword( +${helpers.predefined_type( "page-break-inside", - "auto avoid", - products="gecko", + "BreakWithin", + "computed::BreakWithin::Auto", gecko_ffi_name="mBreakInside", - gecko_constant_prefix="NS_STYLE_PAGE_BREAK", + needs_context=False, + products="gecko", spec="https://drafts.csswg.org/css2/page.html#propdef-page-break-inside", animation_value_type="discrete", )} diff --git a/components/style/properties/longhands/ui.mako.rs b/components/style/properties/longhands/ui.mako.rs index b8a188d896b..c077d4d8ba8 100644 --- a/components/style/properties/longhands/ui.mako.rs +++ b/components/style/properties/longhands/ui.mako.rs @@ -31,15 +31,13 @@ ${helpers.single_keyword( spec="https://drafts.csswg.org/css-scrollbars-1/#scrollbar-width" )} -${helpers.single_keyword( +${helpers.predefined_type( "-moz-user-select", - "auto text none all element elements toggle tri-state -moz-all -moz-text", + "UserSelect", + "computed::UserSelect::Auto", products="gecko", - alias="-webkit-user-select", gecko_ffi_name="mUserSelect", - gecko_enum_prefix="StyleUserSelect", - gecko_strip_moz_prefix=False, - aliases="-moz-none=none", + alias="-webkit-user-select", animation_value_type="discrete", spec="https://drafts.csswg.org/css-ui-4/#propdef-user-select", )} diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 072b78cf377..ad51e87fb2f 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -1354,6 +1354,13 @@ impl ShorthandId { NonCustomPropertyId::from(self).to_nscsspropertyid() } + /// Converts from a nsCSSPropertyID to a ShorthandId. + #[cfg(feature = "gecko")] + #[inline] + pub fn from_nscsspropertyid(prop: nsCSSPropertyID) -> Result { + PropertyId::from_nscsspropertyid(prop)?.as_shorthand().map_err(|_| ()) + } + /// Get the longhand ids that form this shorthand. pub fn longhands(&self) -> NonCustomPropertyIterator { % for property in data.shorthands: diff --git a/components/style/properties/shorthands/svg.mako.rs b/components/style/properties/shorthands/svg.mako.rs index e814a0404c4..b5888948cf8 100644 --- a/components/style/properties/shorthands/svg.mako.rs +++ b/components/style/properties/shorthands/svg.mako.rs @@ -148,21 +148,32 @@ % endfor image.to_css(dest)?; - dest.write_str(" ")?; - mode.to_css(dest)?; - dest.write_str(" ")?; - Position { - horizontal: position_x.clone(), - vertical: position_y.clone() - }.to_css(dest)?; - - if *size != mask_size::single_value::get_initial_specified_value() { - dest.write_str(" / ")?; - size.to_css(dest)?; + if *mode != mask_mode::single_value::get_initial_specified_value() { + dest.write_str(" ")?; + mode.to_css(dest)?; + } + + if *position_x != PositionComponent::zero() || + *position_y != PositionComponent::zero() || + *size != mask_size::single_value::get_initial_specified_value() + { + dest.write_str(" ")?; + Position { + horizontal: position_x.clone(), + vertical: position_y.clone() + }.to_css(dest)?; + + if *size != mask_size::single_value::get_initial_specified_value() { + dest.write_str(" / ")?; + size.to_css(dest)?; + } + } + + if *repeat != mask_repeat::single_value::get_initial_specified_value() { + dest.write_str(" ")?; + repeat.to_css(dest)?; } - dest.write_str(" ")?; - repeat.to_css(dest)?; if *origin != Origin::BorderBox || *clip != Clip::BorderBox { dest.write_str(" ")?; @@ -173,8 +184,10 @@ } } - dest.write_str(" ")?; - composite.to_css(dest)?; + if *composite != mask_composite::single_value::get_initial_specified_value() { + dest.write_str(" ")?; + composite.to_css(dest)?; + } } Ok(()) diff --git a/components/style/values/computed/background.rs b/components/style/values/computed/background.rs index fd1348a9e97..e3fbd17b241 100644 --- a/components/style/values/computed/background.rs +++ b/components/style/values/computed/background.rs @@ -5,12 +5,9 @@ //! Computed types for CSS values related to backgrounds. use crate::values::computed::length::NonNegativeLengthOrPercentageOrAuto; -use crate::values::computed::{Context, ToComputedValue}; use crate::values::generics::background::BackgroundSize as GenericBackgroundSize; -use crate::values::specified::background::BackgroundRepeat as SpecifiedBackgroundRepeat; -use crate::values::specified::background::BackgroundRepeatKeyword; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; + +pub use crate::values::specified::background::BackgroundRepeat; /// A computed value for the `background-size` property. pub type BackgroundSize = GenericBackgroundSize; @@ -24,81 +21,3 @@ impl BackgroundSize { } } } - -/// The computed value of the `background-repeat` property: -/// -/// https://drafts.csswg.org/css-backgrounds/#the-background-repeat -#[derive(Clone, Debug, MallocSizeOf, PartialEq)] -pub struct BackgroundRepeat(pub BackgroundRepeatKeyword, pub BackgroundRepeatKeyword); - -impl BackgroundRepeat { - /// Returns the `repeat repeat` value. - pub fn repeat() -> Self { - BackgroundRepeat( - BackgroundRepeatKeyword::Repeat, - BackgroundRepeatKeyword::Repeat, - ) - } -} - -impl ToCss for BackgroundRepeat { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: Write, - { - match (self.0, self.1) { - (BackgroundRepeatKeyword::Repeat, BackgroundRepeatKeyword::NoRepeat) => { - dest.write_str("repeat-x") - }, - (BackgroundRepeatKeyword::NoRepeat, BackgroundRepeatKeyword::Repeat) => { - dest.write_str("repeat-y") - }, - (horizontal, vertical) => { - horizontal.to_css(dest)?; - if horizontal != vertical { - dest.write_str(" ")?; - vertical.to_css(dest)?; - } - Ok(()) - }, - } - } -} - -impl ToComputedValue for SpecifiedBackgroundRepeat { - type ComputedValue = BackgroundRepeat; - - #[inline] - fn to_computed_value(&self, _: &Context) -> Self::ComputedValue { - match *self { - SpecifiedBackgroundRepeat::RepeatX => BackgroundRepeat( - BackgroundRepeatKeyword::Repeat, - BackgroundRepeatKeyword::NoRepeat, - ), - SpecifiedBackgroundRepeat::RepeatY => BackgroundRepeat( - BackgroundRepeatKeyword::NoRepeat, - BackgroundRepeatKeyword::Repeat, - ), - SpecifiedBackgroundRepeat::Keywords(horizontal, vertical) => { - BackgroundRepeat(horizontal, vertical.unwrap_or(horizontal)) - }, - } - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - // FIXME(emilio): Why can't this just be: - // SpecifiedBackgroundRepeat::Keywords(computed.0, computed.1) - match (computed.0, computed.1) { - (BackgroundRepeatKeyword::Repeat, BackgroundRepeatKeyword::NoRepeat) => { - SpecifiedBackgroundRepeat::RepeatX - }, - (BackgroundRepeatKeyword::NoRepeat, BackgroundRepeatKeyword::Repeat) => { - SpecifiedBackgroundRepeat::RepeatY - }, - (horizontal, vertical) => { - SpecifiedBackgroundRepeat::Keywords(horizontal, Some(vertical)) - }, - } - } -} diff --git a/components/style/values/computed/box.rs b/components/style/values/computed/box.rs index 0cf6ab3e39c..8cf2a63b3c1 100644 --- a/components/style/values/computed/box.rs +++ b/components/style/values/computed/box.rs @@ -11,13 +11,11 @@ use crate::values::generics::box_::Perspective as GenericPerspective; use crate::values::generics::box_::VerticalAlign as GenericVerticalAlign; use crate::values::specified::box_ as specified; -pub use crate::values::specified::box_::{ - AnimationName, Appearance, Contain, Display, OverflowClipBox, -}; +pub use crate::values::specified::box_::{AnimationName, Appearance, BreakBetween, BreakWithin}; pub use crate::values::specified::box_::{Clear as SpecifiedClear, Float as SpecifiedFloat}; -pub use crate::values::specified::box_::{ - OverscrollBehavior, ScrollSnapType, TouchAction, TransitionProperty, WillChange, -}; +pub use crate::values::specified::box_::{Contain, Display, OverflowClipBox}; +pub use crate::values::specified::box_::{OverscrollBehavior, ScrollSnapType}; +pub use crate::values::specified::box_::{TouchAction, TransitionProperty, WillChange}; /// A computed value for the `vertical-align` property. pub type VerticalAlign = GenericVerticalAlign; diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index e24472a8068..0dce1502e92 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -4,10 +4,12 @@ //! Computed values. +use self::transform::DirectionVector; use super::animated::ToAnimatedValue; use super::generics::grid::GridTemplateComponent as GenericGridTemplateComponent; use super::generics::grid::{GridLine as GenericGridLine, TrackBreadth as GenericTrackBreadth}; use super::generics::grid::{TrackList as GenericTrackList, TrackSize as GenericTrackSize}; +use super::generics::transform::IsParallelTo; use super::generics::{GreaterThanOrEqualToOne, NonNegative}; use super::specified; use super::{CSSFloat, CSSInteger}; @@ -40,7 +42,7 @@ pub use self::border::{BorderCornerRadius, BorderRadius, BorderSpacing}; pub use self::border::{BorderImageRepeat, BorderImageSideWidth}; pub use self::border::{BorderImageSlice, BorderImageWidth}; pub use self::box_::{AnimationIterationCount, AnimationName, Contain}; -pub use self::box_::{Appearance, Clear, Float}; +pub use self::box_::{Appearance, BreakBetween, BreakWithin, Clear, Float}; pub use self::box_::{Display, TransitionProperty}; pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize}; pub use self::box_::{ScrollSnapType, TouchAction, VerticalAlign, WillChange}; @@ -84,7 +86,7 @@ pub use self::transform::{Rotate, Scale, Transform, TransformOperation}; pub use self::transform::{TransformOrigin, TransformStyle, Translate}; #[cfg(feature = "gecko")] pub use self::ui::CursorImage; -pub use self::ui::{ColorOrAuto, Cursor, MozForceBrokenImageIcon}; +pub use self::ui::{ColorOrAuto, Cursor, MozForceBrokenImageIcon, UserSelect}; pub use super::specified::{BorderStyle, TextDecorationLine}; pub use super::{Auto, Either, None_}; pub use app_units::Au; @@ -459,6 +461,19 @@ trivial_to_computed_value!(Box); /// A `` value. pub type Number = CSSFloat; +impl IsParallelTo for (Number, Number, Number) { + fn is_parallel_to(&self, vector: &DirectionVector) -> bool { + use euclid::approxeq::ApproxEq; + // If a and b is parallel, the angle between them is 0deg, so + // a x b = |a|*|b|*sin(0)*n = 0 * n, |a x b| == 0. + let self_vector = DirectionVector::new(self.0, self.1, self.2); + self_vector + .cross(*vector) + .square_length() + .approx_eq(&0.0f32) + } +} + /// A wrapper of Number, but the value >= 0. pub type NonNegativeNumber = NonNegative; diff --git a/components/style/values/computed/ui.rs b/components/style/values/computed/ui.rs index 57990d84d79..7d4a5c6a1ce 100644 --- a/components/style/values/computed/ui.rs +++ b/components/style/values/computed/ui.rs @@ -10,7 +10,7 @@ use crate::values::computed::Number; use crate::values::generics::ui as generics; use crate::values::{Auto, Either}; -pub use crate::values::specified::ui::MozForceBrokenImageIcon; +pub use crate::values::specified::ui::{MozForceBrokenImageIcon, UserSelect}; /// auto | pub type ColorOrAuto = Either; diff --git a/components/style/values/generics/transform.rs b/components/style/values/generics/transform.rs index 12cc97fb10f..ef54c511f63 100644 --- a/components/style/values/generics/transform.rs +++ b/components/style/values/generics/transform.rs @@ -538,7 +538,6 @@ pub fn get_normalized_vector_and_angle( SpecifiedValueInfo, ToAnimatedZero, ToComputedValue, - ToCss, )] /// A value of the `Rotate` property /// @@ -552,6 +551,53 @@ pub enum Rotate { Rotate3D(Number, Number, Number, Angle), } +/// A trait to check if the current 3D vector is parallel to the DirectionVector. +/// This is especially for serialization on Rotate. +pub trait IsParallelTo { + /// Returns true if this is parallel to the vector. + fn is_parallel_to(&self, vector: &computed::transform::DirectionVector) -> bool; +} + +impl ToCss for Rotate +where + Number: Copy + ToCss, + Angle: ToCss, + (Number, Number, Number): IsParallelTo, +{ + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: fmt::Write, + { + use crate::values::computed::transform::DirectionVector; + match *self { + Rotate::None => dest.write_str("none"), + Rotate::Rotate(ref angle) => angle.to_css(dest), + Rotate::Rotate3D(x, y, z, ref angle) => { + // If a 3d rotation is specified, the property must serialize with an axis + // specified. If the axis is parallel with the x, y, or z axises, it must + // serialize as the appropriate keyword. + // https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization + let v = (x, y, z); + if v.is_parallel_to(&DirectionVector::new(1., 0., 0.)) { + dest.write_char('x')?; + } else if v.is_parallel_to(&DirectionVector::new(0., 1., 0.)) { + dest.write_char('y')?; + } else if v.is_parallel_to(&DirectionVector::new(0., 0., 1.)) { + dest.write_char('z')?; + } else { + x.to_css(dest)?; + dest.write_char(' ')?; + y.to_css(dest)?; + dest.write_char(' ')?; + z.to_css(dest)?; + } + dest.write_char(' ')?; + angle.to_css(dest) + }, + } + } +} + #[derive( Clone, ComputeSquaredDistance, diff --git a/components/style/values/specified/background.rs b/components/style/values/specified/background.rs index 9aa47fb522c..0c96cf8b707 100644 --- a/components/style/values/specified/background.rs +++ b/components/style/values/specified/background.rs @@ -9,7 +9,8 @@ use crate::values::generics::background::BackgroundSize as GenericBackgroundSize use crate::values::specified::length::NonNegativeLengthOrPercentageOrAuto; use cssparser::Parser; use selectors::parser::SelectorParseErrorKind; -use style_traits::ParseError; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ParseError, ToCss}; /// A specified value for the `background-size` property. pub type BackgroundSize = GenericBackgroundSize; @@ -56,6 +57,7 @@ impl BackgroundSize { ToCss, )] #[allow(missing_docs)] +#[value_info(other_values = "repeat-x,repeat-y")] pub enum BackgroundRepeatKeyword { Repeat, Space, @@ -63,24 +65,45 @@ pub enum BackgroundRepeatKeyword { NoRepeat, } -/// The specified value for the `background-repeat` property. +/// The value of the `background-repeat` property, with `repeat-x` / `repeat-y` +/// represented as the combination of `no-repeat` and `repeat` in the opposite +/// axes. /// /// https://drafts.csswg.org/css-backgrounds/#the-background-repeat -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss)] -pub enum BackgroundRepeat { - /// `repeat-x` - RepeatX, - /// `repeat-y` - RepeatY, - /// `[repeat | space | round | no-repeat]{1,2}` - Keywords(BackgroundRepeatKeyword, Option), -} +#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue)] +pub struct BackgroundRepeat(pub BackgroundRepeatKeyword, pub BackgroundRepeatKeyword); impl BackgroundRepeat { - /// Returns the `repeat` value. - #[inline] + /// Returns the `repeat repeat` value. pub fn repeat() -> Self { - BackgroundRepeat::Keywords(BackgroundRepeatKeyword::Repeat, None) + BackgroundRepeat( + BackgroundRepeatKeyword::Repeat, + BackgroundRepeatKeyword::Repeat, + ) + } +} + +impl ToCss for BackgroundRepeat { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + match (self.0, self.1) { + (BackgroundRepeatKeyword::Repeat, BackgroundRepeatKeyword::NoRepeat) => { + dest.write_str("repeat-x") + }, + (BackgroundRepeatKeyword::NoRepeat, BackgroundRepeatKeyword::Repeat) => { + dest.write_str("repeat-y") + }, + (horizontal, vertical) => { + horizontal.to_css(dest)?; + if horizontal != vertical { + dest.write_str(" ")?; + vertical.to_css(dest)?; + } + Ok(()) + }, + } } } @@ -92,8 +115,12 @@ impl Parse for BackgroundRepeat { let ident = input.expect_ident_cloned()?; match_ignore_ascii_case! { &ident, - "repeat-x" => return Ok(BackgroundRepeat::RepeatX), - "repeat-y" => return Ok(BackgroundRepeat::RepeatY), + "repeat-x" => { + return Ok(BackgroundRepeat(BackgroundRepeatKeyword::Repeat, BackgroundRepeatKeyword::NoRepeat)); + }, + "repeat-y" => { + return Ok(BackgroundRepeat(BackgroundRepeatKeyword::NoRepeat, BackgroundRepeatKeyword::Repeat)); + }, _ => {}, } @@ -107,6 +134,6 @@ impl Parse for BackgroundRepeat { }; let vertical = input.try(BackgroundRepeatKeyword::parse).ok(); - Ok(BackgroundRepeat::Keywords(horizontal, vertical)) + Ok(BackgroundRepeat(horizontal, vertical.unwrap_or(horizontal))) } } diff --git a/components/style/values/specified/basic_shape.rs b/components/style/values/specified/basic_shape.rs index d394d8881dd..252542df794 100644 --- a/components/style/values/specified/basic_shape.rs +++ b/components/style/values/specified/basic_shape.rs @@ -8,20 +8,17 @@ //! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape use crate::parser::{Parse, ParserContext}; -use crate::values::computed::Percentage; use crate::values::generics::basic_shape as generic; use crate::values::generics::basic_shape::{GeometryBox, Path, PolygonCoord}; use crate::values::generics::basic_shape::{ShapeBox, ShapeSource}; use crate::values::generics::rect::Rect; use crate::values::specified::border::BorderRadius; use crate::values::specified::image::Image; -use crate::values::specified::position::{HorizontalPosition, Position, PositionComponent}; -use crate::values::specified::position::{Side, VerticalPosition}; +use crate::values::specified::position::{HorizontalPosition, Position, VerticalPosition}; use crate::values::specified::url::SpecifiedUrl; use crate::values::specified::LengthOrPercentage; use crate::values::specified::SVGPathData; use cssparser::Parser; -use std::borrow::Cow; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; @@ -249,7 +246,7 @@ impl ToCss for Circle { } dest.write_str("at ")?; - serialize_basicshape_position(&self.position, dest)?; + self.position.to_css(dest)?; dest.write_str(")") } } @@ -305,7 +302,7 @@ impl ToCss for Ellipse { } dest.write_str("at ")?; - serialize_basicshape_position(&self.position, dest)?; + self.position.to_css(dest)?; dest.write_str(")") } } @@ -326,82 +323,6 @@ impl Parse for ShapeRadius { } } -/// -/// -/// Positions get serialized differently with basic shapes. Keywords -/// are converted to percentages where possible. Only the two or four -/// value forms are used. In case of two keyword-percentage pairs, -/// the keywords are folded into the percentages -fn serialize_basicshape_position(position: &Position, dest: &mut CssWriter) -> fmt::Result -where - W: Write, -{ - fn to_keyword_and_lop(component: &PositionComponent) -> (S, Cow) - where - S: Copy + Side, - { - match *component { - PositionComponent::Center => ( - S::start(), - Cow::Owned(LengthOrPercentage::Percentage(Percentage(0.5))), - ), - PositionComponent::Side(keyword, None) => { - // left | top => 0% - // right | bottom => 100% - let p = if keyword.is_start() { 0. } else { 1. }; - ( - S::start(), - Cow::Owned(LengthOrPercentage::Percentage(Percentage(p))), - ) - }, - PositionComponent::Side(keyword, Some(ref lop)) if !keyword.is_start() => { - if let LengthOrPercentage::Percentage(p) = *to_non_zero_length(lop) { - ( - S::start(), - Cow::Owned(LengthOrPercentage::Percentage(Percentage(1. - p.0))), - ) - } else { - (keyword, Cow::Borrowed(lop)) - } - }, - PositionComponent::Length(ref lop) | PositionComponent::Side(_, Some(ref lop)) => { - (S::start(), to_non_zero_length(lop)) - }, - } - } - - fn to_non_zero_length(lop: &LengthOrPercentage) -> Cow { - match *lop { - LengthOrPercentage::Length(ref l) if l.is_zero() => { - Cow::Owned(LengthOrPercentage::Percentage(Percentage(0.))) - }, - _ => Cow::Borrowed(lop), - } - } - - fn write_pair(a: &A, b: &B, dest: &mut CssWriter) -> fmt::Result - where - A: ToCss, - B: ToCss, - W: Write, - { - a.to_css(dest)?; - dest.write_str(" ")?; - b.to_css(dest) - } - - let (x_pos, x_lop) = to_keyword_and_lop(&position.horizontal); - let (y_pos, y_lop) = to_keyword_and_lop(&position.vertical); - - if x_pos.is_start() && y_pos.is_start() { - return write_pair(&*x_lop, &*y_lop, dest); - } - - write_pair(&x_pos, &*x_lop, dest)?; - dest.write_str(" ")?; - write_pair(&y_pos, &*y_lop, dest) -} - impl Parse for Polygon { fn parse<'i, 't>( context: &ParserContext, diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index a5761d3f9b2..5229f839eaf 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -52,9 +52,6 @@ fn moz_box_display_values_enabled(context: &ParserContext) -> bool { /// If you change it, make sure to take a look at the /// FrameConstructionDataByDisplay stuff (both the XUL and non-XUL version), and /// ensure it's still correct! -/// -/// Also, when you change this from Gecko you may need to regenerate the -/// C++-side bindings (see components/style/cbindgen.toml). #[allow(missing_docs)] #[derive( Clone, @@ -946,9 +943,6 @@ pub enum Resize { /// The value for the `appearance` property. /// /// https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-appearance -/// -/// NOTE(emilio): When changing this you may want to regenerate the C++ bindings -/// (see components/style/cbindgen.toml) #[allow(missing_docs)] #[derive( Clone, @@ -1295,3 +1289,52 @@ pub enum Appearance { #[css(skip)] Count, } + +/// A kind of break between two boxes. +/// +/// https://drafts.csswg.org/css-break/#break-between +#[allow(missing_docs)] +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToCss, + ToComputedValue, +)] +#[repr(u8)] +pub enum BreakBetween { + Auto, + Always, + Avoid, + Left, + Right, +} + +/// A kind of break within a box. +/// +/// https://drafts.csswg.org/css-break/#break-within +#[allow(missing_docs)] +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToCss, + ToComputedValue, +)] +#[repr(u8)] +pub enum BreakWithin { + Auto, + Avoid, +} diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 3b7a3d0e04e..5ffa947ef35 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -6,9 +6,11 @@ //! //! TODO(emilio): Enhance docs. +use super::computed::transform::DirectionVector; use super::computed::{Context, ToComputedValue}; use super::generics::grid::{GridLine as GenericGridLine, TrackBreadth as GenericTrackBreadth}; use super::generics::grid::{TrackList as GenericTrackList, TrackSize as GenericTrackSize}; +use super::generics::transform::IsParallelTo; use super::generics::{GreaterThanOrEqualToOne, NonNegative}; use super::{Auto, CSSFloat, CSSInteger, Either}; use crate::context::QuirksMode; @@ -34,7 +36,7 @@ pub use self::border::{BorderCornerRadius, BorderImageSlice, BorderImageWidth}; pub use self::border::{BorderImageRepeat, BorderImageSideWidth}; pub use self::border::{BorderRadius, BorderSideWidth, BorderSpacing}; pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display}; -pub use self::box_::{Appearance, Clear, Float}; +pub use self::box_::{Appearance, BreakBetween, BreakWithin, Clear, Float}; pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize}; pub use self::box_::{ScrollSnapType, TouchAction, TransitionProperty, VerticalAlign, WillChange}; pub use self::color::{Color, ColorPropertyValue, RGBAColor}; @@ -82,7 +84,7 @@ pub use self::transform::{Rotate, Scale, Transform}; pub use self::transform::{TransformOrigin, TransformStyle, Translate}; #[cfg(feature = "gecko")] pub use self::ui::CursorImage; -pub use self::ui::{ColorOrAuto, Cursor, MozForceBrokenImageIcon}; +pub use self::ui::{ColorOrAuto, Cursor, MozForceBrokenImageIcon, UserSelect}; pub use super::generics::grid::GridTemplateComponent as GenericGridTemplateComponent; #[cfg(feature = "gecko")] @@ -292,6 +294,19 @@ impl ToCss for Number { } } +impl IsParallelTo for (Number, Number, Number) { + fn is_parallel_to(&self, vector: &DirectionVector) -> bool { + use euclid::approxeq::ApproxEq; + // If a and b is parallel, the angle between them is 0deg, so + // a x b = |a|*|b|*sin(0)*n = 0 * n, |a x b| == 0. + let self_vector = DirectionVector::new(self.0.get(), self.1.get(), self.2.get()); + self_vector + .cross(*vector) + .square_length() + .approx_eq(&0.0f32) + } +} + impl SpecifiedValueInfo for Number {} impl From for f32 { diff --git a/components/style/values/specified/transform.rs b/components/style/values/specified/transform.rs index c1ffe202a79..650722b249f 100644 --- a/components/style/values/specified/transform.rs +++ b/components/style/values/specified/transform.rs @@ -357,17 +357,38 @@ impl Parse for Rotate { return Ok(generic::Rotate::None); } - if let Ok(rx) = input.try(|i| Number::parse(context, i)) { - // 'rotate: {3} ' - let ry = Number::parse(context, input)?; - let rz = Number::parse(context, input)?; - let angle = specified::Angle::parse(context, input)?; - return Ok(generic::Rotate::Rotate3D(rx, ry, rz, angle)); - } + // Parse or [ x | y | z | {3} ] && . + // + // The rotate axis and angle could be in any order, so we parse angle twice to cover + // two cases. i.e. `{3} ` or ` {3}` + let angle = input.try(|i| specified::Angle::parse(context, i)).ok(); + let axis = input + .try(|i| { + Ok(try_match_ident_ignore_ascii_case! { i, + "x" => (Number::new(1.), Number::new(0.), Number::new(0.)), + "y" => (Number::new(0.), Number::new(1.), Number::new(0.)), + "z" => (Number::new(0.), Number::new(0.), Number::new(1.)), + }) + }) + .or_else(|_: ParseError| -> Result<_, ParseError> { + input.try(|i| { + Ok(( + Number::parse(context, i)?, + Number::parse(context, i)?, + Number::parse(context, i)?, + )) + }) + }) + .ok(); + let angle = match angle { + Some(a) => a, + None => specified::Angle::parse(context, input)?, + }; - // 'rotate: ' - let angle = specified::Angle::parse(context, input)?; - Ok(generic::Rotate::Rotate(angle)) + Ok(match axis { + Some((x, y, z)) => generic::Rotate::Rotate3D(x, y, z, angle), + None => generic::Rotate::Rotate(angle), + }) } } diff --git a/components/style/values/specified/ui.rs b/components/style/values/specified/ui.rs index 5adfcaa7f70..067ce312102 100644 --- a/components/style/values/specified/ui.rs +++ b/components/style/values/specified/ui.rs @@ -140,3 +140,43 @@ impl Parse for ScrollbarColor { }) } } + +fn in_ua_sheet(context: &ParserContext) -> bool { + use crate::stylesheets::Origin; + context.stylesheet_origin == Origin::UserAgent +} + +/// The specified value for the `user-select` property. +/// +/// https://drafts.csswg.org/css-ui-4/#propdef-user-select +#[allow(missing_docs)] +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, +)] +#[repr(u8)] +pub enum UserSelect { + Auto, + Text, + #[parse(aliases = "-moz-none")] + None, + /// Force selection of all children, unless an ancestor has `none` set. + All, + /// Like `text`, except that it won't get overridden by ancestors having + /// `all`. + /// + /// FIXME(emilio): This only has one use in contenteditable.css, can we find + /// a better way to do this? + /// + /// See bug 1181130. + #[parse(condition = "in_ua_sheet")] + MozText, +} diff --git a/tests/wpt/metadata/css/css-transforms/parsing/rotate-parsing-valid.html.ini b/tests/wpt/metadata/css/css-transforms/parsing/rotate-parsing-valid.html.ini deleted file mode 100644 index bc107c5939d..00000000000 --- a/tests/wpt/metadata/css/css-transforms/parsing/rotate-parsing-valid.html.ini +++ /dev/null @@ -1,31 +0,0 @@ -[rotate-parsing-valid.html] - [e.style['rotate'\] = "400grad 100 200 300" should set the property value] - expected: FAIL - - [e.style['rotate'\] = "x 400grad" should set the property value] - expected: FAIL - - [e.style['rotate'\] = "400grad x" should set the property value] - expected: FAIL - - [e.style['rotate'\] = "y 400grad" should set the property value] - expected: FAIL - - [e.style['rotate'\] = "400grad y" should set the property value] - expected: FAIL - - [e.style['rotate'\] = "z 400grad" should set the property value] - expected: FAIL - - [e.style['rotate'\] = "400grad z" should set the property value] - expected: FAIL - - [e.style['rotate'\] = "1 0 0 400grad" should set the property value] - expected: FAIL - - [e.style['rotate'\] = "0 1 0 400grad" should set the property value] - expected: FAIL - - [e.style['rotate'\] = "0 0 1 400grad" should set the property value] - expected: FAIL -