style_traits/values.rs
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//! Helper types and traits for the handling of CSS values.
6
7use app_units::Au;
8use cssparser::ToCss as CssparserToCss;
9use cssparser::{serialize_string, ParseError, Parser, Token, UnicodeRange};
10use servo_arc::Arc;
11use std::fmt::{self, Write};
12
13/// Serialises a value according to its CSS representation.
14///
15/// This trait is implemented for `str` and its friends, serialising the string
16/// contents as a CSS quoted string.
17///
18/// This trait is derivable with `#[derive(ToCss)]`, with the following behaviour:
19/// * unit variants get serialised as the `snake-case` representation
20/// of their name;
21/// * unit variants whose name starts with "Moz" or "Webkit" are prepended
22/// with a "-";
23/// * if `#[css(comma)]` is found on a variant, its fields are separated by
24/// commas, otherwise, by spaces;
25/// * if `#[css(function)]` is found on a variant, the variant name gets
26/// serialised like unit variants and its fields are surrounded by parentheses;
27/// * if `#[css(iterable)]` is found on a function variant, that variant needs
28/// to have a single member, and that member needs to be iterable. The
29/// iterable will be serialized as the arguments for the function;
30/// * an iterable field can also be annotated with `#[css(if_empty = "foo")]`
31/// to print `"foo"` if the iterator is empty;
32/// * if `#[css(dimension)]` is found on a variant, that variant needs
33/// to have a single member. The variant would be serialized as a CSS
34/// dimension token, like: <member><identifier>;
35/// * if `#[css(skip)]` is found on a field, the `ToCss` call for that field
36/// is skipped;
37/// * if `#[css(skip_if = "function")]` is found on a field, the `ToCss` call
38/// for that field is skipped if `function` returns true. This function is
39/// provided the field as an argument;
40/// * if `#[css(contextual_skip_if = "function")]` is found on a field, the
41/// `ToCss` call for that field is skipped if `function` returns true. This
42/// function is given all the fields in the current struct or variant as an
43/// argument;
44/// * `#[css(represents_keyword)]` can be used on bool fields in order to
45/// serialize the field name if the field is true, or nothing otherwise. It
46/// also collects those keywords for `SpecifiedValueInfo`.
47/// * `#[css(bitflags(single="", mixed="", validate_mixed="", overlapping_bits)]` can
48/// be used to derive parse / serialize / etc on bitflags. The rules for parsing
49/// bitflags are the following:
50///
51/// * `single` flags can only appear on their own. It's common that bitflags
52/// properties at least have one such value like `none` or `auto`.
53/// * `mixed` properties can appear mixed together, but not along any other
54/// flag that shares a bit with itself. For example, if you have three
55/// bitflags like:
56///
57/// FOO = 1 << 0;
58/// BAR = 1 << 1;
59/// BAZ = 1 << 2;
60/// BAZZ = BAR | BAZ;
61///
62/// Then the following combinations won't be valid:
63///
64/// * foo foo: (every flag shares a bit with itself)
65/// * bar bazz: (bazz shares a bit with bar)
66///
67/// But `bar baz` will be valid, as they don't share bits, and so would
68/// `foo` with any other flag, or `bazz` on its own.
69/// * `validate_mixed` can be used to reject invalid mixed combinations, and also to simplify
70/// the type or add default ones if needed.
71/// * `overlapping_bits` enables some tracking during serialization of mixed flags to avoid
72/// serializing variants that can subsume other variants.
73/// In the example above, you could do:
74/// mixed="foo,bazz,bar,baz", overlapping_bits
75/// to ensure that if bazz is serialized, bar and baz aren't, even though
76/// their bits are set. Note that the serialization order is canonical,
77/// and thus depends on the order you specify the flags in.
78///
79/// * finally, one can put `#[css(derive_debug)]` on the whole type, to
80/// implement `Debug` by a single call to `ToCss::to_css`.
81pub trait ToCss {
82 /// Serialize `self` in CSS syntax, writing to `dest`.
83 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
84 where
85 W: Write;
86
87 /// Serialize `self` in CSS syntax and return a string.
88 ///
89 /// (This is a convenience wrapper for `to_css` and probably should not be overridden.)
90 #[inline]
91 fn to_css_string(&self) -> String {
92 let mut s = String::new();
93 self.to_css(&mut CssWriter::new(&mut s)).unwrap();
94 s
95 }
96
97 /// Serialize `self` in CSS syntax and return a CssString.
98 ///
99 /// (This is a convenience wrapper for `to_css` and probably should not be overridden.)
100 #[inline]
101 fn to_css_cssstring(&self) -> CssString {
102 let mut s = CssString::new();
103 self.to_css(&mut CssWriter::new(&mut s)).unwrap();
104 s
105 }
106}
107
108impl<'a, T> ToCss for &'a T
109where
110 T: ToCss + ?Sized,
111{
112 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
113 where
114 W: Write,
115 {
116 (*self).to_css(dest)
117 }
118}
119
120impl ToCss for crate::owned_str::OwnedStr {
121 #[inline]
122 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
123 where
124 W: Write,
125 {
126 serialize_string(self, dest)
127 }
128}
129
130impl ToCss for str {
131 #[inline]
132 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
133 where
134 W: Write,
135 {
136 serialize_string(self, dest)
137 }
138}
139
140impl ToCss for String {
141 #[inline]
142 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
143 where
144 W: Write,
145 {
146 serialize_string(self, dest)
147 }
148}
149
150impl<T> ToCss for Option<T>
151where
152 T: ToCss,
153{
154 #[inline]
155 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
156 where
157 W: Write,
158 {
159 self.as_ref().map_or(Ok(()), |value| value.to_css(dest))
160 }
161}
162
163impl ToCss for () {
164 #[inline]
165 fn to_css<W>(&self, _: &mut CssWriter<W>) -> fmt::Result
166 where
167 W: Write,
168 {
169 Ok(())
170 }
171}
172
173/// A writer tailored for serialising CSS.
174///
175/// Coupled with SequenceWriter, this allows callers to transparently handle
176/// things like comma-separated values etc.
177pub struct CssWriter<'w, W: 'w> {
178 inner: &'w mut W,
179 prefix: Option<&'static str>,
180}
181
182impl<'w, W> CssWriter<'w, W>
183where
184 W: Write,
185{
186 /// Creates a new `CssWriter`.
187 #[inline]
188 pub fn new(inner: &'w mut W) -> Self {
189 Self {
190 inner,
191 prefix: Some(""),
192 }
193 }
194}
195
196impl<'w, W> Write for CssWriter<'w, W>
197where
198 W: Write,
199{
200 #[inline]
201 fn write_str(&mut self, s: &str) -> fmt::Result {
202 if s.is_empty() {
203 return Ok(());
204 }
205 if let Some(prefix) = self.prefix.take() {
206 // We are going to write things, but first we need to write
207 // the prefix that was set by `SequenceWriter::item`.
208 if !prefix.is_empty() {
209 self.inner.write_str(prefix)?;
210 }
211 }
212 self.inner.write_str(s)
213 }
214
215 #[inline]
216 fn write_char(&mut self, c: char) -> fmt::Result {
217 if let Some(prefix) = self.prefix.take() {
218 // See comment in `write_str`.
219 if !prefix.is_empty() {
220 self.inner.write_str(prefix)?;
221 }
222 }
223 self.inner.write_char(c)
224 }
225}
226
227/// To avoid accidentally instantiating multiple monomorphizations of large
228/// serialization routines, we define explicit concrete types and require
229/// them in those routines. This avoids accidental mixing of String and
230/// nsACString arguments in Gecko, which would cause code size to blow up.
231#[cfg(feature = "gecko")]
232pub type CssStringWriter = ::nsstring::nsACString;
233
234/// String type that coerces to CssStringWriter, used when serialization code
235/// needs to allocate a temporary string. In Gecko, this is backed by
236/// nsCString, which allows the result to be passed directly to C++ without
237/// conversion or copying. This makes it suitable not only for temporary
238/// serialization but also for values that need to cross the Rust/C++ boundary.
239#[cfg(feature = "gecko")]
240pub type CssString = ::nsstring::nsCString;
241
242/// String. The comments for the Gecko types explain the need for this abstraction.
243#[cfg(feature = "servo")]
244pub type CssStringWriter = String;
245
246/// String. The comments for the Gecko types explain the need for this abstraction.
247#[cfg(feature = "servo")]
248pub type CssString = String;
249
250/// Convenience wrapper to serialise CSS values separated by a given string.
251pub struct SequenceWriter<'a, 'b: 'a, W: 'b> {
252 inner: &'a mut CssWriter<'b, W>,
253 separator: &'static str,
254}
255
256impl<'a, 'b, W> SequenceWriter<'a, 'b, W>
257where
258 W: Write + 'b,
259{
260 /// Create a new sequence writer.
261 #[inline]
262 pub fn new(inner: &'a mut CssWriter<'b, W>, separator: &'static str) -> Self {
263 if inner.prefix.is_none() {
264 // See comment in `item`.
265 inner.prefix = Some("");
266 }
267 Self { inner, separator }
268 }
269
270 /// Serialize the CSS Value with the specific serialization function.
271 #[inline]
272 pub fn write_item<F>(&mut self, f: F) -> fmt::Result
273 where
274 F: FnOnce(&mut CssWriter<'b, W>) -> fmt::Result,
275 {
276 // Separate non-generic functions so that this code is not repeated
277 // in every monomorphization with a different type `F` or `W`.
278 // https://github.com/servo/servo/issues/26713
279 fn before(
280 prefix: &mut Option<&'static str>,
281 separator: &'static str,
282 ) -> Option<&'static str> {
283 let old_prefix = *prefix;
284 if old_prefix.is_none() {
285 // If there is no prefix in the inner writer, a previous
286 // call to this method produced output, which means we need
287 // to write the separator next time we produce output again.
288 *prefix = Some(separator);
289 }
290 old_prefix
291 }
292 fn after(
293 old_prefix: Option<&'static str>,
294 prefix: &mut Option<&'static str>,
295 separator: &'static str,
296 ) {
297 match (old_prefix, *prefix) {
298 (_, None) => {
299 // This call produced output and cleaned up after itself.
300 },
301 (None, Some(p)) => {
302 // Some previous call to `item` produced output,
303 // but this one did not, prefix should be the same as
304 // the one we set.
305 debug_assert_eq!(separator, p);
306 // We clean up here even though it's not necessary just
307 // to be able to do all these assertion checks.
308 *prefix = None;
309 },
310 (Some(old), Some(new)) => {
311 // No previous call to `item` produced output, and this one
312 // either.
313 debug_assert_eq!(old, new);
314 },
315 }
316 }
317
318 let old_prefix = before(&mut self.inner.prefix, self.separator);
319 f(self.inner)?;
320 after(old_prefix, &mut self.inner.prefix, self.separator);
321 Ok(())
322 }
323
324 /// Serialises a CSS value, writing any separator as necessary.
325 ///
326 /// The separator is never written before any `item` produces any output,
327 /// and is written in subsequent calls only if the `item` produces some
328 /// output on its own again. This lets us handle `Option<T>` fields by
329 /// just not printing anything on `None`.
330 #[inline]
331 pub fn item<T>(&mut self, item: &T) -> fmt::Result
332 where
333 T: ToCss,
334 {
335 self.write_item(|inner| item.to_css(inner))
336 }
337
338 /// Writes a string as-is (i.e. not escaped or wrapped in quotes)
339 /// with any separator as necessary.
340 ///
341 /// See SequenceWriter::item.
342 #[inline]
343 pub fn raw_item(&mut self, item: &str) -> fmt::Result {
344 self.write_item(|inner| inner.write_str(item))
345 }
346}
347
348/// Type used as the associated type in the `OneOrMoreSeparated` trait on a
349/// type to indicate that a serialized list of elements of this type is
350/// separated by commas.
351pub struct Comma;
352
353/// Type used as the associated type in the `OneOrMoreSeparated` trait on a
354/// type to indicate that a serialized list of elements of this type is
355/// separated by spaces.
356pub struct Space;
357
358/// Type used as the associated type in the `OneOrMoreSeparated` trait on a
359/// type to indicate that a serialized list of elements of this type is
360/// separated by commas, but spaces without commas are also allowed when
361/// parsing.
362pub struct CommaWithSpace;
363
364/// A trait satisfied by the types corresponding to separators.
365pub trait Separator {
366 /// The separator string that the satisfying separator type corresponds to.
367 fn separator() -> &'static str;
368
369 /// Parses a sequence of values separated by this separator.
370 ///
371 /// The given closure is called repeatedly for each item in the sequence.
372 ///
373 /// Successful results are accumulated in a vector.
374 ///
375 /// This method returns `Err(_)` the first time a closure does or if
376 /// the separators aren't correct.
377 fn parse<'i, 't, F, T, E>(
378 parser: &mut Parser<'i, 't>,
379 parse_one: F,
380 ) -> Result<Vec<T>, ParseError<'i, E>>
381 where
382 F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>;
383}
384
385impl Separator for Comma {
386 fn separator() -> &'static str {
387 ", "
388 }
389
390 fn parse<'i, 't, F, T, E>(
391 input: &mut Parser<'i, 't>,
392 parse_one: F,
393 ) -> Result<Vec<T>, ParseError<'i, E>>
394 where
395 F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>,
396 {
397 input.parse_comma_separated(parse_one)
398 }
399}
400
401impl Separator for Space {
402 fn separator() -> &'static str {
403 " "
404 }
405
406 fn parse<'i, 't, F, T, E>(
407 input: &mut Parser<'i, 't>,
408 mut parse_one: F,
409 ) -> Result<Vec<T>, ParseError<'i, E>>
410 where
411 F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>,
412 {
413 input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
414 let mut results = vec![parse_one(input)?];
415 loop {
416 input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
417 if let Ok(item) = input.try_parse(&mut parse_one) {
418 results.push(item);
419 } else {
420 return Ok(results);
421 }
422 }
423 }
424}
425
426impl Separator for CommaWithSpace {
427 fn separator() -> &'static str {
428 ", "
429 }
430
431 fn parse<'i, 't, F, T, E>(
432 input: &mut Parser<'i, 't>,
433 mut parse_one: F,
434 ) -> Result<Vec<T>, ParseError<'i, E>>
435 where
436 F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>,
437 {
438 input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
439 let mut results = vec![parse_one(input)?];
440 loop {
441 input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
442 let comma_location = input.current_source_location();
443 let comma = input.try_parse(|i| i.expect_comma()).is_ok();
444 input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
445 if let Ok(item) = input.try_parse(&mut parse_one) {
446 results.push(item);
447 } else if comma {
448 return Err(comma_location.new_unexpected_token_error(Token::Comma));
449 } else {
450 break;
451 }
452 }
453 Ok(results)
454 }
455}
456
457/// Marker trait on T to automatically implement ToCss for Vec<T> when T's are
458/// separated by some delimiter `delim`.
459pub trait OneOrMoreSeparated {
460 /// Associated type indicating which separator is used.
461 type S: Separator;
462}
463
464impl OneOrMoreSeparated for UnicodeRange {
465 type S = Comma;
466}
467
468impl<T> ToCss for Vec<T>
469where
470 T: ToCss + OneOrMoreSeparated,
471{
472 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
473 where
474 W: Write,
475 {
476 let mut iter = self.iter();
477 iter.next().unwrap().to_css(dest)?;
478 for item in iter {
479 dest.write_str(<T as OneOrMoreSeparated>::S::separator())?;
480 item.to_css(dest)?;
481 }
482 Ok(())
483 }
484}
485
486impl<T> ToCss for Box<T>
487where
488 T: ?Sized + ToCss,
489{
490 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
491 where
492 W: Write,
493 {
494 (**self).to_css(dest)
495 }
496}
497
498impl<T> ToCss for Arc<T>
499where
500 T: ?Sized + ToCss,
501{
502 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
503 where
504 W: Write,
505 {
506 (**self).to_css(dest)
507 }
508}
509
510impl ToCss for Au {
511 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
512 where
513 W: Write,
514 {
515 self.to_f64_px().to_css(dest)?;
516 dest.write_str("px")
517 }
518}
519
520macro_rules! impl_to_css_for_predefined_type {
521 ($name: ty) => {
522 impl<'a> ToCss for $name {
523 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
524 where
525 W: Write,
526 {
527 ::cssparser::ToCss::to_css(self, dest)
528 }
529 }
530 };
531}
532
533impl_to_css_for_predefined_type!(f32);
534impl_to_css_for_predefined_type!(i8);
535impl_to_css_for_predefined_type!(i32);
536impl_to_css_for_predefined_type!(u8);
537impl_to_css_for_predefined_type!(u16);
538impl_to_css_for_predefined_type!(u32);
539impl_to_css_for_predefined_type!(::cssparser::Token<'a>);
540impl_to_css_for_predefined_type!(::cssparser::UnicodeRange);
541
542/// Helper types for the handling of specified values.
543pub mod specified {
544 use crate::ParsingMode;
545
546 /// Whether to allow negative lengths or not.
547 #[repr(u8)]
548 #[derive(
549 Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, PartialOrd, Serialize, ToShmem,
550 )]
551 pub enum AllowedNumericType {
552 /// Allow all kind of numeric values.
553 All,
554 /// Allow only non-negative numeric values.
555 NonNegative,
556 /// Allow only numeric values greater or equal to 1.0.
557 AtLeastOne,
558 /// Allow only numeric values from 0 to 1.0.
559 ZeroToOne,
560 }
561
562 impl Default for AllowedNumericType {
563 #[inline]
564 fn default() -> Self {
565 AllowedNumericType::All
566 }
567 }
568
569 impl AllowedNumericType {
570 /// Whether the value fits the rules of this numeric type.
571 #[inline]
572 pub fn is_ok(&self, parsing_mode: ParsingMode, val: f32) -> bool {
573 if parsing_mode.allows_all_numeric_values() {
574 return true;
575 }
576 match *self {
577 AllowedNumericType::All => true,
578 AllowedNumericType::NonNegative => val >= 0.0,
579 AllowedNumericType::AtLeastOne => val >= 1.0,
580 AllowedNumericType::ZeroToOne => val >= 0.0 && val <= 1.0,
581 }
582 }
583
584 /// Clamp the value following the rules of this numeric type.
585 #[inline]
586 pub fn clamp(&self, val: f32) -> f32 {
587 match *self {
588 AllowedNumericType::All => val,
589 AllowedNumericType::NonNegative => val.max(0.),
590 AllowedNumericType::AtLeastOne => val.max(1.),
591 AllowedNumericType::ZeroToOne => val.max(0.).min(1.),
592 }
593 }
594 }
595}
596
597/// A property-agnostic representation of a value, used by Typed OM.
598///
599/// `TypedValue` is the internal counterpart of the various `CSSStyleValue`
600/// subclasses defined by the Typed OM specification. It captures values that
601/// can be represented independently of any particular property.
602#[derive(Clone, Debug)]
603#[repr(C)]
604pub enum TypedValue {
605 /// A keyword value (e.g. `"block"`, `"none"`, `"thin"`).
606 ///
607 /// Keywords are stored as a `CssString` so they can be represented and
608 /// transferred independently of any specific property. This corresponds
609 /// to `CSSKeywordValue` in the Typed OM specification.
610 Keyword(CssString),
611}
612
613/// Reifies a value into its Typed OM representation.
614///
615/// This trait is the Typed OM analogue of [`ToCss`]. Instead of serializing
616/// values into CSS syntax, it converts them into [`TypedValue`]s that can be
617/// exposed to the DOM as `CSSStyleValue` subclasses.
618///
619/// This trait is derivable with `#[derive(ToTyped)]`. The derived
620/// implementation behaves as follows:
621///
622/// * For enums whose variants are all unit variants (representing keywords),
623/// it automatically reifies the value as [`TypedValue::Keyword`], using the
624/// same serialization logic as [`ToCss`].
625/// * For all other cases, the derived implementation does not attempt to reify
626/// anything and falls back to the default method (which always returns
627/// `None`).
628///
629/// Over time, the derive may be extended to cover additional common patterns,
630/// similar to how `ToCss` supports multiple attribute annotations.
631pub trait ToTyped {
632 /// Attempt to convert `self` into a [`TypedValue`].
633 ///
634 /// Returns `Some(TypedValue)` if the value can be reified into a
635 /// property-agnostic CSSStyleValue subclass. Returns `None` if the value
636 /// is unrepresentable, in which case reification produces a property-tied
637 /// CSSStyleValue instead.
638 fn to_typed(&self) -> Option<TypedValue> {
639 None
640 }
641}
642
643impl<T> ToTyped for Box<T>
644where
645 T: ?Sized + ToTyped,
646{
647 fn to_typed(&self) -> Option<TypedValue> {
648 (**self).to_typed()
649 }
650}
651
652impl ToTyped for Au {
653 fn to_typed(&self) -> Option<TypedValue> {
654 // XXX Should return TypedValue::Numeric in px units once that variant
655 // is available. Tracked in bug 1990419.
656 None
657 }
658}
659
660macro_rules! impl_to_typed_for_predefined_type {
661 ($name: ty) => {
662 impl<'a> ToTyped for $name {
663 fn to_typed(&self) -> Option<TypedValue> {
664 // XXX Should return TypedValue::Numeric with unit "number"
665 // once that variant is available. Tracked in bug 1990419.
666 None
667 }
668 }
669 };
670}
671
672impl_to_typed_for_predefined_type!(f32);
673impl_to_typed_for_predefined_type!(i8);
674impl_to_typed_for_predefined_type!(i32);