diff --git a/src/context.rs b/src/context.rs index 9cb7a876..ad3c08e1 100644 --- a/src/context.rs +++ b/src/context.rs @@ -36,7 +36,7 @@ pub(crate) enum DeclarationContext { #[derive(Debug)] pub(crate) struct PropertyHandlerContext<'i> { - targets: Option, + pub targets: Option, pub used_logical: bool, pub is_important: bool, supports: Vec>, diff --git a/src/declaration.rs b/src/declaration.rs index f834f623..3e6dbce3 100644 --- a/src/declaration.rs +++ b/src/declaration.rs @@ -1,6 +1,7 @@ use cssparser::*; use crate::properties::Property; use crate::properties::box_shadow::BoxShadowHandler; +use crate::properties::masking::MaskHandler; use crate::traits::{PropertyHandler, ToCss}; use crate::printer::Printer; use crate::properties::{ @@ -189,6 +190,7 @@ pub(crate) struct DeclarationHandler<'i> { overflow: OverflowHandler, transform: TransformHandler, box_shadow: BoxShadowHandler, + mask: MaskHandler<'i>, fallback: FallbackHandler, prefix: PrefixHandler, decls: DeclarationList<'i> @@ -219,6 +221,7 @@ impl<'i> DeclarationHandler<'i> { overflow: OverflowHandler::new(targets), transform: TransformHandler::new(targets), box_shadow: BoxShadowHandler::new(targets), + mask: MaskHandler::default(), fallback: FallbackHandler::new(targets), prefix: PrefixHandler::new(targets), decls: DeclarationList::new() @@ -248,6 +251,7 @@ impl<'i> DeclarationHandler<'i> { self.overflow.handle_property(property, &mut self.decls, context) || self.transform.handle_property(property, &mut self.decls, context) || self.box_shadow.handle_property(property, &mut self.decls, context) || + self.mask.handle_property(property, &mut self.decls, context) || self.fallback.handle_property(property, &mut self.decls, context) || self.prefix.handle_property(property, &mut self.decls, context) } @@ -275,6 +279,7 @@ impl<'i> DeclarationHandler<'i> { self.overflow.finalize(&mut self.decls, context); self.transform.finalize(&mut self.decls, context); self.box_shadow.finalize(&mut self.decls, context); + self.mask.finalize(&mut self.decls, context); self.fallback.finalize(&mut self.decls, context); self.prefix.finalize(&mut self.decls, context); } diff --git a/src/lib.rs b/src/lib.rs index b9b74e55..8b635ad4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4920,10 +4920,19 @@ mod tests { } "#, indoc! {r#" .foo { - transition-property: opacity, color; - transition-duration: 2s; - transition-delay: .5s; - transition-timing-function: ease-in-out; + transition: opacity 2s ease-in-out .5s, color 2s ease-in-out .5s; + } + "#}); + test(r#" + .foo { + transition-property: opacity, color, width, height; + transition-duration: 2s, 4s; + transition-timing-function: ease; + transition-delay: 0s; + } + "#, indoc! {r#" + .foo { + transition: opacity 2s, color 4s, width 2s, height 4s; } "#}); @@ -11939,8 +11948,8 @@ mod tests { ".foo { mask-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }", indoc! { r#" .foo { - mask-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff)); - mask-image: -webkit-linear-gradient(#ff0f0e, #7773ff); + -webkit-mask-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff)); + -webkit-mask-image: -webkit-linear-gradient(#ff0f0e, #7773ff); mask-image: linear-gradient(#ff0f0e, #7773ff); mask-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)); } @@ -11955,9 +11964,11 @@ mod tests { ".foo { mask: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 40px 20px }", indoc! { r#" .foo { - mask: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff)) 40px 20px; - mask: -webkit-linear-gradient(#ff0f0e, #7773ff) 40px 20px; + -webkit-mask: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff)) 40px 20px; + -webkit-mask: -webkit-linear-gradient(#ff0f0e, #7773ff) 40px 20px; + -webkit-mask: linear-gradient(#ff0f0e, #7773ff) 40px 20px; mask: linear-gradient(#ff0f0e, #7773ff) 40px 20px; + -webkit-mask: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 40px 20px; mask: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 40px 20px; } "#}, @@ -11971,8 +11982,8 @@ mod tests { ".foo { mask: -webkit-linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 40px 20px }", indoc! { r#" .foo { - mask: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff)) 40px 20px; - mask: -webkit-linear-gradient(#ff0f0e, #7773ff) 40px 20px; + -webkit-mask: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff)) 40px 20px; + -webkit-mask: -webkit-linear-gradient(#ff0f0e, #7773ff) 40px 20px; } "#}, Browsers { @@ -11985,11 +11996,13 @@ mod tests { ".foo { mask: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 40px var(--foo) }", indoc! { r#" .foo { + -webkit-mask: linear-gradient(#ff0f0e, #7773ff) 40px var(--foo); mask: linear-gradient(#ff0f0e, #7773ff) 40px var(--foo); } @supports (color: lab(0% 0 0)) { .foo { + -webkit-mask: linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)) 40px var(--foo); mask: linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)) 40px var(--foo); } } @@ -11999,6 +12012,339 @@ mod tests { ..Browsers::default() } ); + + prefix_test( + ".foo { mask: url(masks.svg#star) luminance }", + indoc! { r#" + .foo { + -webkit-mask: url(masks.svg#star); + -webkit-mask-source-type: luminance; + mask: url(masks.svg#star) luminance; + } + "#},Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }); + + prefix_test( + ".foo { mask-image: url(masks.svg#star) }", + indoc! { r#" + .foo { + -webkit-mask-image: url(masks.svg#star); + mask-image: url(masks.svg#star); + } + "#},Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }); + + prefix_test( + r#" + .foo { + mask-image: url(masks.svg#star); + mask-position: 25% 75%; + mask-size: cover; + mask-repeat: no-repeat; + mask-clip: padding-box; + mask-origin: content-box; + mask-composite: subtract; + mask-mode: luminance; + } + "#, + indoc! { r#" + .foo { + -webkit-mask: url(masks.svg#star) 25% 75% / cover no-repeat content-box padding-box; + -webkit-mask-composite: source-out; + -webkit-mask-source-type: luminance; + mask: url(masks.svg#star) 25% 75% / cover no-repeat content-box padding-box subtract luminance; + } + "#},Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }); + + prefix_test( + r#" + .foo { + mask-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)); + mask-position: 25% 75%; + mask-size: cover; + mask-repeat: no-repeat; + mask-clip: padding-box; + mask-origin: content-box; + mask-composite: subtract; + mask-mode: luminance; + } + "#, + indoc! { r#" + .foo { + -webkit-mask: linear-gradient(#ff0f0e, #7773ff) 25% 75% / cover no-repeat content-box padding-box; + -webkit-mask-composite: source-out; + -webkit-mask-source-type: luminance; + mask: linear-gradient(#ff0f0e, #7773ff) 25% 75% / cover no-repeat content-box padding-box subtract luminance; + -webkit-mask: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 25% 75% / cover no-repeat content-box padding-box; + -webkit-mask-composite: source-out; + -webkit-mask-source-type: luminance; + mask: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 25% 75% / cover no-repeat content-box padding-box subtract luminance; + } + "#},Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }); + + prefix_test( + r#" + .foo { + mask-composite: subtract; + } + "#, + indoc! { r#" + .foo { + -webkit-mask-composite: source-out; + mask-composite: subtract; + } + "#},Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }); + + prefix_test( + r#" + .foo { + mask-mode: luminance; + } + "#, + indoc! { r#" + .foo { + -webkit-mask-source-type: luminance; + mask-mode: luminance; + } + "#},Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }); + + prefix_test( + r#" + .foo { + mask-border: url('border-mask.png') 25 / 35px / 12px space luminance; + } + "#, + indoc! { r#" + .foo { + -webkit-mask-box-image: url(border-mask.png) 25 / 35px / 12px space; + mask-border: url(border-mask.png) 25 / 35px / 12px space luminance; + } + "#},Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }); + + prefix_test( + r#" + .foo { + mask-border: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 25 / 35px / 12px space luminance; + } + "#, + indoc! { r#" + .foo { + -webkit-mask-box-image: linear-gradient(#ff0f0e, #7773ff) 25 / 35px / 12px space; + mask-border: linear-gradient(#ff0f0e, #7773ff) 25 / 35px / 12px space luminance; + -webkit-mask-box-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 25 / 35px / 12px space; + mask-border: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 25 / 35px / 12px space luminance; + } + "#},Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }); + + prefix_test( + r#" + .foo { + mask-border-source: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)); + } + "#, + indoc! { r#" + .foo { + -webkit-mask-box-image-source: linear-gradient(#ff0f0e, #7773ff); + mask-border-source: linear-gradient(#ff0f0e, #7773ff); + -webkit-mask-box-image-source: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)); + mask-border-source: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)); + } + "#},Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }); + + prefix_test( + r#" + .foo { + mask-border-source: url(foo.png); + mask-border-slice: 10 40 10 40; + mask-border-width: 10px; + mask-border-outset: 0; + mask-border-repeat: round round; + mask-border-mode: luminance; + } + "#, + indoc! { r#" + .foo { + -webkit-mask-box-image: url(foo.png) 10 40 / 10px round; + mask-border: url(foo.png) 10 40 / 10px round luminance; + } + "#},Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }); + + prefix_test( + r#" + .foo { + -webkit-mask-box-image-source: url(foo.png); + -webkit-mask-box-image-slice: 10 40 10 40; + -webkit-mask-box-image-width: 10px; + -webkit-mask-box-image-outset: 0; + -webkit-mask-box-image-repeat: round round; + } + "#, + indoc! { r#" + .foo { + -webkit-mask-box-image: url(foo.png) 10 40 / 10px round; + } + "#},Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }); + + prefix_test( + r#" + .foo { + mask-border-slice: 10 40 10 40; + } + "#, + indoc! { r#" + .foo { + -webkit-mask-box-image-slice: 10 40; + mask-border-slice: 10 40; + } + "#},Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }); + + prefix_test( + r#" + .foo { + mask-border-slice: var(--foo); + } + "#, + indoc! { r#" + .foo { + -webkit-mask-box-image-slice: var(--foo); + mask-border-slice: var(--foo); + } + "#},Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }); + + prefix_test( + r#" + .foo { + mask-border: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) var(--foo); + } + "#, + indoc! { r#" + .foo { + -webkit-mask-box-image: linear-gradient(#ff0f0e, #7773ff) var(--foo); + mask-border: linear-gradient(#ff0f0e, #7773ff) var(--foo); + } + + @supports (color: lab(0% 0 0)) { + .foo { + -webkit-mask-box-image: linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)) var(--foo); + mask-border: linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)) var(--foo); + } + } + "#},Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }); + + prefix_test( + r#" + .foo { + transition: mask 200ms; + } + "#, + indoc! { r#" + .foo { + transition: -webkit-mask .2s, mask .2s; + } + "#},Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }); + + prefix_test( + r#" + .foo { + transition: mask-border 200ms; + } + "#, + indoc! { r#" + .foo { + transition: -webkit-mask-box-image .2s, mask-border .2s; + } + "#},Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }); + + prefix_test( + r#" + .foo { + transition-property: mask; + } + "#, + indoc! { r#" + .foo { + transition-property: -webkit-mask, mask; + } + "#},Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }); + + prefix_test( + r#" + .foo { + transition-property: mask-border; + } + "#, + indoc! { r#" + .foo { + transition-property: -webkit-mask-box-image, mask-border; + } + "#},Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }); + + prefix_test( + r#" + .foo { + transition-property: mask-composite, mask-mode; + } + "#, + indoc! { r#" + .foo { + transition-property: -webkit-mask-composite, mask-composite, -webkit-mask-source-type, mask-mode; + } + "#},Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }); } #[test] diff --git a/src/parser.rs b/src/parser.rs index 143fd4b4..092eb6b5 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -745,7 +745,6 @@ impl<'a, 'i> AtRuleParser<'i> for StyleRuleParser<'a, 'i> { Ok(()) }, _ => { - println!("{:?}", prelude); unreachable!() } } diff --git a/src/properties/masking.rs b/src/properties/masking.rs index 2aa59ce3..f154a45b 100644 --- a/src/properties/masking.rs +++ b/src/properties/masking.rs @@ -1,17 +1,27 @@ use cssparser::*; -use crate::traits::{Parse, ToCss}; +use itertools::izip; +use smallvec::SmallVec; +use crate::context::PropertyHandlerContext; +use crate::declaration::DeclarationList; +use crate::prefixes::Feature; +use crate::properties::Property; +use crate::traits::{Parse, ToCss, PropertyHandler, FallbackValues}; use crate::printer::Printer; use crate::macros::enum_property; use crate::error::{ParserError, PrinterError}; use crate::values::image::ImageFallback; +use crate::values::length::LengthOrNumber; +use crate::values::rect::Rect; use crate::values::{ image::Image, position::Position, url::Url, shape::BasicShape, }; +use crate::vendor_prefix::VendorPrefix; +use super::PropertyId; use super::background::{BackgroundSize, BackgroundRepeat}; -use super::border_image::BorderImage; +use super::border_image::{BorderImage, BorderImageSlice, BorderImageSideWidth, BorderImageRepeat}; enum_property! { /// https://www.w3.org/TR/css-masking-1/#the-mask-type @@ -30,6 +40,31 @@ enum_property! { } } +impl Default for MaskMode { + fn default() -> MaskMode { + MaskMode::MatchSource + } +} + +enum_property! { + /// https://github.com/WebKit/WebKit/blob/6eece09a1c31e47489811edd003d1e36910e9fd3/Source/WebCore/css/CSSProperties.json#L6578-L6587 + pub enum WebKitMaskSourceType { + "auto": Auto, + "luminance": Luminance, + "alpha": Alpha, + } +} + +impl From for WebKitMaskSourceType { + fn from(mode: MaskMode) -> WebKitMaskSourceType { + match mode { + MaskMode::Luminance => WebKitMaskSourceType::Luminance, + MaskMode::Alpha => WebKitMaskSourceType::Alpha, + MaskMode::MatchSource => WebKitMaskSourceType::Auto + } + } +} + enum_property! { /// https://www.w3.org/TR/css-masking-1/#typedef-geometry-box pub enum GeometryBox { @@ -92,6 +127,40 @@ enum_property! { } } +impl Default for MaskComposite { + fn default() -> MaskComposite { + MaskComposite::Add + } +} + +enum_property! { + /// https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-mask-composite + pub enum WebKitMaskComposite { + "clear": Clear, + "copy": Copy, + "source-over": SourceOver, + "source-in": SourceIn, + "source-out": SourceOut, + "source-atop": SourceAtop, + "destination-over": DestinationOver, + "destination-in": DestinationIn, + "destination-out": DestinationOut, + "destination-atop": DestinationAtop, + "xor": Xor, + } +} + +impl From for WebKitMaskComposite { + fn from(composite: MaskComposite) -> WebKitMaskComposite { + match composite { + MaskComposite::Add => WebKitMaskComposite::SourceOver, + MaskComposite::Subtract => WebKitMaskComposite::SourceOut, + MaskComposite::Intersect => WebKitMaskComposite::SourceIn, + MaskComposite::Exclude => WebKitMaskComposite::Xor + } + } +} + /// https://www.w3.org/TR/css-masking-1/#the-mask #[derive(Debug, Clone, PartialEq)] pub struct Mask<'i> { @@ -221,12 +290,12 @@ impl<'i> ToCss for Mask<'i> { } } - if self.composite != MaskComposite::Add { + if self.composite != MaskComposite::default() { dest.write_char(' ')?; self.composite.to_css(dest)?; } - if self.mode != MaskMode::MatchSource { + if self.mode != MaskMode::default() { dest.write_char(' ')?; self.mode.to_css(dest)?; } @@ -356,3 +425,546 @@ impl<'i> ToCss for MaskBorder<'i> { Ok(()) } } + +#[derive(Default)] +pub(crate) struct MaskHandler<'i> { + images: Option<(SmallVec<[Image<'i>; 1]>, VendorPrefix)>, + positions: Option<(SmallVec<[Position; 1]>, VendorPrefix)>, + sizes: Option<(SmallVec<[BackgroundSize; 1]>, VendorPrefix)>, + repeats: Option<(SmallVec<[BackgroundRepeat; 1]>, VendorPrefix)>, + clips: Option<(SmallVec<[MaskClip; 1]>, VendorPrefix)>, + origins: Option<(SmallVec<[GeometryBox; 1]>, VendorPrefix)>, + composites: Option>, + modes: Option>, + border_source: Option<(Image<'i>, VendorPrefix)>, + border_mode: Option, + border_slice: Option<(BorderImageSlice, VendorPrefix)>, + border_width: Option<(Rect, VendorPrefix)>, + border_outset: Option<(Rect, VendorPrefix)>, + border_repeat: Option<(BorderImageRepeat, VendorPrefix)>, + has_any: bool +} + +impl<'i> PropertyHandler<'i> for MaskHandler<'i> { + fn handle_property(&mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) -> bool { + macro_rules! maybe_flush { + ($prop: ident, $val: expr, $vp: expr) => {{ + // If two vendor prefixes for the same property have different + // values, we need to flush what we have immediately to preserve order. + if let Some((val, prefixes)) = &self.$prop { + if val != $val && !prefixes.contains(*$vp) { + self.finalize(dest, context); + } + } + }}; + } + + macro_rules! property { + ($prop: ident, $val: expr, $vp: expr) => {{ + maybe_flush!($prop, $val, $vp); + + // Otherwise, update the value and add the prefix. + if let Some((val, prefixes)) = &mut self.$prop { + *val = $val.clone(); + *prefixes |= *$vp; + } else { + self.$prop = Some(($val.clone(), *$vp)); + self.has_any = true; + } + }}; + } + + macro_rules! border_shorthand { + ($val: expr, $vp: expr) => { + let source = $val.source.clone(); + maybe_flush!(border_source, &source, &$vp); + + let slice = $val.slice.clone(); + maybe_flush!(border_slice, &slice, &$vp); + + let width = $val.width.clone(); + maybe_flush!(border_width, &width, &$vp); + + let outset = $val.outset.clone(); + maybe_flush!(border_outset, &outset, &$vp); + + let repeat = $val.repeat.clone(); + maybe_flush!(border_repeat, &repeat, &$vp); + + property!(border_source, &source, &$vp); + property!(border_slice, &slice, &$vp); + property!(border_width, &width, &$vp); + property!(border_outset, &outset, &$vp); + property!(border_repeat, &repeat, &$vp); + } + } + + match property { + Property::MaskImage(val, vp) => property!(images, val, vp), + Property::MaskPosition(val, vp) => property!(positions, val, vp), + Property::MaskSize(val, vp) => property!(sizes, val, vp), + Property::MaskRepeat(val, vp) => property!(repeats, val, vp), + Property::MaskClip(val, vp) => property!(clips, val, vp), + Property::MaskOrigin(val, vp) => property!(origins, val, vp), + Property::MaskComposite(val) => self.composites = Some(val.clone()), + Property::MaskMode(val) => self.modes = Some(val.clone()), + Property::Mask(val, prefix) => { + let images = val.iter().map(|b| b.image.clone()).collect(); + maybe_flush!(images, &images, prefix); + + let positions = val.iter().map(|b| b.position.clone()).collect(); + maybe_flush!(positions, &positions, prefix); + + let sizes = val.iter().map(|b| b.size.clone()).collect(); + maybe_flush!(sizes, &sizes, prefix); + + let repeats = val.iter().map(|b| b.repeat.clone()).collect(); + maybe_flush!(repeats, &repeats, prefix); + + let clips = val.iter().map(|b| b.clip.clone()).collect(); + maybe_flush!(clips, &clips, prefix); + + let origins = val.iter().map(|b| b.origin.clone()).collect(); + maybe_flush!(origins, &origins, prefix); + + self.composites = Some(val.iter().map(|b| b.composite.clone()).collect()); + self.modes = Some(val.iter().map(|b| b.mode.clone()).collect()); + + property!(images, &images, prefix); + property!(positions, &positions, prefix); + property!(sizes, &sizes, prefix); + property!(repeats, &repeats, prefix); + property!(clips, &clips, prefix); + property!(origins, &origins, prefix); + } + Property::Unparsed(val) if is_mask_property(&val.property_id) => { + let mut unparsed = val.get_prefixed(context.targets, Feature::Mask); + context.add_unparsed_fallbacks(&mut unparsed); + dest.push(Property::Unparsed(unparsed)); + } + Property::MaskBorderSource(val) => property!(border_source, val, &VendorPrefix::None), + Property::WebKitMaskBoxImageSource(val, _) => property!(border_source, val, &VendorPrefix::WebKit), + Property::MaskBorderMode(val) => self.border_mode = Some(val.clone()), + Property::MaskBorderSlice(val) => property!(border_slice, val, &VendorPrefix::None), + Property::WebKitMaskBoxImageSlice(val, _) => property!(border_slice, val, &VendorPrefix::WebKit), + Property::MaskBorderWidth(val) => property!(border_width, val, &VendorPrefix::None), + Property::WebKitMaskBoxImageWidth(val, _) => property!(border_width, val, &VendorPrefix::WebKit), + Property::MaskBorderOutset(val) => property!(border_outset, val, &VendorPrefix::None), + Property::WebKitMaskBoxImageOutset(val, _) => property!(border_outset, val, &VendorPrefix::WebKit), + Property::MaskBorderRepeat(val) => property!(border_repeat, val, &VendorPrefix::None), + Property::WebKitMaskBoxImageRepeat(val, _) => property!(border_repeat, val, &VendorPrefix::WebKit), + Property::MaskBorder(val) => { + border_shorthand!(val.border_image, VendorPrefix::None); + self.border_mode = Some(val.mode.clone()); + } + Property::WebKitMaskBoxImage(val, _) => { + border_shorthand!(val, VendorPrefix::WebKit); + } + Property::Unparsed(val) if is_mask_border_property(&val.property_id) => { + // Add vendor prefixes and expand color fallbacks. + let mut val = val.clone(); + let mut prefix = val.property_id.prefix(); + if prefix.contains(VendorPrefix::None) { + if let Some(targets) = context.targets { + prefix = Feature::MaskBorder.prefixes_for(targets); + } + } + + if prefix.contains(VendorPrefix::WebKit) { + if let Some(property_id) = get_webkit_mask_property(&val.property_id) { + let mut clone = val.clone(); + clone.property_id = property_id; + context.add_unparsed_fallbacks(&mut clone); + dest.push(Property::Unparsed(clone)); + } + } + + context.add_unparsed_fallbacks(&mut val); + dest.push(Property::Unparsed(val)); + } + _ => return false + } + + self.has_any = true; + true + } + + fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) { + if !self.has_any { + return + } + + self.has_any = false; + + self.flush_mask(dest, context); + self.flush_mask_border(dest, context); + } +} + +impl<'i> MaskHandler<'i> { + fn flush_mask(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) { + let mut images = std::mem::take(&mut self.images); + let mut positions = std::mem::take(&mut self.positions); + let mut sizes = std::mem::take(&mut self.sizes); + let mut repeats = std::mem::take(&mut self.repeats); + let mut clips = std::mem::take(&mut self.clips); + let mut origins = std::mem::take(&mut self.origins); + let mut composites = std::mem::take(&mut self.composites); + let mut modes = std::mem::take(&mut self.modes); + + if let (Some((images, images_vp)), Some((positions, positions_vp)), Some((sizes, sizes_vp)), Some((repeats, repeats_vp)), Some((clips, clips_vp)), Some((origins, origins_vp)), Some(composites_val), Some(mode_vals)) = (&mut images, &mut positions, &mut sizes, &mut repeats, &mut clips, &mut origins, &mut composites, &mut modes) { + // Only use shorthand syntax if the number of masks matches on all properties. + let len = images.len(); + let intersection = *images_vp & *positions_vp & *sizes_vp & *repeats_vp & *clips_vp & *origins_vp; + if !intersection.is_empty() && positions.len() == len && sizes.len() == len && repeats.len() == len && clips.len() == len && origins.len() == len && composites_val.len() == len && mode_vals.len() == len { + let mut masks: SmallVec<[Mask<'i>; 1]> = izip!(images.drain(..), positions.drain(..), sizes.drain(..), repeats.drain(..), clips.drain(..), origins.drain(..), composites_val.drain(..), mode_vals.drain(..)).map(|(image, position, size, repeat, clip, origin, composite, mode)| { + Mask { + image, + position, + size, + repeat, + clip, + origin, + composite, + mode + } + }).collect(); + + let mut prefix = intersection; + if prefix.contains(VendorPrefix::None) { + if let Some(targets) = context.targets { + prefix = Feature::Mask.prefixes_for(targets) + } + } + + if let Some(targets) = context.targets { + for fallback in masks.get_fallbacks(targets) { + // Match prefix of fallback. e.g. -webkit-linear-gradient + // can only be used in -webkit-mask-image. + // However, if mask-image is unprefixed, gradients can still be. + let mut p = fallback.iter().fold(VendorPrefix::empty(), |p, mask| p | mask.image.get_vendor_prefix()) - VendorPrefix::None & prefix; + if p.is_empty() { + p = prefix; + } + self.flush_mask_shorthand(fallback, p, dest); + } + + let p = masks.iter().fold(VendorPrefix::empty(), |p, mask| p | mask.image.get_vendor_prefix()) - VendorPrefix::None & prefix; + if !p.is_empty() { + prefix = p; + } + } + + self.flush_mask_shorthand(masks, prefix, dest); + + images_vp.remove(intersection); + positions_vp.remove(intersection); + sizes_vp.remove(intersection); + repeats_vp.remove(intersection); + clips_vp.remove(intersection); + origins_vp.remove(intersection); + composites = None; + modes = None; + } + } + + macro_rules! prop { + ($var: ident, $property: ident) => { + if let Some((val, vp)) = $var { + if !vp.is_empty() { + let mut prefix = vp; + if prefix.contains(VendorPrefix::None) { + if let Some(targets) = context.targets { + prefix = Feature::$property.prefixes_for(targets) + } + } + dest.push(Property::$property(val, prefix)) + } + } + }; + } + + if let Some((mut images, vp)) = images { + if !vp.is_empty() { + let mut prefix = vp; + if prefix.contains(VendorPrefix::None) { + if let Some(targets) = context.targets { + prefix = Feature::MaskImage.prefixes_for(targets) + } + } + + if let Some(targets) = context.targets { + for fallback in images.get_fallbacks(targets) { + // Match prefix of fallback. e.g. -webkit-linear-gradient + // can only be used in -webkit-mask-image. + // However, if mask-image is unprefixed, gradients can still be. + let mut p = fallback.iter().fold(prefix, |p, image| p & image.get_vendor_prefix()); + if p.is_empty() { + p = prefix; + } + dest.push(Property::MaskImage(fallback, p)) + } + } + + let p = images.iter().fold(prefix, |p, image| p & image.get_vendor_prefix()); + if !p.is_empty() { + prefix = p; + } + + dest.push(Property::MaskImage(images, prefix)); + } + } + + prop!(positions, MaskPosition); + prop!(sizes, MaskSize); + prop!(repeats, MaskRepeat); + prop!(clips, MaskClip); + prop!(origins, MaskOrigin); + + if let Some(composites) = composites { + let prefix = if let Some(targets) = context.targets { + Feature::MaskComposite.prefixes_for(targets) + } else { + VendorPrefix::None + }; + + if prefix.contains(VendorPrefix::WebKit) { + dest.push(Property::WebKitMaskComposite(composites.iter().map(|c| (*c).into()).collect())); + } + + dest.push(Property::MaskComposite(composites)) + } + + if let Some(modes) = modes { + let prefix = if let Some(targets) = context.targets { + Feature::Mask.prefixes_for(targets) + } else { + VendorPrefix::None + }; + + if prefix.contains(VendorPrefix::WebKit) { + dest.push(Property::WebKitMaskSourceType(modes.iter().map(|c| (*c).into()).collect(), VendorPrefix::WebKit)); + } + + dest.push(Property::MaskMode(modes)) + } + } + + fn flush_mask_shorthand(&self, masks: SmallVec<[Mask<'i>; 1]>, prefix: VendorPrefix, dest: &mut DeclarationList<'i>) { + if prefix.contains(VendorPrefix::WebKit) && masks.iter().any(|mask| mask.composite != MaskComposite::default() || mask.mode != MaskMode::default()) { + // Prefixed shorthand syntax did not support mask-composite or mask-mode. These map to different webkit-specific properties. + // -webkit-mask-composite uses a different syntax than mask-composite. + // -webkit-mask-source-type is equivalent to mask-mode, but only supported in Safari, not Chrome. + let mut webkit = masks.clone(); + let mut composites: SmallVec<[WebKitMaskComposite; 1]> = SmallVec::new(); + let mut modes: SmallVec<[WebKitMaskSourceType; 1]> = SmallVec::new(); + let mut needs_composites = false; + let mut needs_modes = false; + for mask in &mut webkit { + let composite = std::mem::take(&mut mask.composite); + if composite != MaskComposite::default() { + needs_composites = true; + } + composites.push(composite.into()); + + let mode = std::mem::take(&mut mask.mode); + if mode != MaskMode::default() { + needs_modes = true; + } + modes.push(mode.into()); + } + + dest.push(Property::Mask(webkit, VendorPrefix::WebKit)); + if needs_composites { + dest.push(Property::WebKitMaskComposite(composites)); + } + if needs_modes { + dest.push(Property::WebKitMaskSourceType(modes, VendorPrefix::WebKit)); + } + + let prefix = prefix - VendorPrefix::WebKit; + if !prefix.is_empty() { + dest.push(Property::Mask(masks, prefix)); + } + } else { + dest.push(Property::Mask(masks, prefix)); + } + } + + fn flush_mask_border(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) { + let mut source = std::mem::take(&mut self.border_source); + let mut slice = std::mem::take(&mut self.border_slice); + let mut width = std::mem::take(&mut self.border_width); + let mut outset = std::mem::take(&mut self.border_outset); + let mut repeat = std::mem::take(&mut self.border_repeat); + let mut mode = std::mem::take(&mut self.border_mode); + + if let (Some((source, source_vp)), Some((slice, slice_vp)), Some((width, width_vp)), Some((outset, outset_vp)), Some((repeat, repeat_vp))) = (&mut source, &mut slice, &mut width, &mut outset, &mut repeat) { + let intersection = *source_vp & *slice_vp & *width_vp & *outset_vp & *repeat_vp; + if !intersection.is_empty() && (!intersection.contains(VendorPrefix::None) || mode.is_some()) { + let mut border_image = BorderImage { + source: source.clone(), + slice: slice.clone(), + width: width.clone(), + outset: outset.clone(), + repeat: repeat.clone() + }; + + let mut prefix = intersection; + if prefix.contains(VendorPrefix::None) { + if let Some(targets) = context.targets { + prefix = Feature::MaskBorder.prefixes_for(targets) + } + } + + if let Some(targets) = context.targets { + // Get vendor prefix and color fallbacks. + let fallbacks = border_image.get_fallbacks(targets); + for fallback in fallbacks { + let mut p = fallback.source.get_vendor_prefix() - VendorPrefix::None & prefix; + if p.is_empty() { + p = prefix; + } + + if p.contains(VendorPrefix::WebKit) { + dest.push(Property::WebKitMaskBoxImage(fallback.clone(), VendorPrefix::WebKit)); + } + + if p.contains(VendorPrefix::None) { + dest.push(Property::MaskBorder(MaskBorder { + border_image: fallback.clone(), + mode: mode.unwrap().clone() + })) + } + } + } + + let p = border_image.source.get_vendor_prefix() - VendorPrefix::None & prefix; + if !p.is_empty() { + prefix = p; + } + + if prefix.contains(VendorPrefix::WebKit) { + dest.push(Property::WebKitMaskBoxImage(border_image.clone(), VendorPrefix::WebKit)); + } + + if prefix.contains(VendorPrefix::None) { + dest.push(Property::MaskBorder(MaskBorder { + border_image: border_image.clone(), + mode: mode.unwrap() + })); + + mode = None; + } + + source_vp.remove(intersection); + slice_vp.remove(intersection); + width_vp.remove(intersection); + outset_vp.remove(intersection); + repeat_vp.remove(intersection); + } + } + + if let Some((mut source, mut prefix)) = source { + if let Some(targets) = context.targets { + if prefix.contains(VendorPrefix::None) { + prefix = Feature::MaskBorderSource.prefixes_for(targets) + } + + // Get vendor prefix and color fallbacks. + let fallbacks = source.get_fallbacks(targets); + for fallback in fallbacks { + if prefix.contains(VendorPrefix::WebKit) { + dest.push(Property::WebKitMaskBoxImageSource(fallback.clone(), VendorPrefix::WebKit)); + } + + if prefix.contains(VendorPrefix::None) { + dest.push(Property::MaskBorderSource(fallback)); + } + } + } + + if prefix.contains(VendorPrefix::WebKit) { + dest.push(Property::WebKitMaskBoxImageSource(source.clone(), VendorPrefix::WebKit)); + } + + if prefix.contains(VendorPrefix::None) { + dest.push(Property::MaskBorderSource(source)); + } + } + + macro_rules! prop { + ($val: expr, $prop: ident, $webkit: ident) => { + if let Some((val, mut prefix)) = $val { + if let Some(targets) = context.targets { + if prefix.contains(VendorPrefix::None) { + prefix = Feature::$prop.prefixes_for(targets) + } + } + + if prefix.contains(VendorPrefix::WebKit) { + dest.push(Property::$webkit(val.clone(), VendorPrefix::WebKit)); + } + + if prefix.contains(VendorPrefix::None) { + dest.push(Property::$prop(val)); + } + } + } + } + + prop!(slice, MaskBorderSlice, WebKitMaskBoxImageSlice); + prop!(width, MaskBorderWidth, WebKitMaskBoxImageWidth); + prop!(outset, MaskBorderOutset, WebKitMaskBoxImageOutset); + prop!(repeat, MaskBorderRepeat, WebKitMaskBoxImageRepeat); + + if let Some(mode) = mode { + dest.push(Property::MaskBorderMode(mode)); + } + } +} + +#[inline] +fn is_mask_property(property_id: &PropertyId) -> bool { + match property_id { + PropertyId::MaskImage(_) | + PropertyId::MaskPosition(_) | + PropertyId::MaskSize(_) | + PropertyId::MaskRepeat(_) | + PropertyId::MaskClip(_) | + PropertyId::MaskOrigin(_) | + PropertyId::MaskComposite | + PropertyId::MaskMode | + PropertyId::Mask(_) => true, + _ => false + } +} + +#[inline] + fn is_mask_border_property(property_id: &PropertyId) -> bool { + match property_id { + PropertyId::MaskBorderSource | + PropertyId::MaskBorderSlice | + PropertyId::MaskBorderWidth | + PropertyId::MaskBorderOutset | + PropertyId::MaskBorderRepeat | + PropertyId::MaskBorderMode | + PropertyId::MaskBorder => true, + _ => false + } +} + +#[inline] +pub(crate) fn get_webkit_mask_property(property_id: &PropertyId) -> Option> { + Some(match property_id { + PropertyId::MaskBorderSource => PropertyId::WebKitMaskBoxImageSource(VendorPrefix::WebKit), + PropertyId::MaskBorderSlice => PropertyId::WebKitMaskBoxImageSlice(VendorPrefix::WebKit), + PropertyId::MaskBorderWidth => PropertyId::WebKitMaskBoxImageWidth(VendorPrefix::WebKit), + PropertyId::MaskBorderOutset => PropertyId::WebKitMaskBoxImageOutset(VendorPrefix::WebKit), + PropertyId::MaskBorderRepeat => PropertyId::WebKitMaskBoxImageRepeat(VendorPrefix::WebKit), + PropertyId::MaskBorder => PropertyId::WebKitMaskBoxImage(VendorPrefix::WebKit), + PropertyId::MaskComposite => PropertyId::WebKitMaskComposite, + PropertyId::MaskMode => PropertyId::WebKitMaskSourceType(VendorPrefix::WebKit), + _ => return None + }) +} diff --git a/src/properties/mod.rs b/src/properties/mod.rs index a35fa30b..cc8d7c4b 100644 --- a/src/properties/mod.rs +++ b/src/properties/mod.rs @@ -831,18 +831,18 @@ define_properties! { // https://www.w3.org/TR/css-masking-1/ "clip-path": ClipPath(ClipPath<'i>), "clip-rule": ClipRule(FillRule), - "mask-image": MaskImage(SmallVec<[Image<'i>; 1]>), + "mask-image": MaskImage(SmallVec<[Image<'i>; 1]>, VendorPrefix) / WebKit, "mask-mode": MaskMode(SmallVec<[MaskMode; 1]>), - "mask-repeat": MaskRepeat(SmallVec<[BackgroundRepeat; 1]>), + "mask-repeat": MaskRepeat(SmallVec<[BackgroundRepeat; 1]>, VendorPrefix) / WebKit, "mask-position-x": MaskPositionX(SmallVec<[HorizontalPosition; 1]>), "mask-position-y": MaskPositionY(SmallVec<[VerticalPosition; 1]>), - "mask-position": MaskPosition(SmallVec<[Position; 1]>), - "mask-clip": MaskClip(SmallVec<[MaskClip; 1]>), - "mask-origin": MaskOrigin(SmallVec<[GeometryBox; 1]>), - "mask-size": MaskSize(SmallVec<[BackgroundSize; 1]>), + "mask-position": MaskPosition(SmallVec<[Position; 1]>, VendorPrefix) / WebKit, + "mask-clip": MaskClip(SmallVec<[MaskClip; 1]>, VendorPrefix) / WebKit, + "mask-origin": MaskOrigin(SmallVec<[GeometryBox; 1]>, VendorPrefix) / WebKit, + "mask-size": MaskSize(SmallVec<[BackgroundSize; 1]>, VendorPrefix) / WebKit, "mask-composite": MaskComposite(SmallVec<[MaskComposite; 1]>), "mask-type": MaskType(MaskType), - "mask": Mask(SmallVec<[Mask<'i>; 1]>), + "mask": Mask(SmallVec<[Mask<'i>; 1]>, VendorPrefix) / WebKit, "mask-border-source": MaskBorderSource(Image<'i>), "mask-border-mode": MaskBorderMode(MaskBorderMode), "mask-border-slice": MaskBorderSlice(BorderImageSlice), @@ -851,6 +851,16 @@ define_properties! { "mask-border-repeat": MaskBorderRepeat(BorderImageRepeat), "mask-border": MaskBorder(MaskBorder<'i>), + // WebKit additions + "-webkit-mask-composite": WebKitMaskComposite(SmallVec<[WebKitMaskComposite; 1]>), + "mask-source-type": WebKitMaskSourceType(SmallVec<[WebKitMaskSourceType; 1]>, VendorPrefix) / WebKit unprefixed: false, + "mask-box-image": WebKitMaskBoxImage(BorderImage<'i>, VendorPrefix) / WebKit unprefixed: false, + "mask-box-image-source": WebKitMaskBoxImageSource(Image<'i>, VendorPrefix) / WebKit unprefixed: false, + "mask-box-image-slice": WebKitMaskBoxImageSlice(BorderImageSlice, VendorPrefix) / WebKit unprefixed: false, + "mask-box-image-width": WebKitMaskBoxImageWidth(Rect, VendorPrefix) / WebKit unprefixed: false, + "mask-box-image-outset": WebKitMaskBoxImageOutset(Rect, VendorPrefix) / WebKit unprefixed: false, + "mask-box-image-repeat": WebKitMaskBoxImageRepeat(BorderImageRepeat, VendorPrefix) / WebKit unprefixed: false, + // https://drafts.fxtf.org/filter-effects-1/ "filter": Filter(FilterList<'i>), "backdrop-filter": BackdropFilter(FilterList<'i>), diff --git a/src/properties/prefix_handler.rs b/src/properties/prefix_handler.rs index 50279be8..dcb1f456 100644 --- a/src/properties/prefix_handler.rs +++ b/src/properties/prefix_handler.rs @@ -173,6 +173,4 @@ define_fallbacks! { Stroke, CaretColor, Caret, - MaskImage, - Mask, } diff --git a/src/properties/transition.rs b/src/properties/transition.rs index ed481df0..e69de440 100644 --- a/src/properties/transition.rs +++ b/src/properties/transition.rs @@ -1,11 +1,11 @@ use cssparser::*; +use crate::properties::masking::get_webkit_mask_property; use crate::traits::{Parse, ToCss, PropertyHandler}; use crate::values::{time::Time, easing::EasingFunction}; use super::{Property, PropertyId}; use crate::vendor_prefix::VendorPrefix; use crate::declaration::DeclarationList; use crate::printer::Printer; -use itertools::izip; use smallvec::SmallVec; use crate::targets::Browsers; use crate::prefixes::Feature; @@ -214,46 +214,65 @@ impl<'i> TransitionHandler<'i> { }; if let (Some((properties, property_prefixes)), Some((durations, duration_prefixes)), Some((delays, delay_prefixes)), Some((timing_functions, timing_prefixes))) = (&mut properties, &mut durations, &mut delays, &mut timing_functions) { - // Only use shorthand syntax if the number of transitions matches on all properties. - let len = properties.len(); - if durations.len() == len && delays.len() == len && timing_functions.len() == len { - // Find the intersection of prefixes with the same value. - // Remove that from the prefixes of each of the properties. The remaining - // prefixes will be handled by outputing individual properties below. - let intersection = *property_prefixes & *duration_prefixes & *delay_prefixes & *timing_prefixes; - if !intersection.is_empty() { - macro_rules! get_transitions { - ($properties: ident) => { - izip!($properties, durations.iter(), delays.iter(), timing_functions.iter()).map(|(property, duration, delay, timing_function)| { - Transition { - property: property.clone(), - duration: duration.clone(), - delay: delay.clone(), - timing_function: timing_function.clone() + // Find the intersection of prefixes with the same value. + // Remove that from the prefixes of each of the properties. The remaining + // prefixes will be handled by outputing individual properties below. + let intersection = *property_prefixes & *duration_prefixes & *delay_prefixes & *timing_prefixes; + if !intersection.is_empty() { + macro_rules! get_transitions { + ($properties: ident) => {{ + // transition-property determines the number of transitions. The values of other + // properties are repeated to match this length. + let mut transitions = SmallVec::with_capacity($properties.len()); + let mut durations_iter = durations.iter().cycle().cloned(); + let mut delays_iter = delays.iter().cycle().cloned(); + let mut timing_iter = timing_functions.iter().cycle().cloned(); + for property_id in $properties { + let duration = durations_iter.next().unwrap_or(Time::Seconds(0.0)); + let delay = delays_iter.next().unwrap_or(Time::Seconds(0.0)); + let timing_function = timing_iter.next().unwrap_or(EasingFunction::Ease); + let transition = Transition { + property: property_id.clone(), + duration, + delay, + timing_function + }; + + // Expand vendor prefixes into multiple transitions. + let prefix = property_id.prefix(); + let mut b = 1 << (7 - prefix.bits().leading_zeros()); + while b != 0 { + let p = VendorPrefix::from_bits_truncate(b); + if prefix.contains(p) { + let mut t = transition.clone(); + t.property = property_id.with_prefix(p); + transitions.push(t); } - }).collect() - }; - } - - let transitions: SmallVec<[Transition; 1]> = get_transitions!(properties); - - if let Some(rtl_properties) = &rtl_properties { - let rtl_transitions = get_transitions!(rtl_properties); - context.add_logical_property( - dest, - PropertyId::Transition(intersection), - Property::Transition(transitions, intersection), - Property::Transition(rtl_transitions, intersection) - ); - } else { - dest.push(Property::Transition(transitions.clone(), intersection)); - } + b >>= 1; + } + } + transitions + }}; + } + + let transitions: SmallVec<[Transition; 1]> = get_transitions!(properties); - property_prefixes.remove(intersection); - duration_prefixes.remove(intersection); - delay_prefixes.remove(intersection); - timing_prefixes.remove(intersection); + if let Some(rtl_properties) = &rtl_properties { + let rtl_transitions = get_transitions!(rtl_properties); + context.add_logical_property( + dest, + PropertyId::Transition(intersection), + Property::Transition(transitions, intersection), + Property::Transition(rtl_transitions, intersection) + ); + } else { + dest.push(Property::Transition(transitions.clone(), intersection)); } + + property_prefixes.remove(intersection); + duration_prefixes.remove(intersection); + delay_prefixes.remove(intersection); + timing_prefixes.remove(intersection); } } @@ -319,7 +338,6 @@ fn expand_properties<'i>( context: &mut PropertyHandlerContext ) -> Option; 1]>> { let mut rtl_properties: Option> = None; - let len = properties.len(); let mut i = 0; macro_rules! replace { @@ -332,7 +350,7 @@ fn expand_properties<'i>( } // Expand logical properties in place. - while i < len { + while i < properties.len() { match get_logical_properties(&properties[i]) { LogicalPropertyId::Block(feature, props) if !context.is_supported(feature) => { replace!(properties, props); @@ -357,8 +375,23 @@ fn expand_properties<'i>( _ => { // Expand vendor prefixes for targets. properties[i].set_prefixes_for_targets(targets); + + // Expand mask properties, which use different vendor-prefixed names. + if let (Some(targets), Some(property_id)) = (targets, get_webkit_mask_property(&properties[i])) { + if Feature::MaskBorder.prefixes_for(targets).contains(VendorPrefix::WebKit) { + properties.insert(i, property_id); + i += 1; + } + } + if let Some(rtl_properties) = &mut rtl_properties { rtl_properties[i].set_prefixes_for_targets(targets); + + if let (Some(targets), Some(property_id)) = (targets, get_webkit_mask_property(&rtl_properties[i])) { + if Feature::MaskBorder.prefixes_for(targets).contains(VendorPrefix::WebKit) { + rtl_properties.insert(i, property_id); + } + } } i += 1; }