1use crate::context::PropertyHandlerContext;
4use crate::declaration::{DeclarationBlock, DeclarationList};
5use crate::error::{ParserError, PrinterError};
6use crate::macros::*;
7use crate::prefixes::Feature;
8use crate::printer::Printer;
9use crate::properties::{Property, PropertyId, VendorPrefix};
10use crate::targets::{Browsers, Targets};
11use crate::traits::{FallbackValues, IsCompatible, Parse, PropertyHandler, Shorthand, ToCss};
12use crate::values::color::ColorFallbackKind;
13use crate::values::image::ImageFallback;
14use crate::values::{color::CssColor, image::Image, length::LengthPercentageOrAuto, position::*};
15#[cfg(feature = "visitor")]
16use crate::visitor::Visit;
17use cssparser::*;
18use itertools::izip;
19use smallvec::SmallVec;
20
21#[derive(Debug, Clone, PartialEq)]
23#[cfg_attr(feature = "visitor", derive(Visit))]
24#[cfg_attr(
25 feature = "serde",
26 derive(serde::Serialize, serde::Deserialize),
27 serde(tag = "type", rename_all = "kebab-case")
28)]
29#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
30#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
31pub enum BackgroundSize {
32 Explicit {
34 width: LengthPercentageOrAuto,
36 height: LengthPercentageOrAuto,
38 },
39 Cover,
41 Contain,
43}
44
45impl Default for BackgroundSize {
46 fn default() -> BackgroundSize {
47 BackgroundSize::Explicit {
48 width: LengthPercentageOrAuto::Auto,
49 height: LengthPercentageOrAuto::Auto,
50 }
51 }
52}
53
54impl<'i> Parse<'i> for BackgroundSize {
55 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
56 if let Ok(width) = input.try_parse(LengthPercentageOrAuto::parse) {
57 let height = input
58 .try_parse(LengthPercentageOrAuto::parse)
59 .unwrap_or(LengthPercentageOrAuto::Auto);
60 return Ok(BackgroundSize::Explicit { width, height });
61 }
62
63 let location = input.current_source_location();
64 let ident = input.expect_ident()?;
65 Ok(match_ignore_ascii_case! { ident,
66 "cover" => BackgroundSize::Cover,
67 "contain" => BackgroundSize::Contain,
68 _ => return Err(location.new_unexpected_token_error(
69 cssparser::Token::Ident(ident.clone())
70 ))
71 })
72 }
73}
74
75impl ToCss for BackgroundSize {
76 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
77 where
78 W: std::fmt::Write,
79 {
80 use BackgroundSize::*;
81
82 match &self {
83 Cover => dest.write_str("cover"),
84 Contain => dest.write_str("contain"),
85 Explicit { width, height } => {
86 width.to_css(dest)?;
87 if *height != LengthPercentageOrAuto::Auto {
88 dest.write_str(" ")?;
89 height.to_css(dest)?;
90 }
91 Ok(())
92 }
93 }
94 }
95}
96
97impl IsCompatible for BackgroundSize {
98 fn is_compatible(&self, browsers: Browsers) -> bool {
99 match self {
100 BackgroundSize::Explicit { width, height } => {
101 width.is_compatible(browsers) && height.is_compatible(browsers)
102 }
103 BackgroundSize::Cover | BackgroundSize::Contain => true,
104 }
105 }
106}
107
108enum_property! {
109 pub enum BackgroundRepeatKeyword {
115 Repeat,
117 Space,
119 Round,
121 NoRepeat,
123 }
124}
125
126#[derive(Debug, Clone, PartialEq)]
128#[cfg_attr(feature = "visitor", derive(Visit))]
129#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
130#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
131#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
132pub struct BackgroundRepeat {
133 pub x: BackgroundRepeatKeyword,
135 pub y: BackgroundRepeatKeyword,
137}
138
139impl Default for BackgroundRepeat {
140 fn default() -> BackgroundRepeat {
141 BackgroundRepeat {
142 x: BackgroundRepeatKeyword::Repeat,
143 y: BackgroundRepeatKeyword::Repeat,
144 }
145 }
146}
147
148impl<'i> Parse<'i> for BackgroundRepeat {
149 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
150 use BackgroundRepeatKeyword::*;
151 let state = input.state();
152 let ident = input.expect_ident()?;
153
154 match_ignore_ascii_case! { ident,
155 "repeat-x" => return Ok(BackgroundRepeat { x: Repeat, y: NoRepeat }),
156 "repeat-y" => return Ok(BackgroundRepeat { x: NoRepeat, y: Repeat }),
157 _ => {}
158 }
159
160 input.reset(&state);
161
162 let x = BackgroundRepeatKeyword::parse(input)?;
163 let y = input.try_parse(BackgroundRepeatKeyword::parse).unwrap_or(x.clone());
164 Ok(BackgroundRepeat { x, y })
165 }
166}
167
168impl ToCss for BackgroundRepeat {
169 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
170 where
171 W: std::fmt::Write,
172 {
173 use BackgroundRepeatKeyword::*;
174 match (&self.x, &self.y) {
175 (Repeat, NoRepeat) => dest.write_str("repeat-x"),
176 (NoRepeat, Repeat) => dest.write_str("repeat-y"),
177 (x, y) => {
178 x.to_css(dest)?;
179 if y != x {
180 dest.write_str(" ")?;
181 y.to_css(dest)?;
182 }
183 Ok(())
184 }
185 }
186 }
187}
188
189impl IsCompatible for BackgroundRepeat {
190 fn is_compatible(&self, _browsers: Browsers) -> bool {
191 true
192 }
193}
194
195enum_property! {
196 pub enum BackgroundAttachment {
198 Scroll,
200 Fixed,
202 Local,
204 }
205}
206
207impl Default for BackgroundAttachment {
208 fn default() -> BackgroundAttachment {
209 BackgroundAttachment::Scroll
210 }
211}
212
213enum_property! {
214 pub enum BackgroundOrigin {
216 BorderBox,
218 PaddingBox,
220 ContentBox,
222 }
223}
224
225enum_property! {
226 pub enum BackgroundClip {
228 BorderBox,
230 PaddingBox,
232 ContentBox,
234 Border,
236 Text,
238 }
239}
240
241impl PartialEq<BackgroundOrigin> for BackgroundClip {
242 fn eq(&self, other: &BackgroundOrigin) -> bool {
243 match (self, other) {
244 (BackgroundClip::BorderBox, BackgroundOrigin::BorderBox)
245 | (BackgroundClip::PaddingBox, BackgroundOrigin::PaddingBox)
246 | (BackgroundClip::ContentBox, BackgroundOrigin::ContentBox) => true,
247 _ => false,
248 }
249 }
250}
251
252impl Into<BackgroundClip> for BackgroundOrigin {
253 fn into(self) -> BackgroundClip {
254 match self {
255 BackgroundOrigin::BorderBox => BackgroundClip::BorderBox,
256 BackgroundOrigin::PaddingBox => BackgroundClip::PaddingBox,
257 BackgroundOrigin::ContentBox => BackgroundClip::ContentBox,
258 }
259 }
260}
261
262impl Default for BackgroundClip {
263 fn default() -> BackgroundClip {
264 BackgroundClip::BorderBox
265 }
266}
267
268impl BackgroundClip {
269 fn is_background_box(&self) -> bool {
270 matches!(
271 self,
272 BackgroundClip::BorderBox | BackgroundClip::PaddingBox | BackgroundClip::ContentBox
273 )
274 }
275}
276
277define_list_shorthand! {
278 pub struct BackgroundPosition {
280 x: BackgroundPositionX(HorizontalPosition),
282 y: BackgroundPositionY(VerticalPosition),
284 }
285}
286
287impl From<Position> for BackgroundPosition {
288 fn from(pos: Position) -> Self {
289 BackgroundPosition { x: pos.x, y: pos.y }
290 }
291}
292
293impl Into<Position> for &BackgroundPosition {
294 fn into(self) -> Position {
295 Position {
296 x: self.x.clone(),
297 y: self.y.clone(),
298 }
299 }
300}
301
302impl Default for BackgroundPosition {
303 fn default() -> Self {
304 Position::default().into()
305 }
306}
307
308impl<'i> Parse<'i> for BackgroundPosition {
309 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
310 let pos = Position::parse(input)?;
311 Ok(pos.into())
312 }
313}
314
315impl ToCss for BackgroundPosition {
316 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
317 where
318 W: std::fmt::Write,
319 {
320 let pos: Position = self.into();
321 pos.to_css(dest)
322 }
323}
324
325#[derive(Debug, Clone, PartialEq)]
327#[cfg_attr(feature = "visitor", derive(Visit))]
328#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
329#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
330#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
331pub struct Background<'i> {
332 #[cfg_attr(feature = "serde", serde(borrow))]
334 pub image: Image<'i>,
335 pub color: CssColor,
337 pub position: BackgroundPosition,
339 pub repeat: BackgroundRepeat,
341 pub size: BackgroundSize,
343 pub attachment: BackgroundAttachment,
345 pub origin: BackgroundOrigin,
347 pub clip: BackgroundClip,
349}
350
351impl<'i> Parse<'i> for Background<'i> {
352 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
353 let mut color: Option<CssColor> = None;
354 let mut position: Option<BackgroundPosition> = None;
355 let mut size: Option<BackgroundSize> = None;
356 let mut image: Option<Image> = None;
357 let mut repeat: Option<BackgroundRepeat> = None;
358 let mut attachment: Option<BackgroundAttachment> = None;
359 let mut origin: Option<BackgroundOrigin> = None;
360 let mut clip: Option<BackgroundClip> = None;
361
362 loop {
363 if color.is_none() {
365 if let Ok(value) = input.try_parse(CssColor::parse) {
366 color = Some(value);
367 continue;
368 }
369 }
370
371 if position.is_none() {
372 if let Ok(value) = input.try_parse(BackgroundPosition::parse) {
373 position = Some(value);
374
375 size = input
376 .try_parse(|input| {
377 input.expect_delim('/')?;
378 BackgroundSize::parse(input)
379 })
380 .ok();
381
382 continue;
383 }
384 }
385
386 if image.is_none() {
387 if let Ok(value) = input.try_parse(Image::parse) {
388 image = Some(value);
389 continue;
390 }
391 }
392
393 if repeat.is_none() {
394 if let Ok(value) = input.try_parse(BackgroundRepeat::parse) {
395 repeat = Some(value);
396 continue;
397 }
398 }
399
400 if attachment.is_none() {
401 if let Ok(value) = input.try_parse(BackgroundAttachment::parse) {
402 attachment = Some(value);
403 continue;
404 }
405 }
406
407 if origin.is_none() {
408 if let Ok(value) = input.try_parse(BackgroundOrigin::parse) {
409 origin = Some(value);
410 continue;
411 }
412 }
413
414 if clip.is_none() {
415 if let Ok(value) = input.try_parse(BackgroundClip::parse) {
416 clip = Some(value);
417 continue;
418 }
419 }
420
421 break;
422 }
423
424 if clip.is_none() {
425 if let Some(origin) = origin {
426 clip = Some(origin.into());
427 }
428 }
429
430 Ok(Background {
431 image: image.unwrap_or_default(),
432 color: color.unwrap_or_default(),
433 position: position.unwrap_or_default(),
434 repeat: repeat.unwrap_or_default(),
435 size: size.unwrap_or_default(),
436 attachment: attachment.unwrap_or_default(),
437 origin: origin.unwrap_or(BackgroundOrigin::PaddingBox),
438 clip: clip.unwrap_or(BackgroundClip::BorderBox),
439 })
440 }
441}
442
443impl<'i> ToCss for Background<'i> {
444 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
445 where
446 W: std::fmt::Write,
447 {
448 let mut has_output = false;
449
450 if self.color != CssColor::default() {
451 self.color.to_css(dest)?;
452 has_output = true;
453 }
454
455 if self.image != Image::default() {
456 if has_output {
457 dest.write_str(" ")?;
458 }
459 self.image.to_css(dest)?;
460 has_output = true;
461 }
462
463 let position: Position = (&self.position).into();
464 if !position.is_zero() || self.size != BackgroundSize::default() {
465 if has_output {
466 dest.write_str(" ")?;
467 }
468 position.to_css(dest)?;
469
470 if self.size != BackgroundSize::default() {
471 dest.delim('/', true)?;
472 self.size.to_css(dest)?;
473 }
474
475 has_output = true;
476 }
477
478 if self.repeat != BackgroundRepeat::default() {
479 if has_output {
480 dest.write_str(" ")?;
481 }
482
483 self.repeat.to_css(dest)?;
484 has_output = true;
485 }
486
487 if self.attachment != BackgroundAttachment::default() {
488 if has_output {
489 dest.write_str(" ")?;
490 }
491
492 self.attachment.to_css(dest)?;
493 has_output = true;
494 }
495
496 let output_padding_box = self.origin != BackgroundOrigin::PaddingBox
497 || (self.clip != BackgroundOrigin::BorderBox && self.clip.is_background_box());
498 if output_padding_box {
499 if has_output {
500 dest.write_str(" ")?;
501 }
502
503 self.origin.to_css(dest)?;
504 has_output = true;
505 }
506
507 if (output_padding_box && self.clip != self.origin) || self.clip != BackgroundOrigin::BorderBox {
508 if has_output {
509 dest.write_str(" ")?;
510 }
511
512 self.clip.to_css(dest)?;
513 has_output = true;
514 }
515
516 if !has_output {
518 if dest.minify {
519 self.position.to_css(dest)?;
521 } else {
522 dest.write_str("none")?;
523 }
524 }
525
526 Ok(())
527 }
528}
529
530impl<'i> ImageFallback<'i> for Background<'i> {
531 #[inline]
532 fn get_image(&self) -> &Image<'i> {
533 &self.image
534 }
535
536 #[inline]
537 fn with_image(&self, image: Image<'i>) -> Self {
538 Background { image, ..self.clone() }
539 }
540
541 #[inline]
542 fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {
543 self.color.get_necessary_fallbacks(targets) | self.get_image().get_necessary_fallbacks(targets)
544 }
545
546 #[inline]
547 fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
548 Background {
549 color: self.color.get_fallback(kind),
550 image: self.image.get_fallback(kind),
551 ..self.clone()
552 }
553 }
554}
555
556impl<'i> Shorthand<'i> for SmallVec<[Background<'i>; 1]> {
557 fn from_longhands(decls: &DeclarationBlock<'i>, vendor_prefix: VendorPrefix) -> Option<(Self, bool)> {
558 let mut color = None;
559 let mut images = None;
560 let mut x_positions = None;
561 let mut y_positions = None;
562 let mut repeats = None;
563 let mut sizes = None;
564 let mut attachments = None;
565 let mut origins = None;
566 let mut clips = None;
567
568 let mut count = 0;
569 let mut important_count = 0;
570 let mut length = None;
571 for (property, important) in decls.iter() {
572 let len = match property {
573 Property::BackgroundColor(value) => {
574 color = Some(value.clone());
575 count += 1;
576 if important {
577 important_count += 1;
578 }
579 continue;
580 }
581 Property::BackgroundImage(value) => {
582 images = Some(value.clone());
583 value.len()
584 }
585 Property::BackgroundPosition(value) => {
586 x_positions = Some(value.iter().map(|v| v.x.clone()).collect());
587 y_positions = Some(value.iter().map(|v| v.y.clone()).collect());
588 value.len()
589 }
590 Property::BackgroundPositionX(value) => {
591 x_positions = Some(value.clone());
592 value.len()
593 }
594 Property::BackgroundPositionY(value) => {
595 y_positions = Some(value.clone());
596 value.len()
597 }
598 Property::BackgroundRepeat(value) => {
599 repeats = Some(value.clone());
600 value.len()
601 }
602 Property::BackgroundSize(value) => {
603 sizes = Some(value.clone());
604 value.len()
605 }
606 Property::BackgroundAttachment(value) => {
607 attachments = Some(value.clone());
608 value.len()
609 }
610 Property::BackgroundOrigin(value) => {
611 origins = Some(value.clone());
612 value.len()
613 }
614 Property::BackgroundClip(value, vp) => {
615 if *vp != vendor_prefix {
616 return None;
617 }
618 clips = Some(value.clone());
619 value.len()
620 }
621 Property::Background(val) => {
622 color = Some(val.last().unwrap().color.clone());
623 images = Some(val.iter().map(|b| b.image.clone()).collect());
624 x_positions = Some(val.iter().map(|b| b.position.x.clone()).collect());
625 y_positions = Some(val.iter().map(|b| b.position.y.clone()).collect());
626 repeats = Some(val.iter().map(|b| b.repeat.clone()).collect());
627 sizes = Some(val.iter().map(|b| b.size.clone()).collect());
628 attachments = Some(val.iter().map(|b| b.attachment.clone()).collect());
629 origins = Some(val.iter().map(|b| b.origin.clone()).collect());
630 clips = Some(val.iter().map(|b| b.clip.clone()).collect());
631 val.len()
632 }
633 _ => continue,
634 };
635
636 if length.is_none() {
638 length = Some(len);
639 } else if length.unwrap() != len {
640 return None;
641 }
642
643 count += 1;
644 if important {
645 important_count += 1;
646 }
647 }
648
649 if important_count > 0 && important_count != count {
651 return None;
652 }
653
654 if color.is_some()
655 && images.is_some()
656 && x_positions.is_some()
657 && y_positions.is_some()
658 && repeats.is_some()
659 && sizes.is_some()
660 && attachments.is_some()
661 && origins.is_some()
662 && clips.is_some()
663 {
664 let length = length.unwrap();
665 let values = izip!(
666 images.unwrap().drain(..),
667 x_positions.unwrap().drain(..),
668 y_positions.unwrap().drain(..),
669 repeats.unwrap().drain(..),
670 sizes.unwrap().drain(..),
671 attachments.unwrap().drain(..),
672 origins.unwrap().drain(..),
673 clips.unwrap().drain(..),
674 )
675 .enumerate()
676 .map(
677 |(i, (image, x_position, y_position, repeat, size, attachment, origin, clip))| Background {
678 color: if i == length - 1 {
679 color.clone().unwrap()
680 } else {
681 CssColor::default()
682 },
683 image,
684 position: BackgroundPosition {
685 x: x_position,
686 y: y_position,
687 },
688 repeat,
689 size,
690 attachment,
691 origin,
692 clip: clip,
693 },
694 )
695 .collect();
696 return Some((values, important_count > 0));
697 }
698
699 None
700 }
701
702 fn longhands(vendor_prefix: VendorPrefix) -> Vec<PropertyId<'static>> {
703 vec![
704 PropertyId::BackgroundColor,
705 PropertyId::BackgroundImage,
706 PropertyId::BackgroundPositionX,
707 PropertyId::BackgroundPositionY,
708 PropertyId::BackgroundRepeat,
709 PropertyId::BackgroundSize,
710 PropertyId::BackgroundAttachment,
711 PropertyId::BackgroundOrigin,
712 PropertyId::BackgroundClip(vendor_prefix),
713 ]
714 }
715
716 fn longhand(&self, property_id: &PropertyId) -> Option<Property<'i>> {
717 match property_id {
718 PropertyId::BackgroundColor => Some(Property::BackgroundColor(self.last().unwrap().color.clone())),
719 PropertyId::BackgroundImage => Some(Property::BackgroundImage(
720 self.iter().map(|v| v.image.clone()).collect(),
721 )),
722 PropertyId::BackgroundPositionX => Some(Property::BackgroundPositionX(
723 self.iter().map(|v| v.position.x.clone()).collect(),
724 )),
725 PropertyId::BackgroundPositionY => Some(Property::BackgroundPositionY(
726 self.iter().map(|v| v.position.y.clone()).collect(),
727 )),
728 PropertyId::BackgroundRepeat => Some(Property::BackgroundRepeat(
729 self.iter().map(|v| v.repeat.clone()).collect(),
730 )),
731 PropertyId::BackgroundSize => Some(Property::BackgroundSize(self.iter().map(|v| v.size.clone()).collect())),
732 PropertyId::BackgroundAttachment => Some(Property::BackgroundAttachment(
733 self.iter().map(|v| v.attachment.clone()).collect(),
734 )),
735 PropertyId::BackgroundOrigin => Some(Property::BackgroundOrigin(
736 self.iter().map(|v| v.origin.clone()).collect(),
737 )),
738 PropertyId::BackgroundClip(vp) => Some(Property::BackgroundClip(
739 self.iter().map(|v| v.clip.clone()).collect(),
740 *vp,
741 )),
742 _ => None,
743 }
744 }
745
746 fn set_longhand(&mut self, property: &Property<'i>) -> Result<(), ()> {
747 macro_rules! longhand {
748 ($value: ident, $key: ident $(.$k: ident)*) => {{
749 if $value.len() != self.len() {
750 return Err(());
751 }
752 for (i, item) in self.iter_mut().enumerate() {
753 item.$key$(.$k)* = $value[i].clone();
754 }
755 }};
756 }
757
758 match property {
759 Property::BackgroundColor(value) => self.last_mut().unwrap().color = value.clone(),
760 Property::BackgroundImage(value) => longhand!(value, image),
761 Property::BackgroundPositionX(value) => longhand!(value, position.x),
762 Property::BackgroundPositionY(value) => longhand!(value, position.y),
763 Property::BackgroundPosition(value) => longhand!(value, position),
764 Property::BackgroundRepeat(value) => longhand!(value, repeat),
765 Property::BackgroundSize(value) => longhand!(value, size),
766 Property::BackgroundAttachment(value) => longhand!(value, attachment),
767 Property::BackgroundOrigin(value) => longhand!(value, origin),
768 Property::BackgroundClip(value, _vp) => longhand!(value, clip),
769 _ => return Err(()),
770 }
771
772 Ok(())
773 }
774}
775
776property_bitflags! {
777 #[derive(Default)]
778 struct BackgroundProperty: u16 {
779 const BackgroundColor = 1 << 0;
780 const BackgroundImage = 1 << 1;
781 const BackgroundPositionX = 1 << 2;
782 const BackgroundPositionY = 1 << 3;
783 const BackgroundPosition = Self::BackgroundPositionX.bits() | Self::BackgroundPositionY.bits();
784 const BackgroundRepeat = 1 << 4;
785 const BackgroundSize = 1 << 5;
786 const BackgroundAttachment = 1 << 6;
787 const BackgroundOrigin = 1 << 7;
788 const BackgroundClip(_vp) = 1 << 8;
789 const Background = Self::BackgroundColor.bits() | Self::BackgroundImage.bits() | Self::BackgroundPosition.bits() | Self::BackgroundRepeat.bits() | Self::BackgroundSize.bits() | Self::BackgroundAttachment.bits() | Self::BackgroundOrigin.bits() | Self::BackgroundClip.bits();
790 }
791}
792
793#[derive(Default)]
794pub(crate) struct BackgroundHandler<'i> {
795 color: Option<CssColor>,
796 images: Option<SmallVec<[Image<'i>; 1]>>,
797 has_prefix: bool,
798 x_positions: Option<SmallVec<[HorizontalPosition; 1]>>,
799 y_positions: Option<SmallVec<[VerticalPosition; 1]>>,
800 repeats: Option<SmallVec<[BackgroundRepeat; 1]>>,
801 sizes: Option<SmallVec<[BackgroundSize; 1]>>,
802 attachments: Option<SmallVec<[BackgroundAttachment; 1]>>,
803 origins: Option<SmallVec<[BackgroundOrigin; 1]>>,
804 clips: Option<(SmallVec<[BackgroundClip; 1]>, VendorPrefix)>,
805 decls: Vec<Property<'i>>,
806 flushed_properties: BackgroundProperty,
807 has_any: bool,
808}
809
810impl<'i> PropertyHandler<'i> for BackgroundHandler<'i> {
811 fn handle_property(
812 &mut self,
813 property: &Property<'i>,
814 dest: &mut DeclarationList<'i>,
815 context: &mut PropertyHandlerContext<'i, '_>,
816 ) -> bool {
817 macro_rules! background_image {
818 ($val: expr) => {
819 flush!(images, $val);
820
821 self.has_prefix = $val.iter().any(|x| x.has_vendor_prefix());
824 if self.has_prefix {
825 self.decls.push(property.clone())
826 } else if context.targets.browsers.is_some() {
827 self.decls.clear();
828 }
829 };
830 }
831
832 macro_rules! flush {
833 ($key: ident, $val: expr) => {{
834 if self.$key.is_some() && self.$key.as_ref().unwrap() != $val && matches!(context.targets.browsers, Some(targets) if !$val.is_compatible(targets)) {
835 self.flush(dest, context);
836 }
837 }};
838 }
839
840 match &property {
841 Property::BackgroundColor(val) => {
842 flush!(color, val);
843 self.color = Some(val.clone());
844 }
845 Property::BackgroundImage(val) => {
846 background_image!(val);
847 self.images = Some(val.clone())
848 }
849 Property::BackgroundPosition(val) => {
850 self.x_positions = Some(val.iter().map(|p| p.x.clone()).collect());
851 self.y_positions = Some(val.iter().map(|p| p.y.clone()).collect());
852 }
853 Property::BackgroundPositionX(val) => self.x_positions = Some(val.clone()),
854 Property::BackgroundPositionY(val) => self.y_positions = Some(val.clone()),
855 Property::BackgroundRepeat(val) => self.repeats = Some(val.clone()),
856 Property::BackgroundSize(val) => self.sizes = Some(val.clone()),
857 Property::BackgroundAttachment(val) => self.attachments = Some(val.clone()),
858 Property::BackgroundOrigin(val) => self.origins = Some(val.clone()),
859 Property::BackgroundClip(val, vendor_prefix) => {
860 if let Some((clips, vp)) = &mut self.clips {
861 if vendor_prefix != vp && val != clips {
862 self.flush(dest, context);
863 self.clips = Some((val.clone(), *vendor_prefix))
864 } else {
865 if val != clips {
866 *clips = val.clone();
867 }
868 *vp |= *vendor_prefix;
869 }
870 } else {
871 self.clips = Some((val.clone(), *vendor_prefix))
872 }
873 }
874 Property::Background(val) => {
875 let images: SmallVec<[Image; 1]> = val.iter().map(|b| b.image.clone()).collect();
876 background_image!(&images);
877 let color = val.last().unwrap().color.clone();
878 flush!(color, &color);
879 let clips = val.iter().map(|b| b.clip.clone()).collect();
880 let mut clips_vp = VendorPrefix::None;
881 if let Some((cur_clips, cur_clips_vp)) = &mut self.clips {
882 if clips_vp != *cur_clips_vp && *cur_clips != clips {
883 self.flush(dest, context);
884 } else {
885 clips_vp |= *cur_clips_vp;
886 }
887 }
888 self.color = Some(color);
889 self.images = Some(images);
890 self.x_positions = Some(val.iter().map(|b| b.position.x.clone()).collect());
891 self.y_positions = Some(val.iter().map(|b| b.position.y.clone()).collect());
892 self.repeats = Some(val.iter().map(|b| b.repeat.clone()).collect());
893 self.sizes = Some(val.iter().map(|b| b.size.clone()).collect());
894 self.attachments = Some(val.iter().map(|b| b.attachment.clone()).collect());
895 self.origins = Some(val.iter().map(|b| b.origin.clone()).collect());
896 self.clips = Some((clips, clips_vp));
897 }
898 Property::Unparsed(val) if is_background_property(&val.property_id) => {
899 self.flush(dest, context);
900 let mut unparsed = val.clone();
901 context.add_unparsed_fallbacks(&mut unparsed);
902 self
903 .flushed_properties
904 .insert(BackgroundProperty::try_from(&unparsed.property_id).unwrap());
905 dest.push(Property::Unparsed(unparsed));
906 }
907 _ => return false,
908 }
909
910 self.has_any = true;
911 true
912 }
913
914 fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
915 if self.has_prefix {
918 self.decls.pop();
919 }
920
921 dest.extend(self.decls.drain(..));
922 self.flush(dest, context);
923 self.flushed_properties = BackgroundProperty::empty();
924 }
925}
926
927impl<'i> BackgroundHandler<'i> {
928 fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
929 if !self.has_any {
930 return;
931 }
932
933 self.has_any = false;
934
935 macro_rules! push {
936 ($prop: ident, $val: expr) => {
937 dest.push(Property::$prop($val));
938 self.flushed_properties.insert(BackgroundProperty::$prop);
939 };
940 }
941
942 let color = std::mem::take(&mut self.color);
943 let mut images = std::mem::take(&mut self.images);
944 let mut x_positions = std::mem::take(&mut self.x_positions);
945 let mut y_positions = std::mem::take(&mut self.y_positions);
946 let mut repeats = std::mem::take(&mut self.repeats);
947 let mut sizes = std::mem::take(&mut self.sizes);
948 let mut attachments = std::mem::take(&mut self.attachments);
949 let mut origins = std::mem::take(&mut self.origins);
950 let mut clips = std::mem::take(&mut self.clips);
951
952 if let (
953 Some(color),
954 Some(images),
955 Some(x_positions),
956 Some(y_positions),
957 Some(repeats),
958 Some(sizes),
959 Some(attachments),
960 Some(origins),
961 Some(clips),
962 ) = (
963 &color,
964 &mut images,
965 &mut x_positions,
966 &mut y_positions,
967 &mut repeats,
968 &mut sizes,
969 &mut attachments,
970 &mut origins,
971 &mut clips,
972 ) {
973 let len = images.len();
975 if x_positions.len() == len
976 && y_positions.len() == len
977 && repeats.len() == len
978 && sizes.len() == len
979 && attachments.len() == len
980 && origins.len() == len
981 && clips.0.len() == len
982 {
983 let clip_prefixes = if clips.0.iter().any(|clip| *clip == BackgroundClip::Text) {
984 context.targets.prefixes(clips.1, Feature::BackgroundClip)
985 } else {
986 clips.1
987 };
988
989 let clip_property = if clip_prefixes != VendorPrefix::None {
990 Some(Property::BackgroundClip(clips.0.clone(), clip_prefixes))
991 } else {
992 None
993 };
994
995 let mut backgrounds: SmallVec<[Background<'i>; 1]> = izip!(
996 images.drain(..),
997 x_positions.drain(..),
998 y_positions.drain(..),
999 repeats.drain(..),
1000 sizes.drain(..),
1001 attachments.drain(..),
1002 origins.drain(..),
1003 clips.0.drain(..)
1004 )
1005 .enumerate()
1006 .map(
1007 |(i, (image, x_position, y_position, repeat, size, attachment, origin, clip))| Background {
1008 color: if i == len - 1 {
1009 color.clone()
1010 } else {
1011 CssColor::default()
1012 },
1013 image,
1014 position: BackgroundPosition {
1015 x: x_position,
1016 y: y_position,
1017 },
1018 repeat,
1019 size,
1020 attachment,
1021 origin,
1022 clip: if clip_prefixes == VendorPrefix::None {
1023 clip
1024 } else {
1025 BackgroundClip::default()
1026 },
1027 },
1028 )
1029 .collect();
1030
1031 if !self.flushed_properties.intersects(BackgroundProperty::Background) {
1032 for fallback in backgrounds.get_fallbacks(context.targets) {
1033 push!(Background, fallback);
1034 }
1035 }
1036
1037 push!(Background, backgrounds);
1038
1039 if let Some(clip) = clip_property {
1040 dest.push(clip);
1041 self.flushed_properties.insert(BackgroundProperty::BackgroundClip);
1042 }
1043
1044 self.reset();
1045 return;
1046 }
1047 }
1048
1049 if let Some(mut color) = color {
1050 if !self.flushed_properties.contains(BackgroundProperty::BackgroundColor) {
1051 for fallback in color.get_fallbacks(context.targets) {
1052 push!(BackgroundColor, fallback);
1053 }
1054 }
1055
1056 push!(BackgroundColor, color);
1057 }
1058
1059 if let Some(mut images) = images {
1060 if !self.flushed_properties.contains(BackgroundProperty::BackgroundImage) {
1061 for fallback in images.get_fallbacks(context.targets) {
1062 push!(BackgroundImage, fallback);
1063 }
1064 }
1065
1066 push!(BackgroundImage, images);
1067 }
1068
1069 match (&mut x_positions, &mut y_positions) {
1070 (Some(x_positions), Some(y_positions)) if x_positions.len() == y_positions.len() => {
1071 let positions = izip!(x_positions.drain(..), y_positions.drain(..))
1072 .map(|(x, y)| BackgroundPosition { x, y })
1073 .collect();
1074 push!(BackgroundPosition, positions);
1075 }
1076 _ => {
1077 if let Some(x_positions) = x_positions {
1078 push!(BackgroundPositionX, x_positions);
1079 }
1080
1081 if let Some(y_positions) = y_positions {
1082 push!(BackgroundPositionY, y_positions);
1083 }
1084 }
1085 }
1086
1087 if let Some(repeats) = repeats {
1088 push!(BackgroundRepeat, repeats);
1089 }
1090
1091 if let Some(sizes) = sizes {
1092 push!(BackgroundSize, sizes);
1093 }
1094
1095 if let Some(attachments) = attachments {
1096 push!(BackgroundAttachment, attachments);
1097 }
1098
1099 if let Some(origins) = origins {
1100 push!(BackgroundOrigin, origins);
1101 }
1102
1103 if let Some((clips, vp)) = clips {
1104 let prefixes = if clips.iter().any(|clip| *clip == BackgroundClip::Text) {
1105 context.targets.prefixes(vp, Feature::BackgroundClip)
1106 } else {
1107 vp
1108 };
1109 dest.push(Property::BackgroundClip(clips, prefixes));
1110 self.flushed_properties.insert(BackgroundProperty::BackgroundClip);
1111 }
1112
1113 self.reset();
1114 }
1115
1116 fn reset(&mut self) {
1117 self.color = None;
1118 self.images = None;
1119 self.x_positions = None;
1120 self.y_positions = None;
1121 self.repeats = None;
1122 self.sizes = None;
1123 self.attachments = None;
1124 self.origins = None;
1125 self.clips = None
1126 }
1127}
1128
1129#[inline]
1130fn is_background_property(property_id: &PropertyId) -> bool {
1131 match property_id {
1132 PropertyId::BackgroundColor
1133 | PropertyId::BackgroundImage
1134 | PropertyId::BackgroundPosition
1135 | PropertyId::BackgroundPositionX
1136 | PropertyId::BackgroundPositionY
1137 | PropertyId::BackgroundRepeat
1138 | PropertyId::BackgroundSize
1139 | PropertyId::BackgroundAttachment
1140 | PropertyId::BackgroundOrigin
1141 | PropertyId::BackgroundClip(_)
1142 | PropertyId::Background => true,
1143 _ => false,
1144 }
1145}