diff options
Diffstat (limited to 'servo/components/style_traits')
-rw-r--r-- | servo/components/style_traits/Cargo.toml | 31 | ||||
-rw-r--r-- | servo/components/style_traits/arc_slice.rs | 163 | ||||
-rw-r--r-- | servo/components/style_traits/dom.rs | 26 | ||||
-rw-r--r-- | servo/components/style_traits/lib.rs | 258 | ||||
-rw-r--r-- | servo/components/style_traits/owned_slice.rs | 198 | ||||
-rw-r--r-- | servo/components/style_traits/owned_str.rs | 81 | ||||
-rw-r--r-- | servo/components/style_traits/specified_value_info.rs | 138 | ||||
-rw-r--r-- | servo/components/style_traits/values.rs | 615 | ||||
-rw-r--r-- | servo/components/style_traits/viewport.rs | 148 |
9 files changed, 1658 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..88c0aab175 --- /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 = "1.0" +cssparser = "0.29" +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..1f10ed9455 --- /dev/null +++ b/servo/components/style_traits/arc_slice.rs @@ -0,0 +1,163 @@ +/* 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. +/// +/// cbindgen:derive-eq=false +/// cbindgen:derive-neq=false +#[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.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 thin_arc = ThinArc::from_header_and_iter(ARC_SLICE_CANARY, items); + thin_arc.with_arc(|a| a.mark_as_intentionally_leaked()); + ArcSlice(thin_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.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.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.with_arc(|arc| arc.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..be2d275b65 --- /dev/null +++ b/servo/components/style_traits/lib.rs @@ -0,0 +1,258 @@ +/* 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>), + /// @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, + /// Unsupported @ rule + UnsupportedAtRule(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, + } + } +} + +bitflags! { + /// The mode to use when parsing values. + 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..96bb23651f --- /dev/null +++ b/servo/components/style_traits/values.rs @@ -0,0 +1,615 @@ +/* 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::RGBA); +impl_to_css_for_predefined_type!(::cssparser::Color); +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, + } + } +} |