tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

transform.rs (23845B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
      4 
      5 //! Specified types for CSS values that are related to transformations.
      6 
      7 use crate::derives::*;
      8 use crate::parser::{Parse, ParserContext};
      9 use crate::values::computed::{Context, LengthPercentage as ComputedLengthPercentage};
     10 use crate::values::computed::{Percentage as ComputedPercentage, ToComputedValue};
     11 use crate::values::generics::transform as generic;
     12 use crate::values::generics::transform::{Matrix, Matrix3D};
     13 use crate::values::specified::position::{
     14    HorizontalPositionKeyword, Side, VerticalPositionKeyword,
     15 };
     16 use crate::values::specified::{
     17    self, AllowQuirks, Angle, Integer, Length, LengthPercentage, Number, NumberOrPercentage,
     18 };
     19 use crate::Zero;
     20 use cssparser::{match_ignore_ascii_case, Parser};
     21 use style_traits::{ParseError, StyleParseErrorKind};
     22 
     23 pub use crate::values::generics::transform::TransformStyle;
     24 
     25 /// A single operation in a specified CSS `transform`
     26 pub type TransformOperation =
     27    generic::TransformOperation<Angle, Number, Length, Integer, LengthPercentage>;
     28 
     29 /// A specified CSS `transform`
     30 pub type Transform = generic::Transform<TransformOperation>;
     31 
     32 /// The specified value of a CSS `<transform-origin>`
     33 pub type TransformOrigin = generic::TransformOrigin<
     34    OriginComponent<HorizontalPositionKeyword>,
     35    OriginComponent<VerticalPositionKeyword>,
     36    Length,
     37 >;
     38 
     39 #[cfg(feature = "gecko")]
     40 fn all_transform_boxes_are_enabled(_context: &ParserContext) -> bool {
     41    true
     42 }
     43 
     44 #[cfg(feature = "servo")]
     45 fn all_transform_boxes_are_enabled(_context: &ParserContext) -> bool {
     46    false
     47 }
     48 
     49 /// The specified value of `transform-box`.
     50 /// https://drafts.csswg.org/css-transforms-1/#transform-box
     51 // Note: Once we ship everything, we can drop this and just use single_keyword for tranform-box.
     52 #[allow(missing_docs)]
     53 #[derive(
     54    Animate,
     55    Clone,
     56    ComputeSquaredDistance,
     57    Copy,
     58    Debug,
     59    Deserialize,
     60    MallocSizeOf,
     61    Parse,
     62    PartialEq,
     63    Serialize,
     64    SpecifiedValueInfo,
     65    ToAnimatedValue,
     66    ToComputedValue,
     67    ToCss,
     68    ToResolvedValue,
     69    ToShmem,
     70    ToTyped,
     71 )]
     72 #[repr(u8)]
     73 pub enum TransformBox {
     74    #[parse(condition = "all_transform_boxes_are_enabled")]
     75    ContentBox,
     76    BorderBox,
     77    FillBox,
     78    #[parse(condition = "all_transform_boxes_are_enabled")]
     79    StrokeBox,
     80    ViewBox,
     81 }
     82 
     83 impl TransformOrigin {
     84    /// Returns the initial specified value for `transform-origin`.
     85    #[inline]
     86    pub fn initial_value() -> Self {
     87        Self::new(
     88            OriginComponent::Length(LengthPercentage::Percentage(ComputedPercentage(0.5))),
     89            OriginComponent::Length(LengthPercentage::Percentage(ComputedPercentage(0.5))),
     90            Length::zero(),
     91        )
     92    }
     93 
     94    /// Returns the `0 0` value.
     95    pub fn zero_zero() -> Self {
     96        Self::new(
     97            OriginComponent::Length(LengthPercentage::zero()),
     98            OriginComponent::Length(LengthPercentage::zero()),
     99            Length::zero(),
    100        )
    101    }
    102 }
    103 
    104 /// Whether to allow unitless values for perspective in prefixed transform properties.
    105 ///
    106 /// See: https://github.com/whatwg/compat/issues/100
    107 #[allow(missing_docs)]
    108 pub enum AllowUnitlessPerspective {
    109    No,
    110    Yes,
    111 }
    112 
    113 impl Transform {
    114    /// Parse the transform property value, allowing unitless perspective values.
    115    ///
    116    /// This is used for `-webkit-transform` which allows unitless values for perspective.
    117    #[inline]
    118    pub(crate) fn parse_legacy<'i, 't>(
    119        context: &ParserContext,
    120        input: &mut Parser<'i, 't>,
    121    ) -> Result<Self, ParseError<'i>> {
    122        Self::parse_internal(context, input, AllowUnitlessPerspective::Yes)
    123    }
    124    /// Internal parse function for deciding if we wish to accept prefixed values or not
    125    ///
    126    /// `transform` allows unitless zero angles as an exception, see:
    127    /// https://github.com/w3c/csswg-drafts/issues/1162
    128    fn parse_internal<'i, 't>(
    129        context: &ParserContext,
    130        input: &mut Parser<'i, 't>,
    131        allow_unitless_perspective: AllowUnitlessPerspective,
    132    ) -> Result<Self, ParseError<'i>> {
    133        use style_traits::{Separator, Space};
    134 
    135        if input
    136            .try_parse(|input| input.expect_ident_matching("none"))
    137            .is_ok()
    138        {
    139            return Ok(generic::Transform::none());
    140        }
    141 
    142        Ok(generic::Transform(
    143            Space::parse(input, |input| {
    144                let function = input.expect_function()?.clone();
    145                input.parse_nested_block(|input| {
    146                    let location = input.current_source_location();
    147                    let result = match_ignore_ascii_case! { &function,
    148                        "matrix" => {
    149                            let a = Number::parse(context, input)?;
    150                            input.expect_comma()?;
    151                            let b = Number::parse(context, input)?;
    152                            input.expect_comma()?;
    153                            let c = Number::parse(context, input)?;
    154                            input.expect_comma()?;
    155                            let d = Number::parse(context, input)?;
    156                            input.expect_comma()?;
    157                            // Standard matrix parsing.
    158                            let e = Number::parse(context, input)?;
    159                            input.expect_comma()?;
    160                            let f = Number::parse(context, input)?;
    161                            Ok(generic::TransformOperation::Matrix(Matrix { a, b, c, d, e, f }))
    162                        },
    163                        "matrix3d" => {
    164                            let m11 = Number::parse(context, input)?;
    165                            input.expect_comma()?;
    166                            let m12 = Number::parse(context, input)?;
    167                            input.expect_comma()?;
    168                            let m13 = Number::parse(context, input)?;
    169                            input.expect_comma()?;
    170                            let m14 = Number::parse(context, input)?;
    171                            input.expect_comma()?;
    172                            let m21 = Number::parse(context, input)?;
    173                            input.expect_comma()?;
    174                            let m22 = Number::parse(context, input)?;
    175                            input.expect_comma()?;
    176                            let m23 = Number::parse(context, input)?;
    177                            input.expect_comma()?;
    178                            let m24 = Number::parse(context, input)?;
    179                            input.expect_comma()?;
    180                            let m31 = Number::parse(context, input)?;
    181                            input.expect_comma()?;
    182                            let m32 = Number::parse(context, input)?;
    183                            input.expect_comma()?;
    184                            let m33 = Number::parse(context, input)?;
    185                            input.expect_comma()?;
    186                            let m34 = Number::parse(context, input)?;
    187                            input.expect_comma()?;
    188                            // Standard matrix3d parsing.
    189                            let m41 = Number::parse(context, input)?;
    190                            input.expect_comma()?;
    191                            let m42 = Number::parse(context, input)?;
    192                            input.expect_comma()?;
    193                            let m43 = Number::parse(context, input)?;
    194                            input.expect_comma()?;
    195                            let m44 = Number::parse(context, input)?;
    196                            Ok(generic::TransformOperation::Matrix3D(Matrix3D {
    197                                m11, m12, m13, m14,
    198                                m21, m22, m23, m24,
    199                                m31, m32, m33, m34,
    200                                m41, m42, m43, m44,
    201                            }))
    202                        },
    203                        "translate" => {
    204                            let sx = specified::LengthPercentage::parse(context, input)?;
    205                            if input.try_parse(|input| input.expect_comma()).is_ok() {
    206                                let sy = specified::LengthPercentage::parse(context, input)?;
    207                                Ok(generic::TransformOperation::Translate(sx, sy))
    208                            } else {
    209                                Ok(generic::TransformOperation::Translate(sx, Zero::zero()))
    210                            }
    211                        },
    212                        "translatex" => {
    213                            let tx = specified::LengthPercentage::parse(context, input)?;
    214                            Ok(generic::TransformOperation::TranslateX(tx))
    215                        },
    216                        "translatey" => {
    217                            let ty = specified::LengthPercentage::parse(context, input)?;
    218                            Ok(generic::TransformOperation::TranslateY(ty))
    219                        },
    220                        "translatez" => {
    221                            let tz = specified::Length::parse(context, input)?;
    222                            Ok(generic::TransformOperation::TranslateZ(tz))
    223                        },
    224                        "translate3d" => {
    225                            let tx = specified::LengthPercentage::parse(context, input)?;
    226                            input.expect_comma()?;
    227                            let ty = specified::LengthPercentage::parse(context, input)?;
    228                            input.expect_comma()?;
    229                            let tz = specified::Length::parse(context, input)?;
    230                            Ok(generic::TransformOperation::Translate3D(tx, ty, tz))
    231                        },
    232                        "scale" => {
    233                            let sx = NumberOrPercentage::parse(context, input)?.to_number();
    234                            if input.try_parse(|input| input.expect_comma()).is_ok() {
    235                                let sy = NumberOrPercentage::parse(context, input)?.to_number();
    236                                Ok(generic::TransformOperation::Scale(sx, sy))
    237                            } else {
    238                                Ok(generic::TransformOperation::Scale(sx, sx))
    239                            }
    240                        },
    241                        "scalex" => {
    242                            let sx = NumberOrPercentage::parse(context, input)?.to_number();
    243                            Ok(generic::TransformOperation::ScaleX(sx))
    244                        },
    245                        "scaley" => {
    246                            let sy = NumberOrPercentage::parse(context, input)?.to_number();
    247                            Ok(generic::TransformOperation::ScaleY(sy))
    248                        },
    249                        "scalez" => {
    250                            let sz = NumberOrPercentage::parse(context, input)?.to_number();
    251                            Ok(generic::TransformOperation::ScaleZ(sz))
    252                        },
    253                        "scale3d" => {
    254                            let sx = NumberOrPercentage::parse(context, input)?.to_number();
    255                            input.expect_comma()?;
    256                            let sy = NumberOrPercentage::parse(context, input)?.to_number();
    257                            input.expect_comma()?;
    258                            let sz = NumberOrPercentage::parse(context, input)?.to_number();
    259                            Ok(generic::TransformOperation::Scale3D(sx, sy, sz))
    260                        },
    261                        "rotate" => {
    262                            let theta = specified::Angle::parse_with_unitless(context, input)?;
    263                            Ok(generic::TransformOperation::Rotate(theta))
    264                        },
    265                        "rotatex" => {
    266                            let theta = specified::Angle::parse_with_unitless(context, input)?;
    267                            Ok(generic::TransformOperation::RotateX(theta))
    268                        },
    269                        "rotatey" => {
    270                            let theta = specified::Angle::parse_with_unitless(context, input)?;
    271                            Ok(generic::TransformOperation::RotateY(theta))
    272                        },
    273                        "rotatez" => {
    274                            let theta = specified::Angle::parse_with_unitless(context, input)?;
    275                            Ok(generic::TransformOperation::RotateZ(theta))
    276                        },
    277                        "rotate3d" => {
    278                            let ax = Number::parse(context, input)?;
    279                            input.expect_comma()?;
    280                            let ay = Number::parse(context, input)?;
    281                            input.expect_comma()?;
    282                            let az = Number::parse(context, input)?;
    283                            input.expect_comma()?;
    284                            let theta = specified::Angle::parse_with_unitless(context, input)?;
    285                            // TODO(gw): Check that the axis can be normalized.
    286                            Ok(generic::TransformOperation::Rotate3D(ax, ay, az, theta))
    287                        },
    288                        "skew" => {
    289                            let ax = specified::Angle::parse_with_unitless(context, input)?;
    290                            if input.try_parse(|input| input.expect_comma()).is_ok() {
    291                                let ay = specified::Angle::parse_with_unitless(context, input)?;
    292                                Ok(generic::TransformOperation::Skew(ax, ay))
    293                            } else {
    294                                Ok(generic::TransformOperation::Skew(ax, Zero::zero()))
    295                            }
    296                        },
    297                        "skewx" => {
    298                            let theta = specified::Angle::parse_with_unitless(context, input)?;
    299                            Ok(generic::TransformOperation::SkewX(theta))
    300                        },
    301                        "skewy" => {
    302                            let theta = specified::Angle::parse_with_unitless(context, input)?;
    303                            Ok(generic::TransformOperation::SkewY(theta))
    304                        },
    305                        "perspective" => {
    306                            let p = match input.try_parse(|input| {
    307                                if matches!(allow_unitless_perspective, AllowUnitlessPerspective::Yes) {
    308                                    specified::Length::parse_non_negative_quirky(context, input, AllowQuirks::Always)
    309                                } else {
    310                                    specified::Length::parse_non_negative(context, input)
    311                                }
    312                            }) {
    313                                Ok(p) => generic::PerspectiveFunction::Length(p),
    314                                Err(..) => {
    315                                    input.expect_ident_matching("none")?;
    316                                    generic::PerspectiveFunction::None
    317                                }
    318                            };
    319                            Ok(generic::TransformOperation::Perspective(p))
    320                        },
    321                        _ => Err(()),
    322                    };
    323                    result.map_err(|()| {
    324                        location.new_custom_error(StyleParseErrorKind::UnexpectedFunction(
    325                            function.clone(),
    326                        ))
    327                    })
    328                })
    329            })?
    330            .into(),
    331        ))
    332    }
    333 }
    334 
    335 impl Parse for Transform {
    336    fn parse<'i, 't>(
    337        context: &ParserContext,
    338        input: &mut Parser<'i, 't>,
    339    ) -> Result<Self, ParseError<'i>> {
    340        Transform::parse_internal(context, input, AllowUnitlessPerspective::No)
    341    }
    342 }
    343 
    344 /// The specified value of a component of a CSS `<transform-origin>`.
    345 #[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
    346 pub enum OriginComponent<S> {
    347    /// `center`
    348    Center,
    349    /// `<length-percentage>`
    350    Length(LengthPercentage),
    351    /// `<side>`
    352    Side(S),
    353 }
    354 
    355 impl Parse for TransformOrigin {
    356    fn parse<'i, 't>(
    357        context: &ParserContext,
    358        input: &mut Parser<'i, 't>,
    359    ) -> Result<Self, ParseError<'i>> {
    360        let parse_depth = |input: &mut Parser| {
    361            input
    362                .try_parse(|i| Length::parse(context, i))
    363                .unwrap_or(Length::zero())
    364        };
    365        match input.try_parse(|i| OriginComponent::parse(context, i)) {
    366            Ok(x_origin @ OriginComponent::Center) => {
    367                if let Ok(y_origin) = input.try_parse(|i| OriginComponent::parse(context, i)) {
    368                    let depth = parse_depth(input);
    369                    return Ok(Self::new(x_origin, y_origin, depth));
    370                }
    371                let y_origin = OriginComponent::Center;
    372                if let Ok(x_keyword) = input.try_parse(HorizontalPositionKeyword::parse) {
    373                    let x_origin = OriginComponent::Side(x_keyword);
    374                    let depth = parse_depth(input);
    375                    return Ok(Self::new(x_origin, y_origin, depth));
    376                }
    377                let depth = Length::from_px(0.);
    378                return Ok(Self::new(x_origin, y_origin, depth));
    379            },
    380            Ok(x_origin) => {
    381                if let Ok(y_origin) = input.try_parse(|i| OriginComponent::parse(context, i)) {
    382                    let depth = parse_depth(input);
    383                    return Ok(Self::new(x_origin, y_origin, depth));
    384                }
    385                let y_origin = OriginComponent::Center;
    386                let depth = Length::from_px(0.);
    387                return Ok(Self::new(x_origin, y_origin, depth));
    388            },
    389            Err(_) => {},
    390        }
    391        let y_keyword = VerticalPositionKeyword::parse(input)?;
    392        let y_origin = OriginComponent::Side(y_keyword);
    393        if let Ok(x_keyword) = input.try_parse(HorizontalPositionKeyword::parse) {
    394            let x_origin = OriginComponent::Side(x_keyword);
    395            let depth = parse_depth(input);
    396            return Ok(Self::new(x_origin, y_origin, depth));
    397        }
    398        if input
    399            .try_parse(|i| i.expect_ident_matching("center"))
    400            .is_ok()
    401        {
    402            let x_origin = OriginComponent::Center;
    403            let depth = parse_depth(input);
    404            return Ok(Self::new(x_origin, y_origin, depth));
    405        }
    406        let x_origin = OriginComponent::Center;
    407        let depth = Length::from_px(0.);
    408        Ok(Self::new(x_origin, y_origin, depth))
    409    }
    410 }
    411 
    412 impl<S> ToComputedValue for OriginComponent<S>
    413 where
    414    S: Side,
    415 {
    416    type ComputedValue = ComputedLengthPercentage;
    417 
    418    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
    419        match *self {
    420            OriginComponent::Center => {
    421                ComputedLengthPercentage::new_percent(ComputedPercentage(0.5))
    422            },
    423            OriginComponent::Length(ref length) => length.to_computed_value(context),
    424            OriginComponent::Side(ref keyword) => {
    425                let p = ComputedPercentage(if keyword.is_start() { 0. } else { 1. });
    426                ComputedLengthPercentage::new_percent(p)
    427            },
    428        }
    429    }
    430 
    431    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
    432        OriginComponent::Length(ToComputedValue::from_computed_value(computed))
    433    }
    434 }
    435 
    436 impl<S> OriginComponent<S> {
    437    /// `0%`
    438    pub fn zero() -> Self {
    439        OriginComponent::Length(LengthPercentage::Percentage(ComputedPercentage::zero()))
    440    }
    441 }
    442 
    443 /// A specified CSS `rotate`
    444 pub type Rotate = generic::Rotate<Number, Angle>;
    445 
    446 impl Parse for Rotate {
    447    fn parse<'i, 't>(
    448        context: &ParserContext,
    449        input: &mut Parser<'i, 't>,
    450    ) -> Result<Self, ParseError<'i>> {
    451        if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
    452            return Ok(generic::Rotate::None);
    453        }
    454 
    455        // Parse <angle> or [ x | y | z | <number>{3} ] && <angle>.
    456        //
    457        // The rotate axis and angle could be in any order, so we parse angle twice to cover
    458        // two cases. i.e. `<number>{3} <angle>` or `<angle> <number>{3}`
    459        let angle = input
    460            .try_parse(|i| specified::Angle::parse(context, i))
    461            .ok();
    462        let axis = input
    463            .try_parse(|i| {
    464                Ok(try_match_ident_ignore_ascii_case! { i,
    465                    "x" => (Number::new(1.), Number::new(0.), Number::new(0.)),
    466                    "y" => (Number::new(0.), Number::new(1.), Number::new(0.)),
    467                    "z" => (Number::new(0.), Number::new(0.), Number::new(1.)),
    468                })
    469            })
    470            .or_else(|_: ParseError| -> Result<_, ParseError> {
    471                input.try_parse(|i| {
    472                    Ok((
    473                        Number::parse(context, i)?,
    474                        Number::parse(context, i)?,
    475                        Number::parse(context, i)?,
    476                    ))
    477                })
    478            })
    479            .ok();
    480        let angle = match angle {
    481            Some(a) => a,
    482            None => specified::Angle::parse(context, input)?,
    483        };
    484 
    485        Ok(match axis {
    486            Some((x, y, z)) => generic::Rotate::Rotate3D(x, y, z, angle),
    487            None => generic::Rotate::Rotate(angle),
    488        })
    489    }
    490 }
    491 
    492 /// A specified CSS `translate`
    493 pub type Translate = generic::Translate<LengthPercentage, Length>;
    494 
    495 impl Parse for Translate {
    496    fn parse<'i, 't>(
    497        context: &ParserContext,
    498        input: &mut Parser<'i, 't>,
    499    ) -> Result<Self, ParseError<'i>> {
    500        if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
    501            return Ok(generic::Translate::None);
    502        }
    503 
    504        let tx = specified::LengthPercentage::parse(context, input)?;
    505        if let Ok(ty) = input.try_parse(|i| specified::LengthPercentage::parse(context, i)) {
    506            if let Ok(tz) = input.try_parse(|i| specified::Length::parse(context, i)) {
    507                // 'translate: <length-percentage> <length-percentage> <length>'
    508                return Ok(generic::Translate::Translate(tx, ty, tz));
    509            }
    510 
    511            // translate: <length-percentage> <length-percentage>'
    512            return Ok(generic::Translate::Translate(
    513                tx,
    514                ty,
    515                specified::Length::zero(),
    516            ));
    517        }
    518 
    519        // 'translate: <length-percentage> '
    520        Ok(generic::Translate::Translate(
    521            tx,
    522            specified::LengthPercentage::zero(),
    523            specified::Length::zero(),
    524        ))
    525    }
    526 }
    527 
    528 /// A specified CSS `scale`
    529 pub type Scale = generic::Scale<Number>;
    530 
    531 impl Parse for Scale {
    532    /// Scale accepts <number> | <percentage>, so we parse it as NumberOrPercentage,
    533    /// and then convert into an Number if it's a Percentage.
    534    /// https://github.com/w3c/csswg-drafts/pull/4396
    535    fn parse<'i, 't>(
    536        context: &ParserContext,
    537        input: &mut Parser<'i, 't>,
    538    ) -> Result<Self, ParseError<'i>> {
    539        if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
    540            return Ok(generic::Scale::None);
    541        }
    542 
    543        let sx = NumberOrPercentage::parse(context, input)?.to_number();
    544        if let Ok(sy) = input.try_parse(|i| NumberOrPercentage::parse(context, i)) {
    545            let sy = sy.to_number();
    546            if let Ok(sz) = input.try_parse(|i| NumberOrPercentage::parse(context, i)) {
    547                // 'scale: <number> <number> <number>'
    548                return Ok(generic::Scale::Scale(sx, sy, sz.to_number()));
    549            }
    550 
    551            // 'scale: <number> <number>'
    552            return Ok(generic::Scale::Scale(sx, sy, Number::new(1.0)));
    553        }
    554 
    555        // 'scale: <number>'
    556        Ok(generic::Scale::Scale(sx, sx, Number::new(1.0)))
    557    }
    558 }