From b404f7aee73ca72e89627f07d14fbe4af158369a Mon Sep 17 00:00:00 2001 From: Jonathan Chan Date: Sat, 19 Aug 2017 19:34:33 -0700 Subject: [PATCH 01/13] style: Refine custom_properties::parse_name; disallow extra tokens. Part 1 of a series of patches to implement the CSS Properties & Values API. --- components/style/custom_properties.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index 0a3d02bb90e8..f37dc8eb76f9 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -28,10 +28,17 @@ pub type Name = Atom; /// /// https://drafts.csswg.org/css-variables/#typedef-custom-property-name pub fn parse_name(s: &str) -> Result<&str, ()> { - if s.starts_with("--") { - Ok(&s[2..]) - } else { - Err(()) + let mut input = ParserInput::new(s); + let mut input = Parser::new(&mut input); + + match input.expect_ident() { + Ok(_) if s.starts_with("--") => { + match input.expect_exhausted() { + Ok(()) => Ok(&s[2..]), + Err(_) => Err(()), + } + }, + _ => Err(()) } } From a53656c9d5128c14c0e031d889f109061889b5b5 Mon Sep 17 00:00:00 2001 From: Jonathan Chan Date: Sat, 19 Aug 2017 19:38:03 -0700 Subject: [PATCH 02/13] resources: Add a pref for CSS Properties & Values API. The pref is layout.css.properties-and-values.enabled, and it currently defaults to false. Future patches in this series will add code that uses this pref. Part 2 of a series of patches to implement the CSS Properties & Values API. --- resources/prefs.json | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/prefs.json b/resources/prefs.json index f726cb288f4d..a8bceb5b7967 100644 --- a/resources/prefs.json +++ b/resources/prefs.json @@ -59,6 +59,7 @@ "layout.column-gap.enabled": false, "layout.column-width.enabled": false, "layout.columns.enabled": false, + "layout.css.properties-and-values.enabled": false, "layout.text-orientation.enabled": false, "layout.viewport.enabled": false, "layout.writing-mode.enabled": false, From 1f1e9335d65bea2ecd8e56060ac64c01f165dacf Mon Sep 17 00:00:00 2001 From: Jonathan Chan Date: Sat, 19 Aug 2017 19:41:24 -0700 Subject: [PATCH 03/13] script: Add WebIDL and stubs for CSS.(un)registerProperty. Part 3 of a series of patches to implement the CSS Properties & Values API. --- components/script/dom/css.rs | 15 ++++++++++++++- components/script/dom/webidls/CSS.webidl | 8 ++++++++ .../dom/webidls/PropertyDescriptorDict.webidl | 17 +++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 components/script/dom/webidls/PropertyDescriptorDict.webidl diff --git a/components/script/dom/css.rs b/components/script/dom/css.rs index b1b0210d1cfb..40c97a98d762 100644 --- a/components/script/dom/css.rs +++ b/components/script/dom/css.rs @@ -3,8 +3,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use cssparser::{Parser, ParserInput, serialize_identifier}; +use dom::bindings::codegen::Bindings::PropertyDescriptorDictBinding::PropertyDescriptorDict; use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; -use dom::bindings::error::Fallible; +use dom::bindings::error::{Error, Fallible}; use dom::bindings::reflector::Reflector; use dom::bindings::str::DOMString; use dom::window::Window; @@ -65,4 +66,16 @@ impl CSS { false } } + + // https://drafts.css-houdini.org/css-properties-values-api/#dom-css-registerproperty + pub fn RegisterProperty(win: &Window, options: &PropertyDescriptorDict) -> Result<(), Error> { + // STUB: Implemented by a later patch in this series. + Err(Error::NotSupported) + } + + // https://drafts.css-houdini.org/css-properties-values-api/#dom-css-unregisterproperty + pub fn UnregisterProperty(win: &Window, name: DOMString) -> Result<(), Error> { + // STUB: Implemented by a later patch in this series. + Err(Error::NotSupported) + } } diff --git a/components/script/dom/webidls/CSS.webidl b/components/script/dom/webidls/CSS.webidl index 5f4aa4f0befc..d5e71482029d 100644 --- a/components/script/dom/webidls/CSS.webidl +++ b/components/script/dom/webidls/CSS.webidl @@ -17,3 +17,11 @@ partial interface CSS { static boolean supports(DOMString property, DOMString value); static boolean supports(DOMString conditionText); }; + +// https://drafts.css-houdini.org/css-properties-values-api/#registering-custom-properties +partial interface CSS { + [Throws,Pref="layout.css.properties-and-values.enabled"] + static void registerProperty(PropertyDescriptorDict descriptor); + [Throws,Pref="layout.css.properties-and-values.enabled"] + static void unregisterProperty(DOMString name); +}; diff --git a/components/script/dom/webidls/PropertyDescriptorDict.webidl b/components/script/dom/webidls/PropertyDescriptorDict.webidl new file mode 100644 index 000000000000..2fa9e55ee5c4 --- /dev/null +++ b/components/script/dom/webidls/PropertyDescriptorDict.webidl @@ -0,0 +1,17 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ +/* + * The origin of this IDL file is + * https://drafts.css-houdini.org/css-properties-values-api/#registering-custom-properties + */ + +// Renamed from PropertyDescriptor to avoid conflicting with a JS class of the +// same name. +dictionary PropertyDescriptorDict +{ + required DOMString name; + DOMString syntax = "*"; + boolean inherits = false; + DOMString initialValue; +}; From fcee76f18f21ce445e87630878b605a511140d7a Mon Sep 17 00:00:00 2001 From: Jonathan Chan Date: Sat, 19 Aug 2017 19:42:25 -0700 Subject: [PATCH 04/13] style: Expose transform::SpecifiedValue's internals. A later patch in this series will use this when to inspect transform values for typed custom properties. Part 4 of a series of patches to implement the CSS Properties & Values API. --- components/style/properties/longhand/box.mako.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/style/properties/longhand/box.mako.rs b/components/style/properties/longhand/box.mako.rs index 531096dcf3b5..1301e8e28395 100644 --- a/components/style/properties/longhand/box.mako.rs +++ b/components/style/properties/longhand/box.mako.rs @@ -992,7 +992,7 @@ ${helpers.predefined_type( #[derive(Clone, Debug, HasViewportPercentage, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] - pub struct SpecifiedValue(Vec); + pub struct SpecifiedValue(pub Vec); impl ToCss for SpecifiedValue { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { From 52f541ec0c5c002b7cf97bbbcb1cfe1f634328ec Mon Sep 17 00:00:00 2001 From: Jonathan Chan Date: Sat, 19 Aug 2017 22:03:41 -0700 Subject: [PATCH 05/13] style: Add/refactor types of custom_properties for Properties & Values. Add a new ExtraData enum. We carry it along with a custom property's specified value in order to compute it, if it ends up being registered as being able to contain URLs through Properties & Values. When the specified value comes from a declaration, we keep track of the associated UrlExtraData in the Specified variant. However, specified values will also (implemented by a later patch in this series) be able to come from animations: in that case we are able to carry along a copy of the computed value in the Precomputed variant, to save us from recomputation and also to save us from having to re-resolve the URL. Add a new BorrowedExtraData enum, which is used for BorrowedSpecifiedValues. We use BorrowedSpecifiedValues when resolving custom properties; in the case that we inherit an untyped custom property, we create one from its computed value, which is a token stream. In this case we don't have a UrlExtraData or (Properties & Values) ComputedValue to compute with, and in fact we don't want to perform any computation. The BorrowedExtraData enum thus has an additional InheritedUntyped variant for that case. Pull out the css, first_token_type, and last_token_type fields of SpecifiedValue, BorrowedSpecifiedValue, and ComputedValue into a TokenStream struct (ComptuedValue then becomes a type alias of TokenStream). We'll use the TokenStream to store computed token stream values for typed custom properties: it wouldn't make sense to use SpecifiedValue because we have no need of the additional information (and that would also lead to properties_and_values::ComputedValue containing a SpecifiedValue which contains an ExtraData which may contain a properties_and_values::ComputedValue, which the compiler does not like). The properties_and_values module is addded by a later patch in this series, so we omit the Precomputed variant for now. Part 5 of a series of patches to implement the CSS Properties & Values API. --- components/style/custom_properties.rs | 193 ++++++++++++++++++++++---- 1 file changed, 165 insertions(+), 28 deletions(-) diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index f37dc8eb76f9..6286ee27ed15 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -10,6 +10,7 @@ use Atom; use cssparser::{Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType}; use parser::ParserContext; use properties::{CSSWideKeyword, DeclaredValue}; +use properties_and_values; use selectors::parser::SelectorParseError; use servo_arc::Arc; use std::ascii::AsciiExt; @@ -17,7 +18,9 @@ use std::borrow::{Borrow, Cow}; use std::collections::{HashMap, HashSet}; use std::fmt; use std::hash::Hash; +use std::ops::Deref; use style_traits::{HasViewportPercentage, ToCss, StyleParseError, ParseError}; +use stylesheets::UrlExtraData; /// A custom property name is just an `Atom`. /// @@ -42,6 +45,79 @@ pub fn parse_name(s: &str) -> Result<&str, ()> { } } +/// Extra data that we need to pass along with a custom property's specified +/// value in order to compute it, if it ends up being registered as being able +/// to contain URLs through Properties & Values. +/// When the specified value comes from a declaration, we keep track of the +/// associated UrlExtraData. However, specified values can also come from +/// animations: in that case we are able to carry along a copy of the computed +/// value so that we can skip computation altogether (and hopefully avoid bugs +/// with resolving URLs wrong). +#[derive(Clone, PartialEq, Debug)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub enum ExtraData { + /// The specified value comes from a declaration (whence we get + /// the UrlExtraData). + Specified(UrlExtraData), +} + +impl<'a> Into> for &'a ExtraData { + fn into(self) -> BorrowedExtraData<'a> { + match *self { + ExtraData::Specified(ref x) => BorrowedExtraData::Specified(x), + } + } +} + +/// A borrowed version of an ExtraData. Used for BorrowedSpecifiedValue. Has an +/// extra variant, InheritedUntyped, for when the specified value is really +/// borrowed from an inherited value and the property is unregistered. In that +/// case the token stream value is already the computed value. +#[derive(Clone, PartialEq, Debug)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub enum BorrowedExtraData<'a> { + /// The specified value comes from a declaration (whence we get the + /// UrlExtraData). + Specified(&'a UrlExtraData), + + /// The specified value comes from an inherited value for an untyped custom + /// property, and we should just use the token stream value as the computed + /// value. + InheritedUntyped, +} + +/// A token stream, represented as a string and boundary tokens. +/// Custom properties' specified values are token streams. +#[derive(Clone, PartialEq, Debug)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub struct TokenStream { + /// The specified text. + pub css: String, + + /// The first token in the serialization. + /// Used when resolving variable references, because we would like to + /// substitute token streams rather than variables; in particular, if + /// `foo: 5` and we write `width: var(--foo) em`, width's should not be + /// declared to be `5em`, a dimension token with value , but rather the + /// integer token `5` followed by the identifier `em`, which is invalid. + /// We implement this by adding /**/ when necessary (see + /// ComputedValue::push). + pub first_token_type: TokenSerializationType, + + /// The last token in the serialization. + pub last_token_type: TokenSerializationType, +} + +impl Default for TokenStream { + fn default() -> Self { + TokenStream { + css: "".to_owned(), + first_token_type: TokenSerializationType::nothing(), + last_token_type: TokenSerializationType::nothing(), + } + } +} + /// A specified value for a custom property is just a set of tokens. /// /// We preserve the original CSS for serialization, and also the variable @@ -49,13 +125,26 @@ pub fn parse_name(s: &str) -> Result<&str, ()> { #[derive(Clone, PartialEq, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct SpecifiedValue { - css: String, - - first_token_type: TokenSerializationType, - last_token_type: TokenSerializationType, + /// The specified token stream. + pub token_stream: TokenStream, /// Custom property names in var() functions. - references: HashSet, + /// This being None should be treated exactly the same as it being an empty + /// HashSet; it exists so we don't have to create a new HashSet every time + /// we are applying an interpolated custom property. + references: Option>, + + /// Extra data needed to compute the specified value. See the comment on + /// ExtraData. + pub extra: ExtraData, +} + +impl Deref for SpecifiedValue { + type Target = TokenStream; + + fn deref(&self) -> &TokenStream { + &self.token_stream + } } impl HasViewportPercentage for SpecifiedValue { @@ -64,23 +153,36 @@ impl HasViewportPercentage for SpecifiedValue { } } +impl<'a> From<&'a properties_and_values::ComputedValue> for SpecifiedValue { + fn from(other: &'a properties_and_values::ComputedValue) -> Self { + SpecifiedValue { + token_stream: other.into(), + references: None, + extra: ExtraData::Precomputed(other.clone()), + } + } +} + /// This struct is a cheap borrowed version of a `SpecifiedValue`. pub struct BorrowedSpecifiedValue<'a> { - css: &'a str, - first_token_type: TokenSerializationType, - last_token_type: TokenSerializationType, + token_stream: &'a TokenStream, references: Option<&'a HashSet>, + /// Extra data needed to compute the specified value. See the comment on + /// ExtraData. + pub extra: BorrowedExtraData<'a>, +} + +impl<'a> Deref for BorrowedSpecifiedValue<'a> { + type Target = TokenStream; + + fn deref(&self) -> &TokenStream { + &self.token_stream + } } /// A computed value is just a set of tokens as well, until we resolve variables /// properly. -#[derive(Clone, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -pub struct ComputedValue { - css: String, - first_token_type: TokenSerializationType, - last_token_type: TokenSerializationType, -} +pub type ComputedValue = TokenStream; impl ToCss for SpecifiedValue { fn to_css(&self, dest: &mut W) -> fmt::Result @@ -98,6 +200,30 @@ impl ToCss for ComputedValue { } } +impl<'a> From<&'a properties_and_values::ComputedValue> for TokenStream { + fn from(other: &'a properties_and_values::ComputedValue) -> Self { + let mut css = String::new(); + other.to_css::(&mut css).unwrap(); + let (first, last) = { + let mut missing_closing_characters = String::new(); + let mut input = ParserInput::new(&css); + let mut input = Parser::new(&mut input); + // XXX agh! why do we need to parse again just to get + // these guys. + parse_declaration_value_block( + &mut input, + &mut None, + &mut missing_closing_characters + ).unwrap() + }; + TokenStream { + css: css, + first_token_type: first, + last_token_type: last, + } + } +} + /// A map from CSS variable names to CSS variable computed values, used for /// resolving. /// @@ -105,6 +231,10 @@ impl ToCss for ComputedValue { /// DOM. CSSDeclarations expose property names as indexed properties, which /// need to be stable. So we keep an array of property names which order is /// determined on the order that they are added to the name-value map. +/// +/// Outside of this module, this map will normally be accessed through a +/// `properties_and_values::CustomPropertiesMap`, which composes it and stores +/// computed values for typed custom properties as well. pub type CustomPropertiesMap = OrderedMap; /// A map that preserves order for the keys, and that is easily indexable. @@ -251,17 +381,26 @@ impl ComputedValue { impl SpecifiedValue { /// Parse a custom property SpecifiedValue. - pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>) + pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result, ParseError<'i>> { let mut references = Some(HashSet::new()); let (first, css, last) = parse_self_contained_declaration_value(input, &mut references)?; Ok(Box::new(SpecifiedValue { - css: css.into_owned(), - first_token_type: first, - last_token_type: last, - references: references.unwrap(), + token_stream: TokenStream { + css: css.into_owned(), + first_token_type: first, + last_token_type: last, + }, + references: references, + extra: ExtraData::Specified(context.url_data.clone()), })) } + + /// Returns whether or not this specified value contains any variable + /// references. + pub fn has_references(&self) -> bool { + !self.references.as_ref().map(|x| x.is_empty()).unwrap_or(true) + } } /// Parse the value of a non-custom property that contains `var()` references. @@ -473,10 +612,9 @@ pub fn cascade<'a>(custom_properties: &mut Option(custom_properties: &mut Option { map.insert(name, BorrowedSpecifiedValue { - css: &specified_value.css, - first_token_type: specified_value.first_token_type, - last_token_type: specified_value.last_token_type, - references: Some(&specified_value.references), + token_stream: &specified_value.token_stream, + references: specified_value.references.as_ref(), + extra: (&specified_value.extra).into(), }); }, DeclaredValue::WithVariables(_) => unreachable!(), From 04120c302a8f0b80843a4ff14a8804532f498a1f Mon Sep 17 00:00:00 2001 From: Jonathan Chan Date: Sat, 19 Aug 2017 22:06:07 -0700 Subject: [PATCH 06/13] style: Add a properties_and_values module for CSS Properties & Values. Typed custom properties are exactly the same as regular custom properties except when it comes to inheritance, computed values, and animation, so this module is intended to be as independent from custom_properties as possible (they do use types from each other, though, where it would be clumsy to do otherwise). This commit doesn't hook up the module to anything: that's handled by later patches in this series. Part 6 of a series of patches to implement the CSS Properties & Values API. --- components/style/custom_properties.rs | 9 + components/style/lib.rs | 1 + components/style/properties_and_values.rs | 1167 +++++++++++++++++++++ 3 files changed, 1177 insertions(+) create mode 100644 components/style/properties_and_values.rs diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index 6286ee27ed15..5cf736872b72 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -59,12 +59,17 @@ pub enum ExtraData { /// The specified value comes from a declaration (whence we get /// the UrlExtraData). Specified(UrlExtraData), + + /// The specified value comes from an animation or an inherited typed custom + /// property (whence we get the properties_and_values::ComputedValue). + Precomputed(properties_and_values::ComputedValue), } impl<'a> Into> for &'a ExtraData { fn into(self) -> BorrowedExtraData<'a> { match *self { ExtraData::Specified(ref x) => BorrowedExtraData::Specified(x), + ExtraData::Precomputed(ref x) => BorrowedExtraData::Precomputed(x), } } } @@ -80,6 +85,10 @@ pub enum BorrowedExtraData<'a> { /// UrlExtraData). Specified(&'a UrlExtraData), + /// The specified value comes from an animation or an inherited typed custom + /// property (whence we get the properties_and_values::ComputedValue). + Precomputed(&'a properties_and_values::ComputedValue), + /// The specified value comes from an inherited value for an untyped custom /// property, and we should just use the token stream value as the computed /// value. diff --git a/components/style/lib.rs b/components/style/lib.rs index fc409276b679..d880a44f174a 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -119,6 +119,7 @@ pub mod matching; pub mod media_queries; pub mod parallel; pub mod parser; +pub mod properties_and_values; pub mod rule_tree; pub mod scoped_tls; pub mod selector_map; diff --git a/components/style/properties_and_values.rs b/components/style/properties_and_values.rs new file mode 100644 index 000000000000..9509f2232717 --- /dev/null +++ b/components/style/properties_and_values.rs @@ -0,0 +1,1167 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +//! Support for the [Properties & Values API][spec]. +//! +//! [spec]: https://drafts.css-houdini.org/css-properties-values-api-1/ + +use Atom; +use cssparser::{BasicParseError, ParseError, ParserInput, Parser, Token}; +use custom_properties::{self, Name}; +use parser::{Parse, ParserContext}; +use properties::CSSWideKeyword; +use properties::longhands::transform; +use selectors::parser::SelectorParseError; +use std::collections::{HashMap, HashSet}; +use std::fmt; +use std::ops::{Deref, DerefMut}; +use std::vec::Vec; +use style_traits::{ParseError as StyleTraitsParseError, StyleParseError}; +use style_traits::values::{OneOrMoreSeparated, Space, ToCss}; +use values; +use values::computed::{self, ComputedValueAsSpecified, ToComputedValue}; +use values::specified; + +/// A registration for a custom property. +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub struct Registration { + /// The custom property name, sans leading '--'. + pub name: Name, + + /// The syntax of the custom property. + pub syntax: Syntax, + + /// Whether the custom property is inherited down the DOM tree. + pub inherits: bool, + + /// The initial value of the custom property. + /// + /// Ideally we'd merge this with `syntax` so that illegal states would be + /// unrepresentable. But while we could do that by turning the fields of the + /// SpecifiedVariable variants into Option's, we would need a more + /// expressive type system to do this with disjunctions. + /// + /// Ideally this would also be a ComputedValue. But to reuse the + /// to_computed_value code we need a style::values::computed::Context, which + /// is a real pain to construct in a nice way for both Stylo & Servo. + /// Instead we just store the specified value and compute this later; the + /// is_computationally_independent check should mean this doesn't matter. + pub initial_value: Option, +} + +/// A versioned set of registered custom properties, stored on the document. +/// The [[registeredPropertySet]] of the spec. We keep track of the version to +/// know if we need to recompute which declarations are valid. +#[derive(Default)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub struct RegisteredPropertySet { + /// The set of registered custom properties. Names are sans leading '--'. + registrations: HashMap, + + /// The current version. Must be incremented whenever `registrations` is + /// modified. + generation: u32, +} + +impl RegisteredPropertySet { + /// Attempt to register a custom property. + /// + /// If a custom property has already been registered with that name, return + /// Err(()), otherwise return Ok(()) and increment the generation. + pub fn register_property(&mut self, registration: Registration) -> Result<(), ()> { + match self.registrations.insert(registration.name.clone(), registration) { + Some(_) => Err(()), + None => { + self.generation += 1; + Ok(()) + } + } + } + + /// Attempt to unregister a custom property. + /// + /// If no custom property has been registered with that name, return + /// Err(()), otherwise return Ok(()) and increment the generation. + pub fn unregister_property(&mut self, name: &Name) -> Result<(), ()> { + match self.registrations.remove(name) { + Some(_) => { + self.generation += 1; + Ok(()) + }, + None => Err(()) + } + } + + /// Return the current generation. + /// + /// The generation is incremented every time the set of custom property + /// registrations changes. It's used by the Stylist to keep track of when it + /// has to restyle. + pub fn generation(&self) -> u32 { + self.generation + } + + /// Attempt to get the registration for the custom property with the given + /// name. + pub fn get(&self, name: &Name) -> Option<&Registration> { + self.registrations.get(name) + } + + /// Return the set of all uninherited custom properties. + /// + /// Used by style::properties::compute_early_custom_properties to insert + /// initial values when needed. + pub fn uninherited_properties(&self) -> HashSet<&Name> { + self.registrations + .iter() + .filter(|&(_, registration)| !registration.inherits) + .map(|(name, _)| name) + .collect() + } + + /// Returns (computed) initial values for all custom properties except those + /// specified in `except`. If `except` is None, it's treated as the empty + /// set, and we return the initial values for all custom properties. + /// + /// Initial values are computationally idempotent, so they should not need + /// actual computation. Unfortunately the most convenient way to convert a + /// specified value to a computed value is to use the to_computed_value + /// method, which requires a context. + pub fn initial_values_except( + &self, + context: &computed::Context, + except: Option<&HashSet<&Name>> + ) -> CustomPropertiesMap { + let mut map = CustomPropertiesMap::new(); + for (name, registration) in self.registrations.iter() { + if except.map(|x| x.contains(name)).unwrap_or(false) { + continue + } + if let Some(ref initial) = registration.initial_value { + let computed = + (&initial.clone().to_computed_value(context) + .expect("The initial value should never fail to compute.")) + .into(); + map.insert((*name).clone(), computed); + } + } + map + } +} + +/// The result of a call to register_property or unregister_property, +/// corresponding to the errors that can be thrown by CSS.(un)registerProperty. +/// +/// Should be kept in sync with mozilla::PropertyRegistrationResult on the Gecko +/// side. +pub enum PropertyRegistrationResult { + /// Indicates that the call was successful. The caller should return without + /// error. + Ok = 0, + /// Indicates that the call failed, and that the caller should throw a + /// SyntaxError. + SyntaxError, + /// Indicates that the call failed, and that the caller should throw an + /// InvalidModificationError. + InvalidModificationError, + /// Indicates that the call failed, and that the caller should throw a + /// NotFoundError. + NotFoundError, +} + +/// Attempt to register a custom property. +/// +/// This is used by the CSS.registerProperty implementations for Servo and Gecko +/// in order to share logic. The caller should handle the returned +/// PropertyRegistraitonResult by throwing the appropriate DOM error. +pub fn register_property( + registered_property_set: &mut RegisteredPropertySet, + parser_context: &ParserContext, + name: &str, + syntax: &str, + inherits: bool, + initial_value: Option<&str> +) -> PropertyRegistrationResult { + let name = match custom_properties::parse_name(name) { + Ok(name) => name, + Err(()) => return PropertyRegistrationResult::SyntaxError, + }; + + let syntax = match Syntax::from_string(syntax) { + Ok(syntax) => syntax, + Err(()) => return PropertyRegistrationResult::SyntaxError, + }; + + let initial_value = match initial_value { + Some(ref specified) => { + let mut input = ParserInput::new(specified); + let mut input = Parser::new(&mut input); + match syntax.parse(parser_context, &mut input) { + Ok(parsed) => { + if parsed.is_computationally_independent() { + Some(parsed) + } else { + return PropertyRegistrationResult::SyntaxError + } + }, + _ => return PropertyRegistrationResult::SyntaxError, + } + }, + None if matches!(syntax, Syntax::Anything) => None, + // initialValue is required if the syntax is not '*'. + _ => return PropertyRegistrationResult::SyntaxError, + }; + + let result = + registered_property_set + .register_property(Registration { + name: name.into(), + syntax: syntax, + inherits: inherits, + initial_value: initial_value, + }); + + match result { + Ok(_) => PropertyRegistrationResult::Ok, + Err(_) => PropertyRegistrationResult::InvalidModificationError, + } +} + +/// Attempt to unregister a custom property. +/// +/// This is used by the CSS.registerProperty implementations for Servo and Gecko +/// in order to share logic. The caller should handle the returned +/// PropertyRegistraitonResult by throwing the appropriate DOM error. +pub fn unregister_property( + registered_property_set: &mut RegisteredPropertySet, + name: &str +) -> PropertyRegistrationResult { + let name = match custom_properties::parse_name(name) { + Ok(name) => name, + Err(()) => return PropertyRegistrationResult::SyntaxError, + }; + + let result = + registered_property_set + .unregister_property(&name.into()); + + match result { + Ok(_) => PropertyRegistrationResult::Ok, + Err(_) => PropertyRegistrationResult::NotFoundError, + } +} + + + +/// A CSS . +/// +/// We make a newtype for Atom and implement ToCss ourselves because the +/// ToCss implementation for atom in `style_traits::values` uses `cssparsers`'s +/// `serialize_string` function, which writes a double-quoted CSS string. We're +/// only storing , which should be serialized as specified. +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub struct Ident(pub Atom); + +impl ComputedValueAsSpecified for Ident {} + +impl ToCss for Ident { + #[cfg(feature = "servo")] + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + dest.write_str(&*self.0) + } + + #[cfg(feature = "gecko")] + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + dest.write_str(&self.0.to_string()) + } +} + +impl Deref for Ident { + type Target = Atom; + + fn deref(&self) -> &Atom { + &self.0 + } +} + +/// A computed value. +/// +/// FIXME: While as_str() resolves URLs on the Servo side, it currently does not +/// resolve them on the Gecko side (see the comment on Gecko's +/// specified::url::SpecifiedUrl implementation). That means we don't actually +/// resolve URLs when running in Stylo yet. +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub struct ComputedUrl(pub String); + +impl ComputedUrl { + #[cfg(feature = "servo")] + fn from_specified(url: &specified::url::SpecifiedUrl) -> Result { + url.url().map(|x| ComputedUrl(x.as_str().to_owned())).ok_or(()) + } + + #[cfg(feature = "gecko")] + fn from_specified(url: &specified::url::SpecifiedUrl) -> Result { + // Doesn't work. + // url.extra_data.join(url.as_str()).map(|x| ComputedUrl(x.as_str().to_owned())).ok_or(()) + Ok(ComputedUrl(url.as_str().to_owned())) + // Note: the Gecko binding doesn't currently output resolved URLs. + } +} + +impl ToCss for ComputedUrl { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + dest.write_str("url(")?; + self.0.to_css(dest)?; + dest.write_str(")") + } +} + +/// A basic custom property syntax string for a custom property that, used to +/// build up disjunctions and list terms. +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub enum Type { + /// Syntax to allow any valid value. + Length, + /// Syntax to allow any valid value. + Number, + /// Syntax to allow any valid value. + Percentage, + /// Syntax to allow any valid or value, or any valid + /// expression combining and components. + LengthPercentage, + /// Syntax to allow any valid value. + Color, + /// Syntax to allow any valid value. + Image, + /// Syntax to allow any valid value. + Url, + /// Syntax to allow any valid value. + Integer, + /// Syntax to allow any valid value. + Angle, + /// Syntax to allow any valid