summaryrefslogtreecommitdiffstats
path: root/servo/components/style_traits
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style_traits')
-rw-r--r--servo/components/style_traits/Cargo.toml31
-rw-r--r--servo/components/style_traits/arc_slice.rs160
-rw-r--r--servo/components/style_traits/dom.rs26
-rw-r--r--servo/components/style_traits/lib.rs286
-rw-r--r--servo/components/style_traits/owned_slice.rs198
-rw-r--r--servo/components/style_traits/owned_str.rs81
-rw-r--r--servo/components/style_traits/specified_value_info.rs138
-rw-r--r--servo/components/style_traits/values.rs613
-rw-r--r--servo/components/style_traits/viewport.rs148
9 files changed, 1681 insertions, 0 deletions
diff --git a/servo/components/style_traits/Cargo.toml b/servo/components/style_traits/Cargo.toml
new file mode 100644
index 0000000000..fd50a5a0aa
--- /dev/null
+++ b/servo/components/style_traits/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "style_traits"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+license = "MPL-2.0"
+publish = false
+
+[lib]
+name = "style_traits"
+path = "lib.rs"
+
+[features]
+servo = ["servo_atoms", "cssparser/serde", "webrender_api", "servo_url", "euclid/serde"]
+gecko = []
+
+[dependencies]
+app_units = "0.7"
+bitflags = "2"
+cssparser = "0.31"
+euclid = "0.22"
+lazy_static = "1"
+malloc_size_of = { path = "../malloc_size_of" }
+malloc_size_of_derive = "0.1"
+selectors = { path = "../selectors" }
+serde = "1.0"
+servo_arc = { path = "../servo_arc" }
+servo_atoms = { path = "../atoms", optional = true }
+servo_url = { path = "../url", optional = true }
+to_shmem = { path = "../to_shmem" }
+to_shmem_derive = { path = "../to_shmem_derive" }
+webrender_api = { git = "https://github.com/servo/webrender", optional = true }
diff --git a/servo/components/style_traits/arc_slice.rs b/servo/components/style_traits/arc_slice.rs
new file mode 100644
index 0000000000..43f31cb283
--- /dev/null
+++ b/servo/components/style_traits/arc_slice.rs
@@ -0,0 +1,160 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! A thin atomically-reference-counted slice.
+
+use serde::de::{Deserialize, Deserializer};
+use serde::ser::{Serialize, Serializer};
+use servo_arc::ThinArc;
+use std::ops::Deref;
+use std::ptr::NonNull;
+use std::{iter, mem};
+
+use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalSizeOf};
+
+/// A canary that we stash in ArcSlices.
+///
+/// Given we cannot use a zero-sized-type for the header, since well, C++
+/// doesn't have zsts, and we want to use cbindgen for this type, we may as well
+/// assert some sanity at runtime.
+///
+/// We use an u64, to guarantee that we can use a single singleton for every
+/// empty slice, even if the types they hold are aligned differently.
+const ARC_SLICE_CANARY: u64 = 0xf3f3f3f3f3f3f3f3;
+
+/// A wrapper type for a refcounted slice using ThinArc.
+#[repr(C)]
+#[derive(Debug, Eq, PartialEq, ToShmem)]
+pub struct ArcSlice<T>(#[shmem(field_bound)] ThinArc<u64, T>);
+
+impl<T> Deref for ArcSlice<T> {
+ type Target = [T];
+
+ #[inline]
+ fn deref(&self) -> &Self::Target {
+ debug_assert_eq!(self.0.header, ARC_SLICE_CANARY);
+ self.0.slice()
+ }
+}
+
+impl<T> Clone for ArcSlice<T> {
+ fn clone(&self) -> Self {
+ ArcSlice(self.0.clone())
+ }
+}
+
+lazy_static! {
+ // ThinArc doesn't support alignments greater than align_of::<u64>.
+ static ref EMPTY_ARC_SLICE: ArcSlice<u64> = {
+ ArcSlice::from_iter_leaked(iter::empty())
+ };
+}
+
+impl<T> Default for ArcSlice<T> {
+ #[allow(unsafe_code)]
+ fn default() -> Self {
+ debug_assert!(
+ mem::align_of::<T>() <= mem::align_of::<u64>(),
+ "Need to increase the alignment of EMPTY_ARC_SLICE"
+ );
+ unsafe {
+ let empty: ArcSlice<_> = EMPTY_ARC_SLICE.clone();
+ let empty: Self = mem::transmute(empty);
+ debug_assert_eq!(empty.len(), 0);
+ empty
+ }
+ }
+}
+
+impl<T: Serialize> Serialize for ArcSlice<T> {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ self.deref().serialize(serializer)
+ }
+}
+
+impl<'de, T: Deserialize<'de>> Deserialize<'de> for ArcSlice<T> {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ let r = Vec::deserialize(deserializer)?;
+ Ok(ArcSlice::from_iter(r.into_iter()))
+ }
+}
+
+impl<T> ArcSlice<T> {
+ /// Creates an Arc for a slice using the given iterator to generate the
+ /// slice.
+ #[inline]
+ pub fn from_iter<I>(items: I) -> Self
+ where
+ I: Iterator<Item = T> + ExactSizeIterator,
+ {
+ if items.len() == 0 {
+ return Self::default();
+ }
+ ArcSlice(ThinArc::from_header_and_iter(ARC_SLICE_CANARY, items))
+ }
+
+ /// Creates an Arc for a slice using the given iterator to generate the
+ /// slice, and marks the arc as intentionally leaked from the refcount
+ /// logging point of view.
+ #[inline]
+ pub fn from_iter_leaked<I>(items: I) -> Self
+ where
+ I: Iterator<Item = T> + ExactSizeIterator,
+ {
+ let arc = ThinArc::from_header_and_iter(ARC_SLICE_CANARY, items);
+ arc.mark_as_intentionally_leaked();
+ ArcSlice(arc)
+ }
+
+ /// Creates a value that can be passed via FFI, and forgets this value
+ /// altogether.
+ #[inline]
+ #[allow(unsafe_code)]
+ pub fn forget(self) -> ForgottenArcSlicePtr<T> {
+ let ret = unsafe {
+ ForgottenArcSlicePtr(NonNull::new_unchecked(self.0.raw_ptr() as *const _ as *mut _))
+ };
+ mem::forget(self);
+ ret
+ }
+
+ /// Leaks an empty arc slice pointer, and returns it. Only to be used to
+ /// construct ArcSlices from FFI.
+ #[inline]
+ pub fn leaked_empty_ptr() -> *mut std::os::raw::c_void {
+ let empty: ArcSlice<_> = EMPTY_ARC_SLICE.clone();
+ let ptr = empty.0.raw_ptr();
+ std::mem::forget(empty);
+ ptr as *mut _
+ }
+
+ /// Returns whether there's only one reference to this ArcSlice.
+ pub fn is_unique(&self) -> bool {
+ self.0.is_unique()
+ }
+}
+
+impl<T: MallocSizeOf> MallocUnconditionalSizeOf for ArcSlice<T> {
+ #[allow(unsafe_code)]
+ fn unconditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
+ let mut size = unsafe { ops.malloc_size_of(self.0.heap_ptr()) };
+ for el in self.iter() {
+ size += el.size_of(ops);
+ }
+ size
+ }
+}
+
+/// The inner pointer of an ArcSlice<T>, to be sent via FFI.
+/// The type of the pointer is a bit of a lie, we just want to preserve the type
+/// but these pointers cannot be constructed outside of this crate, so we're
+/// good.
+#[repr(C)]
+pub struct ForgottenArcSlicePtr<T>(NonNull<T>);
diff --git a/servo/components/style_traits/dom.rs b/servo/components/style_traits/dom.rs
new file mode 100644
index 0000000000..03d5264abf
--- /dev/null
+++ b/servo/components/style_traits/dom.rs
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! Types used to access the DOM from style calculation.
+
+/// An opaque handle to a node, which, unlike UnsafeNode, cannot be transformed
+/// back into a non-opaque representation. The only safe operation that can be
+/// performed on this node is to compare it to another opaque handle or to another
+/// OpaqueNode.
+///
+/// Layout and Graphics use this to safely represent nodes for comparison purposes.
+/// Because the script task's GC does not trace layout, node data cannot be safely stored in layout
+/// data structures. Also, layout code tends to be faster when the DOM is not being accessed, for
+/// locality reasons. Using `OpaqueNode` enforces this invariant.
+#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
+#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
+pub struct OpaqueNode(pub usize);
+
+impl OpaqueNode {
+ /// Returns the address of this node, for debugging purposes.
+ #[inline]
+ pub fn id(&self) -> usize {
+ self.0
+ }
+}
diff --git a/servo/components/style_traits/lib.rs b/servo/components/style_traits/lib.rs
new file mode 100644
index 0000000000..054b74b6b8
--- /dev/null
+++ b/servo/components/style_traits/lib.rs
@@ -0,0 +1,286 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! This module contains shared types and messages for use by devtools/script.
+//! The traits are here instead of in script so that the devtools crate can be
+//! modified independently of the rest of Servo.
+
+#![crate_name = "style_traits"]
+#![crate_type = "rlib"]
+#![deny(unsafe_code, missing_docs)]
+
+extern crate app_units;
+#[macro_use]
+extern crate bitflags;
+#[macro_use]
+extern crate cssparser;
+extern crate euclid;
+#[macro_use]
+extern crate lazy_static;
+extern crate malloc_size_of;
+#[macro_use]
+extern crate malloc_size_of_derive;
+extern crate selectors;
+#[macro_use]
+extern crate serde;
+extern crate servo_arc;
+#[cfg(feature = "servo")]
+extern crate servo_atoms;
+#[cfg(feature = "servo")]
+extern crate servo_url;
+extern crate to_shmem;
+#[macro_use]
+extern crate to_shmem_derive;
+#[cfg(feature = "servo")]
+extern crate webrender_api;
+#[cfg(feature = "servo")]
+pub use webrender_api::units::DevicePixel;
+
+use cssparser::{CowRcStr, Token};
+use selectors::parser::SelectorParseErrorKind;
+#[cfg(feature = "servo")]
+use servo_atoms::Atom;
+
+/// One hardware pixel.
+///
+/// This unit corresponds to the smallest addressable element of the display hardware.
+#[cfg(not(feature = "servo"))]
+#[derive(Clone, Copy, Debug)]
+pub enum DevicePixel {}
+
+/// Represents a mobile style pinch zoom factor.
+/// TODO(gw): Once WR supports pinch zoom, use a type directly from webrender_api.
+#[derive(Clone, Copy, Debug, PartialEq)]
+#[cfg_attr(feature = "servo", derive(Deserialize, Serialize, MallocSizeOf))]
+pub struct PinchZoomFactor(f32);
+
+impl PinchZoomFactor {
+ /// Construct a new pinch zoom factor.
+ pub fn new(scale: f32) -> PinchZoomFactor {
+ PinchZoomFactor(scale)
+ }
+
+ /// Get the pinch zoom factor as an untyped float.
+ pub fn get(&self) -> f32 {
+ self.0
+ }
+}
+
+/// One CSS "px" in the coordinate system of the "initial viewport":
+/// <http://www.w3.org/TR/css-device-adapt/#initial-viewport>
+///
+/// `CSSPixel` is equal to `DeviceIndependentPixel` times a "page zoom" factor controlled by the user. This is
+/// the desktop-style "full page" zoom that enlarges content but then reflows the layout viewport
+/// so it still exactly fits the visible area.
+///
+/// At the default zoom level of 100%, one `CSSPixel` is equal to one `DeviceIndependentPixel`. However, if the
+/// document is zoomed in or out then this scale may be larger or smaller.
+#[derive(Clone, Copy, Debug)]
+pub enum CSSPixel {}
+
+// In summary, the hierarchy of pixel units and the factors to convert from one to the next:
+//
+// DevicePixel
+// / hidpi_ratio => DeviceIndependentPixel
+// / desktop_zoom => CSSPixel
+
+pub mod arc_slice;
+pub mod dom;
+pub mod specified_value_info;
+#[macro_use]
+pub mod values;
+#[macro_use]
+pub mod viewport;
+pub mod owned_slice;
+pub mod owned_str;
+
+pub use crate::specified_value_info::{CssType, KeywordsCollectFn, SpecifiedValueInfo};
+pub use crate::values::{
+ Comma, CommaWithSpace, CssWriter, OneOrMoreSeparated, Separator, Space, ToCss,
+};
+
+/// The error type for all CSS parsing routines.
+pub type ParseError<'i> = cssparser::ParseError<'i, StyleParseErrorKind<'i>>;
+
+/// Error in property value parsing
+pub type ValueParseError<'i> = cssparser::ParseError<'i, ValueParseErrorKind<'i>>;
+
+#[derive(Clone, Debug, PartialEq)]
+/// Errors that can be encountered while parsing CSS values.
+pub enum StyleParseErrorKind<'i> {
+ /// A bad URL token in a DVB.
+ BadUrlInDeclarationValueBlock(CowRcStr<'i>),
+ /// A bad string token in a DVB.
+ BadStringInDeclarationValueBlock(CowRcStr<'i>),
+ /// Unexpected closing parenthesis in a DVB.
+ UnbalancedCloseParenthesisInDeclarationValueBlock,
+ /// Unexpected closing bracket in a DVB.
+ UnbalancedCloseSquareBracketInDeclarationValueBlock,
+ /// Unexpected closing curly bracket in a DVB.
+ UnbalancedCloseCurlyBracketInDeclarationValueBlock,
+ /// A property declaration value had input remaining after successfully parsing.
+ PropertyDeclarationValueNotExhausted,
+ /// An unexpected dimension token was encountered.
+ UnexpectedDimension(CowRcStr<'i>),
+ /// Missing or invalid media feature name.
+ MediaQueryExpectedFeatureName(CowRcStr<'i>),
+ /// Missing or invalid media feature value.
+ MediaQueryExpectedFeatureValue,
+ /// A media feature range operator was not expected.
+ MediaQueryUnexpectedOperator,
+ /// min- or max- properties must have a value.
+ RangedExpressionWithNoValue,
+ /// A function was encountered that was not expected.
+ UnexpectedFunction(CowRcStr<'i>),
+ /// Error encountered parsing a @property's `syntax` descriptor
+ PropertySyntaxField(PropertySyntaxParseError),
+ /// @namespace must be before any rule but @charset and @import
+ UnexpectedNamespaceRule,
+ /// @import must be before any rule but @charset
+ UnexpectedImportRule,
+ /// @import rules are disallowed in the parser.
+ DisallowedImportRule,
+ /// Unexpected @charset rule encountered.
+ UnexpectedCharsetRule,
+ /// The @property `<custom-property-name>` must start with `--`
+ UnexpectedIdent(CowRcStr<'i>),
+ /// A placeholder for many sources of errors that require more specific variants.
+ UnspecifiedError,
+ /// An unexpected token was found within a namespace rule.
+ UnexpectedTokenWithinNamespace(Token<'i>),
+ /// An error was encountered while parsing a property value.
+ ValueError(ValueParseErrorKind<'i>),
+ /// An error was encountered while parsing a selector
+ SelectorError(SelectorParseErrorKind<'i>),
+ /// The property declaration was for an unknown property.
+ UnknownProperty(CowRcStr<'i>),
+ /// The property declaration was for a disabled experimental property.
+ ExperimentalProperty,
+ /// The property declaration contained an invalid color value.
+ InvalidColor(CowRcStr<'i>, Token<'i>),
+ /// The property declaration contained an invalid filter value.
+ InvalidFilter(CowRcStr<'i>, Token<'i>),
+ /// The property declaration contained an invalid value.
+ OtherInvalidValue(CowRcStr<'i>),
+ /// The declaration contained an animation property, and we were parsing
+ /// this as a keyframe block (so that property should be ignored).
+ ///
+ /// See: https://drafts.csswg.org/css-animations/#keyframes
+ AnimationPropertyInKeyframeBlock,
+ /// The property is not allowed within a page rule.
+ NotAllowedInPageRule,
+}
+
+impl<'i> From<ValueParseErrorKind<'i>> for StyleParseErrorKind<'i> {
+ fn from(this: ValueParseErrorKind<'i>) -> Self {
+ StyleParseErrorKind::ValueError(this)
+ }
+}
+
+impl<'i> From<SelectorParseErrorKind<'i>> for StyleParseErrorKind<'i> {
+ fn from(this: SelectorParseErrorKind<'i>) -> Self {
+ StyleParseErrorKind::SelectorError(this)
+ }
+}
+
+/// Specific errors that can be encountered while parsing property values.
+#[derive(Clone, Debug, PartialEq)]
+pub enum ValueParseErrorKind<'i> {
+ /// An invalid token was encountered while parsing a color value.
+ InvalidColor(Token<'i>),
+ /// An invalid filter value was encountered.
+ InvalidFilter(Token<'i>),
+}
+
+impl<'i> StyleParseErrorKind<'i> {
+ /// Create an InvalidValue parse error
+ pub fn new_invalid<S>(name: S, value_error: ParseError<'i>) -> ParseError<'i>
+ where
+ S: Into<CowRcStr<'i>>,
+ {
+ let name = name.into();
+ let variant = match value_error.kind {
+ cssparser::ParseErrorKind::Custom(StyleParseErrorKind::ValueError(e)) => match e {
+ ValueParseErrorKind::InvalidColor(token) => {
+ StyleParseErrorKind::InvalidColor(name, token)
+ },
+ ValueParseErrorKind::InvalidFilter(token) => {
+ StyleParseErrorKind::InvalidFilter(name, token)
+ },
+ },
+ _ => StyleParseErrorKind::OtherInvalidValue(name),
+ };
+ cssparser::ParseError {
+ kind: cssparser::ParseErrorKind::Custom(variant),
+ location: value_error.location,
+ }
+ }
+}
+
+/// Errors that can be encountered while parsing the @property rule's syntax descriptor.
+#[derive(Clone, Debug, PartialEq)]
+pub enum PropertySyntaxParseError {
+ /// The string's length was 0.
+ EmptyInput,
+ /// A non-whitespace, non-pipe character was fount after parsing a component.
+ ExpectedPipeBetweenComponents,
+ /// The start of an identifier was expected but not found.
+ ///
+ /// <https://drafts.csswg.org/css-syntax-3/#name-start-code-point>
+ InvalidNameStart,
+ /// The name is not a valid `<ident>`.
+ InvalidName,
+ /// The data type name was not closed.
+ ///
+ /// <https://drafts.css-houdini.org/css-properties-values-api-1/#consume-data-type-name>
+ UnclosedDataTypeName,
+ /// The next byte was expected while parsing, but EOF was found instead.
+ UnexpectedEOF,
+ /// The data type is not a supported syntax component name.
+ ///
+ /// <https://drafts.css-houdini.org/css-properties-values-api-1/#supported-names>
+ UnknownDataTypeName,
+}
+
+bitflags! {
+ /// The mode to use when parsing values.
+ #[derive(Clone, Copy, Eq, PartialEq)]
+ pub struct ParsingMode: u8 {
+ /// In CSS; lengths must have units, except for zero values, where the unit can be omitted.
+ /// <https://www.w3.org/TR/css3-values/#lengths>
+ const DEFAULT = 0x00;
+ /// In SVG; a coordinate or length value without a unit identifier (e.g., "25") is assumed
+ /// to be in user units (px).
+ /// <https://www.w3.org/TR/SVG/coords.html#Units>
+ const ALLOW_UNITLESS_LENGTH = 0x01;
+ /// In SVG; out-of-range values are not treated as an error in parsing.
+ /// <https://www.w3.org/TR/SVG/implnote.html#RangeClamping>
+ const ALLOW_ALL_NUMERIC_VALUES = 0x02;
+ }
+}
+
+impl ParsingMode {
+ /// Whether the parsing mode allows unitless lengths for non-zero values to be intpreted as px.
+ #[inline]
+ pub fn allows_unitless_lengths(&self) -> bool {
+ self.intersects(ParsingMode::ALLOW_UNITLESS_LENGTH)
+ }
+
+ /// Whether the parsing mode allows all numeric values.
+ #[inline]
+ pub fn allows_all_numeric_values(&self) -> bool {
+ self.intersects(ParsingMode::ALLOW_ALL_NUMERIC_VALUES)
+ }
+}
+
+#[cfg(feature = "servo")]
+/// Speculatively execute paint code in the worklet thread pool.
+pub trait SpeculativePainter: Send + Sync {
+ /// <https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image>
+ fn speculatively_draw_a_paint_image(
+ &self,
+ properties: Vec<(Atom, String)>,
+ arguments: Vec<String>,
+ );
+}
diff --git a/servo/components/style_traits/owned_slice.rs b/servo/components/style_traits/owned_slice.rs
new file mode 100644
index 0000000000..36ba3162e5
--- /dev/null
+++ b/servo/components/style_traits/owned_slice.rs
@@ -0,0 +1,198 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#![allow(unsafe_code)]
+
+//! A replacement for `Box<[T]>` that cbindgen can understand.
+
+use malloc_size_of::{MallocShallowSizeOf, MallocSizeOf, MallocSizeOfOps};
+use serde::de::{Deserialize, Deserializer};
+use serde::ser::{Serialize, Serializer};
+use std::marker::PhantomData;
+use std::ops::{Deref, DerefMut};
+use std::ptr::NonNull;
+use std::{fmt, iter, mem, slice};
+use to_shmem::{self, SharedMemoryBuilder, ToShmem};
+
+/// A struct that basically replaces a `Box<[T]>`, but which cbindgen can
+/// understand.
+///
+/// We could rely on the struct layout of `Box<[T]>` per:
+///
+/// https://github.com/rust-lang/unsafe-code-guidelines/blob/master/reference/src/layout/pointers.md
+///
+/// But handling fat pointers with cbindgen both in structs and argument
+/// positions more generally is a bit tricky.
+///
+/// cbindgen:derive-eq=false
+/// cbindgen:derive-neq=false
+#[repr(C)]
+pub struct OwnedSlice<T: Sized> {
+ ptr: NonNull<T>,
+ len: usize,
+ _phantom: PhantomData<T>,
+}
+
+impl<T: Sized> Default for OwnedSlice<T> {
+ #[inline]
+ fn default() -> Self {
+ Self {
+ len: 0,
+ ptr: NonNull::dangling(),
+ _phantom: PhantomData,
+ }
+ }
+}
+
+impl<T: Sized> Drop for OwnedSlice<T> {
+ #[inline]
+ fn drop(&mut self) {
+ if self.len != 0 {
+ let _ = mem::replace(self, Self::default()).into_vec();
+ }
+ }
+}
+
+unsafe impl<T: Sized + Send> Send for OwnedSlice<T> {}
+unsafe impl<T: Sized + Sync> Sync for OwnedSlice<T> {}
+
+impl<T: Clone> Clone for OwnedSlice<T> {
+ #[inline]
+ fn clone(&self) -> Self {
+ Self::from_slice(&**self)
+ }
+}
+
+impl<T: fmt::Debug> fmt::Debug for OwnedSlice<T> {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ self.deref().fmt(formatter)
+ }
+}
+
+impl<T: PartialEq> PartialEq for OwnedSlice<T> {
+ fn eq(&self, other: &Self) -> bool {
+ self.deref().eq(other.deref())
+ }
+}
+
+impl<T: Eq> Eq for OwnedSlice<T> {}
+
+impl<T: Sized> OwnedSlice<T> {
+ /// Convert the OwnedSlice into a boxed slice.
+ #[inline]
+ pub fn into_box(self) -> Box<[T]> {
+ self.into_vec().into_boxed_slice()
+ }
+
+ /// Convert the OwnedSlice into a Vec.
+ #[inline]
+ pub fn into_vec(self) -> Vec<T> {
+ let ret = unsafe { Vec::from_raw_parts(self.ptr.as_ptr(), self.len, self.len) };
+ mem::forget(self);
+ ret
+ }
+
+ /// Convert the regular slice into an owned slice.
+ #[inline]
+ pub fn from_slice(s: &[T]) -> Self
+ where
+ T: Clone,
+ {
+ Self::from(s.to_vec())
+ }
+}
+
+impl<T> IntoIterator for OwnedSlice<T> {
+ type Item = T;
+ type IntoIter = <Vec<T> as IntoIterator>::IntoIter;
+
+ #[inline]
+ fn into_iter(self) -> Self::IntoIter {
+ self.into_vec().into_iter()
+ }
+}
+
+impl<T> Deref for OwnedSlice<T> {
+ type Target = [T];
+
+ #[inline(always)]
+ fn deref(&self) -> &Self::Target {
+ unsafe { slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
+ }
+}
+
+impl<T> DerefMut for OwnedSlice<T> {
+ #[inline(always)]
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ unsafe { slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
+ }
+}
+
+impl<T> From<Box<[T]>> for OwnedSlice<T> {
+ #[inline]
+ fn from(mut b: Box<[T]>) -> Self {
+ let len = b.len();
+ let ptr = unsafe { NonNull::new_unchecked(b.as_mut_ptr()) };
+ mem::forget(b);
+ Self {
+ len,
+ ptr,
+ _phantom: PhantomData,
+ }
+ }
+}
+
+impl<T> From<Vec<T>> for OwnedSlice<T> {
+ #[inline]
+ fn from(b: Vec<T>) -> Self {
+ Self::from(b.into_boxed_slice())
+ }
+}
+
+impl<T: Sized> MallocShallowSizeOf for OwnedSlice<T> {
+ fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
+ unsafe { ops.malloc_size_of(self.ptr.as_ptr()) }
+ }
+}
+
+impl<T: MallocSizeOf + Sized> MallocSizeOf for OwnedSlice<T> {
+ fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
+ self.shallow_size_of(ops) + (**self).size_of(ops)
+ }
+}
+
+impl<T: ToShmem + Sized> ToShmem for OwnedSlice<T> {
+ fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
+ unsafe {
+ let dest = to_shmem::to_shmem_slice(self.iter(), builder)?;
+ Ok(mem::ManuallyDrop::new(Self::from(Box::from_raw(dest))))
+ }
+ }
+}
+
+impl<T> iter::FromIterator<T> for OwnedSlice<T> {
+ #[inline]
+ fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
+ Vec::from_iter(iter).into()
+ }
+}
+
+impl<T: Serialize> Serialize for OwnedSlice<T> {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ self.deref().serialize(serializer)
+ }
+}
+
+impl<'de, T: Deserialize<'de>> Deserialize<'de> for OwnedSlice<T> {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ let r = Box::<[T]>::deserialize(deserializer)?;
+ Ok(r.into())
+ }
+}
diff --git a/servo/components/style_traits/owned_str.rs b/servo/components/style_traits/owned_str.rs
new file mode 100644
index 0000000000..ebfdcd5e06
--- /dev/null
+++ b/servo/components/style_traits/owned_str.rs
@@ -0,0 +1,81 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#![allow(unsafe_code)]
+
+//! A replacement for `Box<str>` that has a defined layout for FFI.
+
+use crate::owned_slice::OwnedSlice;
+use std::fmt;
+use std::ops::{Deref, DerefMut};
+
+/// A struct that basically replaces a Box<str>, but with a defined layout,
+/// suitable for FFI.
+#[repr(C)]
+#[derive(Clone, Default, Eq, MallocSizeOf, PartialEq, ToShmem)]
+pub struct OwnedStr(OwnedSlice<u8>);
+
+impl fmt::Debug for OwnedStr {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ self.deref().fmt(formatter)
+ }
+}
+
+impl Deref for OwnedStr {
+ type Target = str;
+
+ #[inline(always)]
+ fn deref(&self) -> &Self::Target {
+ unsafe { std::str::from_utf8_unchecked(&*self.0) }
+ }
+}
+
+impl DerefMut for OwnedStr {
+ #[inline(always)]
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ unsafe { std::str::from_utf8_unchecked_mut(&mut *self.0) }
+ }
+}
+
+impl OwnedStr {
+ /// Convert the OwnedStr into a boxed str.
+ #[inline]
+ pub fn into_box(self) -> Box<str> {
+ self.into_string().into_boxed_str()
+ }
+
+ /// Convert the OwnedStr into a `String`.
+ #[inline]
+ pub fn into_string(self) -> String {
+ unsafe { String::from_utf8_unchecked(self.0.into_vec()) }
+ }
+}
+
+impl From<OwnedStr> for String {
+ #[inline]
+ fn from(b: OwnedStr) -> Self {
+ b.into_string()
+ }
+}
+
+impl From<OwnedStr> for Box<str> {
+ #[inline]
+ fn from(b: OwnedStr) -> Self {
+ b.into_box()
+ }
+}
+
+impl From<Box<str>> for OwnedStr {
+ #[inline]
+ fn from(b: Box<str>) -> Self {
+ Self::from(b.into_string())
+ }
+}
+
+impl From<String> for OwnedStr {
+ #[inline]
+ fn from(s: String) -> Self {
+ OwnedStr(s.into_bytes().into())
+ }
+}
diff --git a/servo/components/style_traits/specified_value_info.rs b/servo/components/style_traits/specified_value_info.rs
new file mode 100644
index 0000000000..1dd368d36e
--- /dev/null
+++ b/servo/components/style_traits/specified_value_info.rs
@@ -0,0 +1,138 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! Value information for devtools.
+
+use crate::arc_slice::ArcSlice;
+use crate::owned_slice::OwnedSlice;
+use servo_arc::Arc;
+use std::ops::Range;
+use std::sync::Arc as StdArc;
+
+/// Type of value that a property supports. This is used by Gecko's
+/// devtools to make sense about value it parses, and types listed
+/// here should match InspectorPropertyType in InspectorUtils.webidl.
+///
+/// XXX This should really be a bitflags rather than a namespace mod,
+/// but currently we cannot use bitflags in const.
+#[allow(non_snake_case)]
+pub mod CssType {
+ /// <color>
+ pub const COLOR: u8 = 1 << 0;
+ /// <gradient>
+ pub const GRADIENT: u8 = 1 << 1;
+ /// <timing-function>
+ pub const TIMING_FUNCTION: u8 = 1 << 2;
+}
+
+/// See SpecifiedValueInfo::collect_completion_keywords.
+pub type KeywordsCollectFn<'a> = &'a mut dyn FnMut(&[&'static str]);
+
+/// Information of values of a given specified value type.
+///
+/// This trait is derivable with `#[derive(SpecifiedValueInfo)]`.
+///
+/// The algorithm traverses the type definition. For `SUPPORTED_TYPES`,
+/// it puts an or'ed value of `SUPPORTED_TYPES` of all types it finds.
+/// For `collect_completion_keywords`, it recursively invokes this
+/// method on types found, and lists all keyword values and function
+/// names following the same rule as `ToCss` in that method.
+///
+/// Some attributes of `ToCss` can affect the behavior, specifically:
+/// * If `#[css(function)]` is found, the content inside the annotated
+/// variant (or the whole type) isn't traversed, only the function
+/// name is listed in `collect_completion_keywords`.
+/// * If `#[css(skip)]` is found, the content inside the variant or
+/// field is ignored.
+/// * Values listed in `#[css(if_empty)]`, `#[parse(aliases)]`, and
+/// `#[css(keyword)]` are added into `collect_completion_keywords`.
+///
+/// In addition to `css` attributes, it also has `value_info` helper
+/// attributes, including:
+/// * `#[value_info(ty = "TYPE")]` can be used to specify a constant
+/// from `CssType` to `SUPPORTED_TYPES`.
+/// * `#[value_info(other_values = "value1,value2")]` can be used to
+/// add other values related to a field, variant, or the type itself
+/// into `collect_completion_keywords`.
+/// * `#[value_info(starts_with_keyword)]` can be used on variants to
+/// add the name of a non-unit variant (serialized like `ToCss`) into
+/// `collect_completion_keywords`.
+pub trait SpecifiedValueInfo {
+ /// Supported CssTypes by the given value type.
+ ///
+ /// XXX This should be typed CssType when that becomes a bitflags.
+ /// Currently we cannot do so since bitflags cannot be used in constant.
+ const SUPPORTED_TYPES: u8 = 0;
+
+ /// Collect value starting words for the given specified value type.
+ /// This includes keyword and function names which can appear at the
+ /// beginning of a value of this type.
+ ///
+ /// Caller should pass in a callback function to accept the list of
+ /// values. The callback function can be called multiple times, and
+ /// some values passed to the callback may be duplicate.
+ fn collect_completion_keywords(_f: KeywordsCollectFn) {}
+}
+
+impl SpecifiedValueInfo for bool {}
+impl SpecifiedValueInfo for f32 {}
+impl SpecifiedValueInfo for i8 {}
+impl SpecifiedValueInfo for i32 {}
+impl SpecifiedValueInfo for u8 {}
+impl SpecifiedValueInfo for u16 {}
+impl SpecifiedValueInfo for u32 {}
+impl SpecifiedValueInfo for usize {}
+impl SpecifiedValueInfo for str {}
+impl SpecifiedValueInfo for String {}
+impl SpecifiedValueInfo for crate::owned_str::OwnedStr {}
+
+#[cfg(feature = "servo")]
+impl SpecifiedValueInfo for ::servo_atoms::Atom {}
+#[cfg(feature = "servo")]
+impl SpecifiedValueInfo for ::servo_url::ServoUrl {}
+
+impl<T: SpecifiedValueInfo + ?Sized> SpecifiedValueInfo for Box<T> {
+ const SUPPORTED_TYPES: u8 = T::SUPPORTED_TYPES;
+ fn collect_completion_keywords(f: KeywordsCollectFn) {
+ T::collect_completion_keywords(f);
+ }
+}
+
+impl<T: SpecifiedValueInfo> SpecifiedValueInfo for [T] {
+ const SUPPORTED_TYPES: u8 = T::SUPPORTED_TYPES;
+ fn collect_completion_keywords(f: KeywordsCollectFn) {
+ T::collect_completion_keywords(f);
+ }
+}
+
+macro_rules! impl_generic_specified_value_info {
+ ($ty:ident<$param:ident>) => {
+ impl<$param: SpecifiedValueInfo> SpecifiedValueInfo for $ty<$param> {
+ const SUPPORTED_TYPES: u8 = $param::SUPPORTED_TYPES;
+ fn collect_completion_keywords(f: KeywordsCollectFn) {
+ $param::collect_completion_keywords(f);
+ }
+ }
+ };
+}
+impl_generic_specified_value_info!(Option<T>);
+impl_generic_specified_value_info!(OwnedSlice<T>);
+impl_generic_specified_value_info!(Vec<T>);
+impl_generic_specified_value_info!(Arc<T>);
+impl_generic_specified_value_info!(StdArc<T>);
+impl_generic_specified_value_info!(ArcSlice<T>);
+impl_generic_specified_value_info!(Range<Idx>);
+
+impl<T1, T2> SpecifiedValueInfo for (T1, T2)
+where
+ T1: SpecifiedValueInfo,
+ T2: SpecifiedValueInfo,
+{
+ const SUPPORTED_TYPES: u8 = T1::SUPPORTED_TYPES | T2::SUPPORTED_TYPES;
+
+ fn collect_completion_keywords(f: KeywordsCollectFn) {
+ T1::collect_completion_keywords(f);
+ T2::collect_completion_keywords(f);
+ }
+}
diff --git a/servo/components/style_traits/values.rs b/servo/components/style_traits/values.rs
new file mode 100644
index 0000000000..b6d92fe869
--- /dev/null
+++ b/servo/components/style_traits/values.rs
@@ -0,0 +1,613 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! Helper types and traits for the handling of CSS values.
+
+use app_units::Au;
+use cssparser::ToCss as CssparserToCss;
+use cssparser::{serialize_string, ParseError, Parser, Token, UnicodeRange};
+use servo_arc::Arc;
+use std::fmt::{self, Write};
+
+/// Serialises a value according to its CSS representation.
+///
+/// This trait is implemented for `str` and its friends, serialising the string
+/// contents as a CSS quoted string.
+///
+/// This trait is derivable with `#[derive(ToCss)]`, with the following behaviour:
+/// * unit variants get serialised as the `snake-case` representation
+/// of their name;
+/// * unit variants whose name starts with "Moz" or "Webkit" are prepended
+/// with a "-";
+/// * if `#[css(comma)]` is found on a variant, its fields are separated by
+/// commas, otherwise, by spaces;
+/// * if `#[css(function)]` is found on a variant, the variant name gets
+/// serialised like unit variants and its fields are surrounded by parentheses;
+/// * if `#[css(iterable)]` is found on a function variant, that variant needs
+/// to have a single member, and that member needs to be iterable. The
+/// iterable will be serialized as the arguments for the function;
+/// * an iterable field can also be annotated with `#[css(if_empty = "foo")]`
+/// to print `"foo"` if the iterator is empty;
+/// * if `#[css(dimension)]` is found on a variant, that variant needs
+/// to have a single member. The variant would be serialized as a CSS
+/// dimension token, like: <member><identifier>;
+/// * if `#[css(skip)]` is found on a field, the `ToCss` call for that field
+/// is skipped;
+/// * if `#[css(skip_if = "function")]` is found on a field, the `ToCss` call
+/// for that field is skipped if `function` returns true. This function is
+/// provided the field as an argument;
+/// * if `#[css(contextual_skip_if = "function")]` is found on a field, the
+/// `ToCss` call for that field is skipped if `function` returns true. This
+/// function is given all the fields in the current struct or variant as an
+/// argument;
+/// * `#[css(represents_keyword)]` can be used on bool fields in order to
+/// serialize the field name if the field is true, or nothing otherwise. It
+/// also collects those keywords for `SpecifiedValueInfo`.
+/// * `#[css(bitflags(single="", mixed="", validate="", overlapping_bits)]` can
+/// be used to derive parse / serialize / etc on bitflags. The rules for parsing
+/// bitflags are the following:
+///
+/// * `single` flags can only appear on their own. It's common that bitflags
+/// properties at least have one such value like `none` or `auto`.
+/// * `mixed` properties can appear mixed together, but not along any other
+/// flag that shares a bit with itself. For example, if you have three
+/// bitflags like:
+///
+/// FOO = 1 << 0;
+/// BAR = 1 << 1;
+/// BAZ = 1 << 2;
+/// BAZZ = BAR | BAZ;
+///
+/// Then the following combinations won't be valid:
+///
+/// * foo foo: (every flag shares a bit with itself)
+/// * bar bazz: (bazz shares a bit with bar)
+///
+/// But `bar baz` will be valid, as they don't share bits, and so would
+/// `foo` with any other flag, or `bazz` on its own.
+/// * `overlapping_bits` enables some tracking during serialization of mixed
+/// flags to avoid serializing variants that can subsume other variants.
+/// In the example above, you could do:
+/// mixed="foo,bazz,bar,baz", overlapping_bits
+/// to ensure that if bazz is serialized, bar and baz aren't, even though
+/// their bits are set. Note that the serialization order is canonical,
+/// and thus depends on the order you specify the flags in.
+///
+/// * finally, one can put `#[css(derive_debug)]` on the whole type, to
+/// implement `Debug` by a single call to `ToCss::to_css`.
+pub trait ToCss {
+ /// Serialize `self` in CSS syntax, writing to `dest`.
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write;
+
+ /// Serialize `self` in CSS syntax and return a string.
+ ///
+ /// (This is a convenience wrapper for `to_css` and probably should not be overridden.)
+ #[inline]
+ fn to_css_string(&self) -> String {
+ let mut s = String::new();
+ self.to_css(&mut CssWriter::new(&mut s)).unwrap();
+ s
+ }
+}
+
+impl<'a, T> ToCss for &'a T
+where
+ T: ToCss + ?Sized,
+{
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ (*self).to_css(dest)
+ }
+}
+
+impl ToCss for crate::owned_str::OwnedStr {
+ #[inline]
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ serialize_string(self, dest)
+ }
+}
+
+impl ToCss for str {
+ #[inline]
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ serialize_string(self, dest)
+ }
+}
+
+impl ToCss for String {
+ #[inline]
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ serialize_string(self, dest)
+ }
+}
+
+impl<T> ToCss for Option<T>
+where
+ T: ToCss,
+{
+ #[inline]
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ self.as_ref().map_or(Ok(()), |value| value.to_css(dest))
+ }
+}
+
+impl ToCss for () {
+ #[inline]
+ fn to_css<W>(&self, _: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ Ok(())
+ }
+}
+
+/// A writer tailored for serialising CSS.
+///
+/// Coupled with SequenceWriter, this allows callers to transparently handle
+/// things like comma-separated values etc.
+pub struct CssWriter<'w, W: 'w> {
+ inner: &'w mut W,
+ prefix: Option<&'static str>,
+}
+
+impl<'w, W> CssWriter<'w, W>
+where
+ W: Write,
+{
+ /// Creates a new `CssWriter`.
+ #[inline]
+ pub fn new(inner: &'w mut W) -> Self {
+ Self {
+ inner,
+ prefix: Some(""),
+ }
+ }
+}
+
+impl<'w, W> Write for CssWriter<'w, W>
+where
+ W: Write,
+{
+ #[inline]
+ fn write_str(&mut self, s: &str) -> fmt::Result {
+ if s.is_empty() {
+ return Ok(());
+ }
+ if let Some(prefix) = self.prefix.take() {
+ // We are going to write things, but first we need to write
+ // the prefix that was set by `SequenceWriter::item`.
+ if !prefix.is_empty() {
+ self.inner.write_str(prefix)?;
+ }
+ }
+ self.inner.write_str(s)
+ }
+
+ #[inline]
+ fn write_char(&mut self, c: char) -> fmt::Result {
+ if let Some(prefix) = self.prefix.take() {
+ // See comment in `write_str`.
+ if !prefix.is_empty() {
+ self.inner.write_str(prefix)?;
+ }
+ }
+ self.inner.write_char(c)
+ }
+}
+
+/// Convenience wrapper to serialise CSS values separated by a given string.
+pub struct SequenceWriter<'a, 'b: 'a, W: 'b> {
+ inner: &'a mut CssWriter<'b, W>,
+ separator: &'static str,
+}
+
+impl<'a, 'b, W> SequenceWriter<'a, 'b, W>
+where
+ W: Write + 'b,
+{
+ /// Create a new sequence writer.
+ #[inline]
+ pub fn new(inner: &'a mut CssWriter<'b, W>, separator: &'static str) -> Self {
+ if inner.prefix.is_none() {
+ // See comment in `item`.
+ inner.prefix = Some("");
+ }
+ Self { inner, separator }
+ }
+
+ #[inline]
+ fn write_item<F>(&mut self, f: F) -> fmt::Result
+ where
+ F: FnOnce(&mut CssWriter<'b, W>) -> fmt::Result,
+ {
+ // Separate non-generic functions so that this code is not repeated
+ // in every monomorphization with a different type `F` or `W`.
+ // https://github.com/servo/servo/issues/26713
+ fn before(
+ prefix: &mut Option<&'static str>,
+ separator: &'static str,
+ ) -> Option<&'static str> {
+ let old_prefix = *prefix;
+ if old_prefix.is_none() {
+ // If there is no prefix in the inner writer, a previous
+ // call to this method produced output, which means we need
+ // to write the separator next time we produce output again.
+ *prefix = Some(separator);
+ }
+ old_prefix
+ }
+ fn after(
+ old_prefix: Option<&'static str>,
+ prefix: &mut Option<&'static str>,
+ separator: &'static str,
+ ) {
+ match (old_prefix, *prefix) {
+ (_, None) => {
+ // This call produced output and cleaned up after itself.
+ },
+ (None, Some(p)) => {
+ // Some previous call to `item` produced output,
+ // but this one did not, prefix should be the same as
+ // the one we set.
+ debug_assert_eq!(separator, p);
+ // We clean up here even though it's not necessary just
+ // to be able to do all these assertion checks.
+ *prefix = None;
+ },
+ (Some(old), Some(new)) => {
+ // No previous call to `item` produced output, and this one
+ // either.
+ debug_assert_eq!(old, new);
+ },
+ }
+ }
+
+ let old_prefix = before(&mut self.inner.prefix, self.separator);
+ f(self.inner)?;
+ after(old_prefix, &mut self.inner.prefix, self.separator);
+ Ok(())
+ }
+
+ /// Serialises a CSS value, writing any separator as necessary.
+ ///
+ /// The separator is never written before any `item` produces any output,
+ /// and is written in subsequent calls only if the `item` produces some
+ /// output on its own again. This lets us handle `Option<T>` fields by
+ /// just not printing anything on `None`.
+ #[inline]
+ pub fn item<T>(&mut self, item: &T) -> fmt::Result
+ where
+ T: ToCss,
+ {
+ self.write_item(|inner| item.to_css(inner))
+ }
+
+ /// Writes a string as-is (i.e. not escaped or wrapped in quotes)
+ /// with any separator as necessary.
+ ///
+ /// See SequenceWriter::item.
+ #[inline]
+ pub fn raw_item(&mut self, item: &str) -> fmt::Result {
+ self.write_item(|inner| inner.write_str(item))
+ }
+}
+
+/// Type used as the associated type in the `OneOrMoreSeparated` trait on a
+/// type to indicate that a serialized list of elements of this type is
+/// separated by commas.
+pub struct Comma;
+
+/// Type used as the associated type in the `OneOrMoreSeparated` trait on a
+/// type to indicate that a serialized list of elements of this type is
+/// separated by spaces.
+pub struct Space;
+
+/// Type used as the associated type in the `OneOrMoreSeparated` trait on a
+/// type to indicate that a serialized list of elements of this type is
+/// separated by commas, but spaces without commas are also allowed when
+/// parsing.
+pub struct CommaWithSpace;
+
+/// A trait satisfied by the types corresponding to separators.
+pub trait Separator {
+ /// The separator string that the satisfying separator type corresponds to.
+ fn separator() -> &'static str;
+
+ /// Parses a sequence of values separated by this separator.
+ ///
+ /// The given closure is called repeatedly for each item in the sequence.
+ ///
+ /// Successful results are accumulated in a vector.
+ ///
+ /// This method returns `Err(_)` the first time a closure does or if
+ /// the separators aren't correct.
+ fn parse<'i, 't, F, T, E>(
+ parser: &mut Parser<'i, 't>,
+ parse_one: F,
+ ) -> Result<Vec<T>, ParseError<'i, E>>
+ where
+ F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>;
+}
+
+impl Separator for Comma {
+ fn separator() -> &'static str {
+ ", "
+ }
+
+ fn parse<'i, 't, F, T, E>(
+ input: &mut Parser<'i, 't>,
+ parse_one: F,
+ ) -> Result<Vec<T>, ParseError<'i, E>>
+ where
+ F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>,
+ {
+ input.parse_comma_separated(parse_one)
+ }
+}
+
+impl Separator for Space {
+ fn separator() -> &'static str {
+ " "
+ }
+
+ fn parse<'i, 't, F, T, E>(
+ input: &mut Parser<'i, 't>,
+ mut parse_one: F,
+ ) -> Result<Vec<T>, ParseError<'i, E>>
+ where
+ F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>,
+ {
+ input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less.
+ let mut results = vec![parse_one(input)?];
+ loop {
+ input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less.
+ if let Ok(item) = input.try(&mut parse_one) {
+ results.push(item);
+ } else {
+ return Ok(results);
+ }
+ }
+ }
+}
+
+impl Separator for CommaWithSpace {
+ fn separator() -> &'static str {
+ ", "
+ }
+
+ fn parse<'i, 't, F, T, E>(
+ input: &mut Parser<'i, 't>,
+ mut parse_one: F,
+ ) -> Result<Vec<T>, ParseError<'i, E>>
+ where
+ F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>,
+ {
+ input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less.
+ let mut results = vec![parse_one(input)?];
+ loop {
+ input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less.
+ let comma_location = input.current_source_location();
+ let comma = input.try(|i| i.expect_comma()).is_ok();
+ input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less.
+ if let Ok(item) = input.try(&mut parse_one) {
+ results.push(item);
+ } else if comma {
+ return Err(comma_location.new_unexpected_token_error(Token::Comma));
+ } else {
+ break;
+ }
+ }
+ Ok(results)
+ }
+}
+
+/// Marker trait on T to automatically implement ToCss for Vec<T> when T's are
+/// separated by some delimiter `delim`.
+pub trait OneOrMoreSeparated {
+ /// Associated type indicating which separator is used.
+ type S: Separator;
+}
+
+impl OneOrMoreSeparated for UnicodeRange {
+ type S = Comma;
+}
+
+impl<T> ToCss for Vec<T>
+where
+ T: ToCss + OneOrMoreSeparated,
+{
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ let mut iter = self.iter();
+ iter.next().unwrap().to_css(dest)?;
+ for item in iter {
+ dest.write_str(<T as OneOrMoreSeparated>::S::separator())?;
+ item.to_css(dest)?;
+ }
+ Ok(())
+ }
+}
+
+impl<T> ToCss for Box<T>
+where
+ T: ?Sized + ToCss,
+{
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ (**self).to_css(dest)
+ }
+}
+
+impl<T> ToCss for Arc<T>
+where
+ T: ?Sized + ToCss,
+{
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ (**self).to_css(dest)
+ }
+}
+
+impl ToCss for Au {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ self.to_f64_px().to_css(dest)?;
+ dest.write_str("px")
+ }
+}
+
+macro_rules! impl_to_css_for_predefined_type {
+ ($name: ty) => {
+ impl<'a> ToCss for $name {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ ::cssparser::ToCss::to_css(self, dest)
+ }
+ }
+ };
+}
+
+impl_to_css_for_predefined_type!(f32);
+impl_to_css_for_predefined_type!(i8);
+impl_to_css_for_predefined_type!(i32);
+impl_to_css_for_predefined_type!(u16);
+impl_to_css_for_predefined_type!(u32);
+impl_to_css_for_predefined_type!(::cssparser::Token<'a>);
+impl_to_css_for_predefined_type!(::cssparser::UnicodeRange);
+
+/// Define an enum type with unit variants that each correspond to a CSS keyword.
+macro_rules! define_css_keyword_enum {
+ (pub enum $name:ident { $($variant:ident = $css:expr,)+ }) => {
+ #[allow(missing_docs)]
+ #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
+ #[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)]
+ pub enum $name {
+ $($variant),+
+ }
+
+ impl $name {
+ /// Parse this property from a CSS input stream.
+ pub fn parse<'i, 't>(input: &mut ::cssparser::Parser<'i, 't>)
+ -> Result<$name, $crate::ParseError<'i>> {
+ use cssparser::Token;
+ let location = input.current_source_location();
+ match *input.next()? {
+ Token::Ident(ref ident) => {
+ Self::from_ident(ident).map_err(|()| {
+ location.new_unexpected_token_error(
+ Token::Ident(ident.clone()),
+ )
+ })
+ }
+ ref token => {
+ Err(location.new_unexpected_token_error(token.clone()))
+ }
+ }
+ }
+
+ /// Parse this property from an already-tokenized identifier.
+ pub fn from_ident(ident: &str) -> Result<$name, ()> {
+ match_ignore_ascii_case! { ident,
+ $($css => Ok($name::$variant),)+
+ _ => Err(())
+ }
+ }
+ }
+
+ impl $crate::ToCss for $name {
+ fn to_css<W>(
+ &self,
+ dest: &mut $crate::CssWriter<W>,
+ ) -> ::std::fmt::Result
+ where
+ W: ::std::fmt::Write,
+ {
+ match *self {
+ $( $name::$variant => ::std::fmt::Write::write_str(dest, $css) ),+
+ }
+ }
+ }
+ };
+}
+
+/// Helper types for the handling of specified values.
+pub mod specified {
+ use crate::ParsingMode;
+
+ /// Whether to allow negative lengths or not.
+ #[repr(u8)]
+ #[derive(
+ Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, PartialOrd, Serialize, ToShmem,
+ )]
+ pub enum AllowedNumericType {
+ /// Allow all kind of numeric values.
+ All,
+ /// Allow only non-negative numeric values.
+ NonNegative,
+ /// Allow only numeric values greater or equal to 1.0.
+ AtLeastOne,
+ /// Allow only numeric values from 0 to 1.0.
+ ZeroToOne,
+ }
+
+ impl Default for AllowedNumericType {
+ #[inline]
+ fn default() -> Self {
+ AllowedNumericType::All
+ }
+ }
+
+ impl AllowedNumericType {
+ /// Whether the value fits the rules of this numeric type.
+ #[inline]
+ pub fn is_ok(&self, parsing_mode: ParsingMode, val: f32) -> bool {
+ if parsing_mode.allows_all_numeric_values() {
+ return true;
+ }
+ match *self {
+ AllowedNumericType::All => true,
+ AllowedNumericType::NonNegative => val >= 0.0,
+ AllowedNumericType::AtLeastOne => val >= 1.0,
+ AllowedNumericType::ZeroToOne => val >= 0.0 && val <= 1.0,
+ }
+ }
+
+ /// Clamp the value following the rules of this numeric type.
+ #[inline]
+ pub fn clamp(&self, val: f32) -> f32 {
+ match *self {
+ AllowedNumericType::All => val,
+ AllowedNumericType::NonNegative => val.max(0.),
+ AllowedNumericType::AtLeastOne => val.max(1.),
+ AllowedNumericType::ZeroToOne => val.max(0.).min(1.),
+ }
+ }
+ }
+}
diff --git a/servo/components/style_traits/viewport.rs b/servo/components/style_traits/viewport.rs
new file mode 100644
index 0000000000..eff1dfca7a
--- /dev/null
+++ b/servo/components/style_traits/viewport.rs
@@ -0,0 +1,148 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! Helper types for the `@viewport` rule.
+
+use crate::{CSSPixel, CssWriter, ParseError, PinchZoomFactor, ToCss};
+use cssparser::Parser;
+use euclid::Size2D;
+use std::fmt::{self, Write};
+
+define_css_keyword_enum! {
+ pub enum UserZoom {
+ Zoom = "zoom",
+ Fixed = "fixed",
+ }
+}
+
+define_css_keyword_enum! {
+ pub enum Orientation {
+ Auto = "auto",
+ Portrait = "portrait",
+ Landscape = "landscape",
+ }
+}
+
+/// A set of viewport descriptors:
+///
+/// <https://drafts.csswg.org/css-device-adapt/#viewport-desc>
+#[derive(Clone, Debug, PartialEq)]
+#[cfg_attr(feature = "servo", derive(Deserialize, Serialize, MallocSizeOf))]
+pub struct ViewportConstraints {
+ /// Width and height:
+ /// * https://drafts.csswg.org/css-device-adapt/#width-desc
+ /// * https://drafts.csswg.org/css-device-adapt/#height-desc
+ pub size: Size2D<f32, CSSPixel>,
+ /// <https://drafts.csswg.org/css-device-adapt/#zoom-desc>
+ pub initial_zoom: PinchZoomFactor,
+ /// <https://drafts.csswg.org/css-device-adapt/#min-max-width-desc>
+ pub min_zoom: Option<PinchZoomFactor>,
+ /// <https://drafts.csswg.org/css-device-adapt/#min-max-width-desc>
+ pub max_zoom: Option<PinchZoomFactor>,
+ /// <https://drafts.csswg.org/css-device-adapt/#user-zoom-desc>
+ pub user_zoom: UserZoom,
+ /// <https://drafts.csswg.org/css-device-adapt/#orientation-desc>
+ pub orientation: Orientation,
+}
+
+impl ToCss for ViewportConstraints {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ dest.write_str("@viewport { width: ")?;
+ self.size.width.to_css(dest)?;
+
+ dest.write_str("px; height: ")?;
+ self.size.height.to_css(dest)?;
+
+ dest.write_str("px; zoom: ")?;
+ self.initial_zoom.get().to_css(dest)?;
+
+ if let Some(min_zoom) = self.min_zoom {
+ dest.write_str("; min-zoom: ")?;
+ min_zoom.get().to_css(dest)?;
+ }
+
+ if let Some(max_zoom) = self.max_zoom {
+ dest.write_str("; max-zoom: ")?;
+ max_zoom.get().to_css(dest)?;
+ }
+
+ dest.write_str("; user-zoom: ")?;
+ self.user_zoom.to_css(dest)?;
+
+ dest.write_str("; orientation: ")?;
+ self.orientation.to_css(dest)?;
+ dest.write_str("; }")
+ }
+}
+
+/// <https://drafts.csswg.org/css-device-adapt/#descdef-viewport-zoom>
+#[derive(Clone, Copy, Debug, PartialEq, ToShmem)]
+#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
+pub enum Zoom {
+ /// A number value.
+ Number(f32),
+ /// A percentage value.
+ Percentage(f32),
+ /// The `auto` keyword.
+ Auto,
+}
+
+impl ToCss for Zoom {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: fmt::Write,
+ {
+ match *self {
+ Zoom::Number(number) => number.to_css(dest),
+ Zoom::Auto => dest.write_str("auto"),
+ Zoom::Percentage(percentage) => {
+ (percentage * 100.).to_css(dest)?;
+ dest.write_char('%')
+ },
+ }
+ }
+}
+
+impl Zoom {
+ /// Parse a zoom value per:
+ ///
+ /// <https://drafts.csswg.org/css-device-adapt/#descdef-viewport-zoom>
+ pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Zoom, ParseError<'i>> {
+ use crate::values::specified::AllowedNumericType::NonNegative;
+ use crate::ParsingMode;
+ use cssparser::Token;
+
+ let location = input.current_source_location();
+ match *input.next()? {
+ // TODO: This parse() method should take ParserContext as an
+ // argument, and pass ParsingMode owned by the ParserContext to
+ // is_ok() instead of using ParsingMode::DEFAULT directly.
+ // In order to do so, we might want to move these stuff into style::stylesheets::viewport_rule.
+ Token::Percentage { unit_value, .. }
+ if NonNegative.is_ok(ParsingMode::DEFAULT, unit_value) =>
+ {
+ Ok(Zoom::Percentage(unit_value))
+ },
+ Token::Number { value, .. } if NonNegative.is_ok(ParsingMode::DEFAULT, value) => {
+ Ok(Zoom::Number(value))
+ },
+ Token::Ident(ref value) if value.eq_ignore_ascii_case("auto") => Ok(Zoom::Auto),
+ ref t => Err(location.new_unexpected_token_error(t.clone())),
+ }
+ }
+
+ /// Get this zoom value as a float value. Returns `None` if the value is the
+ /// `auto` keyword.
+ #[inline]
+ pub fn to_f32(&self) -> Option<f32> {
+ match *self {
+ Zoom::Number(number) => Some(number as f32),
+ Zoom::Percentage(percentage) => Some(percentage as f32),
+ Zoom::Auto => None,
+ }
+ }
+}