diff --git a/components/layout_thread/dom_wrapper.rs b/components/layout_thread/dom_wrapper.rs index d35536c627f6..c254df2becf4 100644 --- a/components/layout_thread/dom_wrapper.rs +++ b/components/layout_thread/dom_wrapper.rs @@ -74,6 +74,7 @@ use style::dom::{PresentationalHintsSynthesizer, TElement, TNode, UnsafeNode}; use style::element_state::*; use style::font_metrics::ServoMetricsProvider; use style::properties::{ComputedValues, PropertyDeclarationBlock}; +use style::properties_and_values::RegisteredPropertySet; use style::selector_parser::{AttrValue as SelectorAttrValue, NonTSPseudoClass, PseudoClassStringArg}; use style::selector_parser::{PseudoElement, SelectorImpl, extended_filtering}; use style::shared_lock::{SharedRwLock as StyleSharedRwLock, Locked as StyleLocked}; @@ -365,6 +366,10 @@ impl<'ld> ServoLayoutDocument<'ld> { unsafe { self.document.style_shared_lock() } } + pub fn registered_property_set(&self) -> Arc> { + unsafe { self.document.registered_property_set() } + } + pub fn from_layout_js(doc: LayoutJS) -> ServoLayoutDocument<'ld> { ServoLayoutDocument { document: doc, diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 59796fe3f92d..1d19b05c2546 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -1137,6 +1137,18 @@ impl LayoutThread { element, self.url, data.query_type); trace!("{:?}", ShowSubtree(element.as_node())); + let document_shared_lock = document.style_shared_lock(); + self.document_shared_lock = Some(document_shared_lock.clone()); + let document_guard = document_shared_lock.read(); + + // Set the registered property set (if it isn't already set), and mark + // stylesheet origins dirty if they've changed, analogously to the check + // regarding device changes below. + self.stylist.set_registered_property_set(document.registered_property_set()); + if self.stylist.registered_property_set_updated(&document_guard) { + self.stylist.force_stylesheet_origins_dirty(OriginSet::all()); + } + let initial_viewport = data.window_size.initial_viewport; let device_pixel_ratio = data.window_size.device_pixel_ratio; let old_viewport_size = self.viewport_size; @@ -1145,12 +1157,9 @@ impl LayoutThread { // Calculate the actual viewport as per DEVICE-ADAPT § 6 - let document_shared_lock = document.style_shared_lock(); - self.document_shared_lock = Some(document_shared_lock.clone()); - let author_guard = document_shared_lock.read(); let device = Device::new(MediaType::screen(), initial_viewport, device_pixel_ratio); let sheet_origins_affected_by_device_change = - self.stylist.set_device(device, &author_guard); + self.stylist.set_device(device, &document_guard); self.stylist.force_stylesheet_origins_dirty(sheet_origins_affected_by_device_change); self.viewport_size = @@ -1200,8 +1209,9 @@ impl LayoutThread { let ua_stylesheets = &*UA_STYLESHEETS; let ua_or_user_guard = ua_stylesheets.shared_lock.read(); let guards = StylesheetGuards { - author: &author_guard, + author: &document_guard, ua_or_user: &ua_or_user_guard, + registered_property_set: &document_guard, }; { @@ -1517,12 +1527,13 @@ impl LayoutThread { // Unwrap here should not panic since self.root_flow is only ever set to Some(_) // in handle_reflow() where self.document_shared_lock is as well. - let author_shared_lock = self.document_shared_lock.clone().unwrap(); - let author_guard = author_shared_lock.read(); + let document_shared_lock = self.document_shared_lock.clone().unwrap(); + let document_guard = document_shared_lock.read(); let ua_or_user_guard = UA_STYLESHEETS.shared_lock.read(); let guards = StylesheetGuards { - author: &author_guard, + author: &document_guard, ua_or_user: &ua_or_user_guard, + registered_property_set: &document_guard, }; let snapshots = SnapshotMap::new(); let mut layout_context = self.build_layout_context(guards, diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index df681e3add9f..96a66dff2cb9 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -100,6 +100,7 @@ use style::context::QuirksMode; use style::element_state::*; use style::media_queries::MediaList; use style::properties::PropertyDeclarationBlock; +use style::properties_and_values; use style::selector_parser::{PseudoElement, Snapshot}; use style::shared_lock::{SharedRwLock as StyleSharedRwLock, Locked as StyleLocked}; use style::stylesheet_set::StylesheetSet; @@ -410,6 +411,7 @@ unsafe_no_jsmanaged_fields!(WebGLVertexArrayId); unsafe_no_jsmanaged_fields!(MediaList); unsafe_no_jsmanaged_fields!(WebVRGamepadHand); unsafe_no_jsmanaged_fields!(ScriptToConstellationChan); +unsafe_no_jsmanaged_fields!(StyleLocked); unsafe impl<'a> JSTraceable for &'a str { #[inline] diff --git a/components/script/dom/css.rs b/components/script/dom/css.rs index b1b0210d1cfb..8e68ad6f23b4 100644 --- a/components/script/dom/css.rs +++ b/components/script/dom/css.rs @@ -3,18 +3,42 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use cssparser::{Parser, ParserInput, serialize_identifier}; +use dom::bindings::codegen::Bindings::DocumentBinding::DocumentBinding::DocumentMethods; +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::inheritance::Castable; use dom::bindings::reflector::Reflector; use dom::bindings::str::DOMString; +use dom::node::{Node, NodeDamage}; use dom::window::Window; use dom_struct::dom_struct; use style::context::QuirksMode; use style::parser::ParserContext; +use style::properties_and_values::{self, PropertyRegistrationResult}; use style::stylesheets::CssRuleType; use style::stylesheets::supports_rule::{Declaration, parse_condition_or_declaration}; use style_traits::PARSING_MODE_DEFAULT; +fn handle_property_registration_result( + win: &Window, + result: PropertyRegistrationResult +) -> Result<(), Error> { + match result { + PropertyRegistrationResult::Ok => { + // Should lead to the RestyleHint::restyle_subtree() being inserted + // eventually in handle_reflow due to checks in Stylist. + if let Some(element) = win.Document().GetDocumentElement() { + element.upcast::().dirty(NodeDamage::NodeStyleDamaged); + } + Ok(()) + }, + PropertyRegistrationResult::SyntaxError => Err(Error::Syntax), + PropertyRegistrationResult::InvalidModificationError => Err(Error::InvalidModification), + PropertyRegistrationResult::NotFoundError => Err(Error::NotFound), + } +} + #[dom_struct] pub struct CSS { reflector_: Reflector, @@ -65,4 +89,41 @@ impl CSS { false } } + + /// https://drafts.css-houdini.org/css-properties-values-api/#dom-css-registerproperty + pub fn RegisterProperty(win: &Window, options: &PropertyDescriptorDict) -> Result<(), Error> { + let document = win.Document(); + let registered_property_set = document.registered_property_set(); + let mut guard = document.style_shared_lock().write(); + let registered_property_set = registered_property_set.write_with(&mut guard); + handle_property_registration_result( + win, + properties_and_values::register_property( + registered_property_set, + &ParserContext::new_for_cssom( + &win.Document().url(), + win.css_error_reporter(), + /* rule_type */ None, + PARSING_MODE_DEFAULT, + win.Document().quirks_mode(), + ), + &*options.name, + &options.syntax, + options.inherits, + options.initialValue.as_ref().map(|x| &**x), + ) + ) + } + + /// https://drafts.css-houdini.org/css-properties-values-api/#dom-css-unregisterproperty + pub fn UnregisterProperty(win: &Window, name: DOMString) -> Result<(), Error> { + let document = win.Document(); + let registered_property_set = document.registered_property_set(); + let mut guard = document.style_shared_lock().write(); + let registered_property_set = registered_property_set.write_with(&mut guard); + handle_property_registration_result( + win, + properties_and_values::unregister_property(registered_property_set, &*name) + ) + } } diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 4629ff8814f5..694fb3ae517d 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -138,8 +138,9 @@ use style::attr::AttrValue; use style::context::{QuirksMode, ReflowGoal}; use style::invalidation::element::restyle_hints::{RestyleHint, RESTYLE_SELF, RESTYLE_STYLE_ATTRIBUTE}; use style::media_queries::{Device, MediaList, MediaType}; +use style::properties_and_values::RegisteredPropertySet; use style::selector_parser::{RestyleDamage, Snapshot}; -use style::shared_lock::{SharedRwLock as StyleSharedRwLock, SharedRwLockReadGuard}; +use style::shared_lock::{Locked as StyleLocked, SharedRwLock as StyleSharedRwLock, SharedRwLockReadGuard}; use style::str::{HTML_SPACE_CHARACTERS, split_html_space_chars, str_join}; use style::stylesheet_set::StylesheetSet; use style::stylesheets::{Stylesheet, StylesheetContents, OriginSet}; @@ -249,7 +250,8 @@ pub struct Document { scripts: MutNullableJS, anchors: MutNullableJS, applets: MutNullableJS, - /// Lock use for style attributes and author-origin stylesheet objects in this document. + /// Lock used for style attributes, author-origin stylesheet objects, and + /// the registered property set in/for this document. /// Can be acquired once for accessing many objects. style_shared_lock: StyleSharedRwLock, /// List of stylesheets associated with nodes in this document. |None| if the list needs to be refreshed. @@ -351,6 +353,10 @@ pub struct Document { /// is inserted or removed from the document. /// See https://html.spec.whatwg.org/multipage/#form-owner form_id_listener_map: DOMRefCell>>>, + + /// The set of registered custom properties. + #[ignore_heap_size_of = "Arc"] + registered_property_set: Arc>, } #[derive(JSTraceable, HeapSizeOf)] @@ -2041,6 +2047,7 @@ pub trait LayoutDocumentHelpers { unsafe fn will_paint(&self); unsafe fn quirks_mode(&self) -> QuirksMode; unsafe fn style_shared_lock(&self) -> &StyleSharedRwLock; + unsafe fn registered_property_set(&self) -> Arc>; } #[allow(unsafe_code)] @@ -2082,6 +2089,11 @@ impl LayoutDocumentHelpers for LayoutJS { unsafe fn style_shared_lock(&self) -> &StyleSharedRwLock { (*self.unsafe_get()).style_shared_lock() } + + #[inline] + unsafe fn registered_property_set(&self) -> Arc> { + (*self.unsafe_get()).registered_property_set() + } } // https://html.spec.whatwg.org/multipage/#is-a-registrable-domain-suffix-of-or-is-equal-to @@ -2169,6 +2181,18 @@ impl Document { (DocumentReadyState::Complete, true) }; + lazy_static! { + /// Per-process shared lock for author-origin stylesheets + /// + /// FIXME: make it per-document or per-pipeline instead: + /// https://github.com/servo/servo/issues/16027 + /// (Need to figure out what to do with the style attribute + /// of elements adopted into another document.) + static ref PER_PROCESS_AUTHOR_SHARED_LOCK: StyleSharedRwLock = { + StyleSharedRwLock::new() + }; + } + Document { node: Node::new_document_node(), window: JS::from_ref(window), @@ -2204,17 +2228,6 @@ impl Document { anchors: Default::default(), applets: Default::default(), style_shared_lock: { - lazy_static! { - /// Per-process shared lock for author-origin stylesheets - /// - /// FIXME: make it per-document or per-pipeline instead: - /// https://github.com/servo/servo/issues/16027 - /// (Need to figure out what to do with the style attribute - /// of elements adopted into another document.) - static ref PER_PROCESS_AUTHOR_SHARED_LOCK: StyleSharedRwLock = { - StyleSharedRwLock::new() - }; - } PER_PROCESS_AUTHOR_SHARED_LOCK.clone() //StyleSharedRwLock::new() }, @@ -2261,6 +2274,7 @@ impl Document { dom_count: Cell::new(1), fullscreen_element: MutNullableJS::new(None), form_id_listener_map: Default::default(), + registered_property_set: Arc::new(PER_PROCESS_AUTHOR_SHARED_LOCK.wrap(Default::default())), } } @@ -2680,6 +2694,10 @@ impl Document { } } } + + pub fn registered_property_set(&self) -> Arc> { + self.registered_property_set.clone() + } } 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; +}; diff --git a/components/style/animation.rs b/components/style/animation.rs index 6736d39269f0..f9cc23949fb2 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -494,6 +494,8 @@ fn compute_style_for_animation_step(context: &SharedStyleContext, // This currently ignores visited styles, which seems acceptable, // as existing browsers don't appear to animate visited styles. + let registered_property_set = + context.stylist.registered_property_set().read_with(context.guards.registered_property_set); let computed = properties::apply_declarations(context.stylist.device(), /* pseudo = */ None, @@ -506,7 +508,8 @@ fn compute_style_for_animation_step(context: &SharedStyleContext, /* visited_style = */ None, font_metrics_provider, CascadeFlags::empty(), - context.quirks_mode); + context.quirks_mode, + registered_property_set); computed } } diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index 0a3d02bb90e8..32ec6672bb4e 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -7,17 +7,19 @@ //! [custom]: https://drafts.csswg.org/css-variables/ use Atom; -use cssparser::{Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType}; +use cssparser::{self, 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; 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`. /// @@ -28,10 +30,99 @@ 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(()) + } +} + +/// 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), + + /// 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), + } + } +} + +/// 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 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. + 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(), + } } } @@ -42,13 +133,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 { @@ -57,23 +161,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 @@ -91,6 +208,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. /// @@ -98,6 +239,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. @@ -159,7 +304,7 @@ where } /// Remove an item given its key. - fn remove(&mut self, key: &Q) -> Option + pub fn remove(&mut self, key: &Q) -> Option where K: Borrow, Q: Hash + Eq, @@ -244,17 +389,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. @@ -305,7 +459,7 @@ fn parse_declaration_value<'i, 't> /// Like parse_declaration_value, but accept `!` and `;` since they are only /// invalid at the top level -fn parse_declaration_value_block<'i, 't> +pub fn parse_declaration_value_block<'i, 't> (input: &mut Parser<'i, 't>, references: &mut Option>, missing_closing_characters: &mut String) @@ -448,148 +602,322 @@ fn parse_var_function<'i, 't>(input: &mut Parser<'i, 't>, /// Add one custom property declaration to a map, unless another with the same /// name was already there. -pub fn cascade<'a>(custom_properties: &mut Option>>, - inherited: &'a Option>, - seen: &mut HashSet<&'a Name>, - name: &'a Name, - specified_value: DeclaredValue<'a, Box>) { +/// +/// `inherited_computed(name)` should return the computed value for inherited +/// typed custom properties and None otherwise. +/// +/// `handle_keyword(name, keyword)` should return `true` in the case where, if +/// the property with name `name` is declared with CSS-wide keyword `keyword`, +/// the property should inherit. +pub fn cascade<'a, F, G>( + custom_properties: &mut Option>>, + inherited: Option<&'a CustomPropertiesMap>, + inherited_computed: &F, + handle_keyword: G, + seen: &mut HashSet<&'a Name>, + name: &'a Name, + specified_value: DeclaredValue<'a, Box> +) +where F: Fn(&'a Name) -> Option<&'a properties_and_values::ComputedValue>, + G: Fn(&'a Name, CSSWideKeyword) -> bool, +{ let was_already_present = !seen.insert(name); if was_already_present { return; } - let map = match *custom_properties { - Some(ref mut map) => map, - None => { - let mut map = OrderedMap::new(); - if let Some(ref inherited) = *inherited { - for name in &inherited.index { - let inherited_value = inherited.get(name).unwrap(); - map.insert(name, BorrowedSpecifiedValue { - css: &inherited_value.css, - first_token_type: inherited_value.first_token_type, - last_token_type: inherited_value.last_token_type, - references: None - }) + #[inline] + fn inherit<'a, F>( + map: &mut OrderedMap<&'a Name, BorrowedSpecifiedValue<'a>>, + get_computed: F, + name: &'a Name, + value: &'a ComputedValue, + ) + where F: Fn(&'a Name) -> Option<&'a properties_and_values::ComputedValue>, + { + let extra = + get_computed(name) + .map(BorrowedExtraData::Precomputed) + .unwrap_or(BorrowedExtraData::InheritedUntyped); + let borrowed = BorrowedSpecifiedValue { + token_stream: value, + references: None, + extra: extra, + }; + map.insert(name, borrowed); + } + + if let None = *custom_properties { + let mut map = OrderedMap::new(); + if let Some(ref inherited) = inherited { + for name in &inherited.index { + let inherited_value = inherited.get(name).unwrap(); + if handle_keyword(name, CSSWideKeyword::Unset) { + // We should inherit. + inherit(&mut map, inherited_computed, name, inherited_value); } } - *custom_properties = Some(map); - custom_properties.as_mut().unwrap() } - }; + *custom_properties = Some(map); + } + + let mut map = custom_properties.as_mut().unwrap(); + match specified_value { DeclaredValue::Value(ref specified_value) => { 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!(), - DeclaredValue::CSSWideKeyword(keyword) => match keyword { - CSSWideKeyword::Initial => { + DeclaredValue::CSSWideKeyword(keyword) => { + if handle_keyword(name, keyword) { + // We should inherit. + // The inherited value is what we already have, if we are an + // inherited custom property (whence we initialize + // *custom_properties above). But we might not be! + if !map.get(&name).is_some() { + let inherited_value = + inherited + .as_ref() + .and_then(|inherited| inherited.get(name)); + if let Some(inherited_value) = inherited_value { + inherit(&mut map, inherited_computed, name, inherited_value); + } + } + } else { + // We should use the initial value. Remove the value we + // inherited, if any. map.remove(&name); } - CSSWideKeyword::Unset | // Custom properties are inherited by default. - CSSWideKeyword::Inherit => {} // The inherited value is what we already have. } } } -/// Returns the final map of applicable custom properties. -/// -/// If there was any specified property, we've created a new map and now we need -/// to remove any potential cycles, and wrap it in an arc. -/// -/// Otherwise, just use the inherited custom properties map. -pub fn finish_cascade(specified_values_map: Option>, - inherited: &Option>) - -> Option> { - if let Some(mut map) = specified_values_map { - remove_cycles(&mut map); - Some(Arc::new(substitute_all(map, inherited))) - } else { - inherited.clone() + +/// Replace `var()` functions for all custom properties. +pub fn substitute_all( + specified_values_map: &OrderedMap<&Name, BorrowedSpecifiedValue>, + to_substitute: &Option>, + custom_properties_map: &mut CustomPropertiesMap, + mut compute: &mut C, + mut handle_invalid: &mut H, +) +where C: FnMut(&Name, ComputedValue) -> Result, + H: FnMut(&Name) -> Result, +{ + let mut invalid = HashSet::new(); + for (&name, value) in specified_values_map.iter() { + if !to_substitute.as_ref().map(|x| x.contains(name)).unwrap_or(true) { + continue + } + // If this value is invalid at computed-time it won’t be inserted in + // computed_values_map. Nothing else to do. + let _ = substitute_one( + name, value, specified_values_map, None, custom_properties_map, + &mut invalid, &mut compute, &mut handle_invalid); } } -/// https://drafts.csswg.org/css-variables/#cycles +/// Identify and remove cyclical variable declarations, identify if font-size is +/// involved in a cycle, and identify those variables that must be computed +/// before font-size. /// -/// The initial value of a custom property is represented by this property not -/// being in the map. -fn remove_cycles(map: &mut OrderedMap<&Name, BorrowedSpecifiedValue>) { - let mut to_remove = HashSet::new(); - { - let mut visited = HashSet::new(); - let mut stack = Vec::new(); - for name in &map.index { - walk(map, name, &mut stack, &mut visited, &mut to_remove); - - fn walk<'a>(map: &OrderedMap<&'a Name, BorrowedSpecifiedValue<'a>>, - name: &'a Name, - stack: &mut Vec<&'a Name>, - visited: &mut HashSet<&'a Name>, - to_remove: &mut HashSet) { - let already_visited_before = !visited.insert(name); - if already_visited_before { +/// specified[p] is removed if p is involved in a cycle. +/// +/// Finally, we return whether font-size is involved in a cycle as well as a set +/// of those variables to compute before computing early properties, including +/// those variables (possibly transitively) referenced by the declaration of +/// font-size. We count references in fallbacks (as does cycle detection). +/// +/// For the purposes of cycle detection, we imagine font-size as another +/// variable, and create an edge in the dependency graph from a variable to +/// font-size if the variable is in possibly_font_size_dependent and if we parse +/// a dimension token inside. The caller should put those variables in +/// possibly_font_size_dependent that have as a possible syntax at least one of +/// , , or (syntaxes whose +/// computation might involve computing absolute lengths from relative lengths). +pub fn compute_ordering( + specified: &mut OrderedMap<&Name, BorrowedSpecifiedValue>, + referenced_by_font_size: &HashSet, + referenced_by_others: &HashSet, + possibly_font_size_dependent: &HashSet, +) -> (bool, HashSet, HashSet) { + fn walk<'a>( + specified: &HashMap<&'a Name, BorrowedSpecifiedValue<'a>>, + // None if we aren't descending down font-size. + // Some((set, might_require_computation)) if we are descending down + // font-size; might_require_computation is set to true if we are looking + // at a dependency of a custom property that might require computation. + // Determine to be cyclical if the set contains us and we have an em in + // our declaration, or if one of our descendants in the tree (for which + // might_require_computation is set to true) contains an em. + font_size_data: Option<(&HashSet, bool)>, + name: &'a Name, + stack: &mut Vec<&'a Name>, + visited: &mut HashSet<&'a Name>, + cyclical: &mut HashSet, + font_size_cyclical: &mut bool, + ) { + let already_visited_before = !visited.insert(name); + if already_visited_before { + return + } + + let (declaration, references) = { + if let Some(declaration) = specified.get(name) { + if let Some(references) = declaration.references { + (declaration, references) + } else { + // No variables referenced. return } - if let Some(value) = map.get(&name) { - if let Some(references) = value.references { - stack.push(name); - for next in references { - if let Some(position) = stack.iter().position(|&x| x == next) { - // Found a cycle - for &in_cycle in &stack[position..] { - to_remove.insert(in_cycle.clone()); - } - } else { - walk(map, next, stack, visited, to_remove); + } else { + // Invalid variable reference--will handle later. + return + } + }; + + stack.push(name); + + // Recurse into variable references. + for next in references { + if let Some(position) = stack.iter().position(|&x| x == next) { + // Found a cycle! + for in_cycle in &stack[position..] { + cyclical.insert((*in_cycle).clone()); + } + } else { + let font_size_data = match font_size_data { + Some((possibly_font_size_dependent, false)) + if possibly_font_size_dependent.contains(name) => { + Some((possibly_font_size_dependent, true)) + }, + _ => font_size_data, + }; + walk(specified, font_size_data, next, stack, visited, cyclical, + font_size_cyclical); + } + } + + // If this is a registered custom property whose computation + // calculation requires font-size, recurse into the + // references for font-size. + let might_cause_font_size_cycle = { + if let Some((possibly_font_size_dependent, might_require_computation)) = font_size_data { + might_require_computation || possibly_font_size_dependent.contains(name) + } else { + false + } + }; + if might_cause_font_size_cycle { + fn detect_ems<'i, 'tt>(input: &mut Parser<'i, 'tt>) + -> Result> { + while !input.is_exhausted() { + let token = input.next()?.clone(); + // We have to descend into functions. + match token { + Token::Function(_) => { + if input.parse_nested_block(detect_ems).unwrap() { + return Ok(true) + } + }, + Token::Dimension { ref unit, .. } => { + if unit.eq_ignore_ascii_case("em") || + unit.eq_ignore_ascii_case("rem") { + return Ok(true) } } - stack.pop(); + _ => () } } + Ok(false) + } + + let mut input = ParserInput::new(&declaration.css); + let mut input = Parser::new(&mut input); + + if detect_ems(&mut input).unwrap() { + // Found a cycle involving font-size! + *font_size_cyclical = true; + for in_cycle in stack.iter() { + cyclical.insert((*in_cycle).clone()); + } } } + stack.pop(); } - for name in to_remove { - map.remove(&name); - } -} -/// Replace `var()` functions for all custom properties. -fn substitute_all(specified_values_map: OrderedMap<&Name, BorrowedSpecifiedValue>, - inherited: &Option>) - -> CustomPropertiesMap { - let mut custom_properties_map = CustomPropertiesMap::new(); - let mut invalid = HashSet::new(); - for name in &specified_values_map.index { - let value = specified_values_map.get(name).unwrap(); + // Identify cycles. - // If this value is invalid at computed-time it won’t be inserted in computed_values_map. - // Nothing else to do. - let _ = substitute_one( - name, value, &specified_values_map, inherited, None, - &mut custom_properties_map, &mut invalid); + let mut cyclical = HashSet::new(); + let mut font_size_cyclical = false; + let mut visited = HashSet::new(); + // We recursively follow variable references in declarations, and push + // the current variable to the stack, so that the declaration for + // stack[i] has a variable reference to stack[i+1]. So if we see a + // reference to a variable which already appears on the stack, we know + // that we have a cycle. + let mut stack = Vec::new(); + + // Check variables referenced by font-size first, to identify if there is a + // cycle involving font-size. + // Then, check variables referenced by other properties which we need to + // check (i.e. "early" properties). + // Then we can just take visited to get those variables which are + // transitively referenced by it. + for name in referenced_by_font_size { + walk(&specified.values, Some((possibly_font_size_dependent, false)), + &name, &mut stack, &mut visited, &mut cyclical, + &mut font_size_cyclical); + } + for name in referenced_by_others { + walk(&specified.values, None, &name, &mut stack, &mut visited, + &mut cyclical, &mut font_size_cyclical); } + let mut dependencies = visited.clone(); + let dependencies: HashSet = dependencies.drain().map(|x| x.clone()).collect(); - custom_properties_map + for name in specified.values.keys() { + walk(&specified.values, None, name, &mut stack, &mut visited, + &mut cyclical, &mut font_size_cyclical); + } + + // If we wanted dependencies to be completely accurate, we would + // remove z if x depends on y depends on z yet y is involved in a cycle. + // But we don't really need that; we just want to include all those + // properties that are depended on by early properties. Properties that + // are in dependencies can't depend on those early properties (the only + // such dependency we can have here is one on font-size) because in that + // case we would have detected the cycle; by resetting to the initial + // value (which is computationally independent) we no longer have the + // cycle. + // + // Really, what dependencies describe is the custom properties that we can + // compute early and those that we must. + + (font_size_cyclical, dependencies, cyclical) } /// Replace `var()` functions for one custom property. /// Also recursively record results for other custom properties referenced by `var()` functions. /// Return `Err(())` for invalid at computed time. /// or `Ok(last_token_type that was pushed to partial_computed_value)` otherwise. -fn substitute_one(name: &Name, - specified_value: &BorrowedSpecifiedValue, - specified_values_map: &OrderedMap<&Name, BorrowedSpecifiedValue>, - inherited: &Option>, - partial_computed_value: Option<&mut ComputedValue>, - custom_properties_map: &mut CustomPropertiesMap, - invalid: &mut HashSet) - -> Result { +fn substitute_one( + name: &Name, + specified_value: &BorrowedSpecifiedValue, + specified_values_map: &OrderedMap<&Name, BorrowedSpecifiedValue>, + mut partial_computed_value: Option<&mut ComputedValue>, + custom_properties_map: &mut CustomPropertiesMap, + invalid: &mut HashSet, + compute: &mut C, + handle_invalid: &mut H, +) -> Result +where C: FnMut(&Name, ComputedValue) -> Result, + H: FnMut(&Name) -> Result, +{ if let Some(computed_value) = custom_properties_map.get(&name) { if let Some(partial_computed_value) = partial_computed_value { partial_computed_value.push_variable(computed_value) @@ -600,7 +928,7 @@ fn substitute_one(name: &Name, if invalid.contains(name) { return Err(()); } - let computed_value = if specified_value.references.map(|set| set.is_empty()) == Some(false) { + let computed_value = if !specified_value.references.map(|set| set.is_empty()).unwrap_or(true) { let mut partial_computed_value = ComputedValue::empty(); let mut input = ParserInput::new(&specified_value.css); let mut input = Parser::new(&mut input); @@ -609,8 +937,9 @@ fn substitute_one(name: &Name, &mut input, &mut position, &mut partial_computed_value, &mut |name, partial_computed_value| { if let Some(other_specified_value) = specified_values_map.get(&name) { - substitute_one(name, other_specified_value, specified_values_map, inherited, - Some(partial_computed_value), custom_properties_map, invalid) + substitute_one(name, other_specified_value, specified_values_map, + Some(partial_computed_value), custom_properties_map, invalid, + compute, handle_invalid) } else { Err(()) } @@ -618,30 +947,36 @@ fn substitute_one(name: &Name, ); if let Ok(last_token_type) = result { partial_computed_value.push_from(position, &input, last_token_type); - partial_computed_value + Ok(partial_computed_value) } else { - // Invalid at computed-value time. Use the inherited value. - if let Some(inherited_value) = inherited.as_ref().and_then(|i| i.values.get(name)) { - inherited_value.clone() - } else { - invalid.insert(name.clone()); - return Err(()) - } + Err(()) } } else { // The specified value contains no var() reference - ComputedValue { + Ok(ComputedValue { css: specified_value.css.to_owned(), first_token_type: specified_value.first_token_type, last_token_type: specified_value.last_token_type, - } + }) }; - if let Some(partial_computed_value) = partial_computed_value { - partial_computed_value.push_variable(&computed_value) - } - let last_token_type = computed_value.last_token_type; - custom_properties_map.insert(name.clone(), computed_value); - Ok(last_token_type) + + computed_value + .and_then(|x| compute(name, x)) + .or_else(|()| { + let result = handle_invalid(name); + if let Err(()) = result { + invalid.insert(name.clone()); + } + result + }) + .and_then(|x| { + if let Some(ref mut partial_computed_value) = partial_computed_value { + partial_computed_value.push_variable(&x) + } + let last_token_type = x.last_token_type; + custom_properties_map.insert(name.clone(), x); + Ok(last_token_type) + }) } /// Replace `var()` functions in an arbitrary bit of input. @@ -693,6 +1028,8 @@ fn substitute_block<'i, 't, F>(input: &mut Parser<'i, 't>, // FIXME: Add a specialized method to cssparser to do this with less work. while let Ok(_) = input.next() {} } else { + // Try to substitute any variable references in the + // fallback. input.expect_comma()?; let after_comma = input.state(); let first_token_type = input.next_including_whitespace_and_comments() @@ -733,10 +1070,11 @@ fn substitute_block<'i, 't, F>(input: &mut Parser<'i, 't>, Ok(last_token_type) } -/// Replace `var()` functions for a non-custom property. -/// Return `Err(())` for invalid at computed time. +/// Replace `var()` functions for a non-custom property declaration. +/// Return `Err(..)` if the declaration should be invalid at computed-value time +/// (that is, if resolution fails). pub fn substitute<'i>(input: &'i str, first_token_type: TokenSerializationType, - computed_values_map: &Option>) + computed_values_map: Option<&CustomPropertiesMap>) -> Result> { let mut substituted = ComputedValue::empty(); let mut input = ParserInput::new(input); @@ -744,7 +1082,7 @@ pub fn substitute<'i>(input: &'i str, first_token_type: TokenSerializationType, let mut position = (input.position(), first_token_type); let last_token_type = substitute_block( &mut input, &mut position, &mut substituted, &mut |name, substituted| { - if let Some(value) = computed_values_map.as_ref().and_then(|map| map.get(name)) { + if let Some(value) = computed_values_map.and_then(|map| map.get(name)) { substituted.push_variable(value); Ok(value.last_token_type) } else { 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/declaration_block.rs b/components/style/properties/declaration_block.rs index 07bc5eeab1dd..1200e223709a 100644 --- a/components/style/properties/declaration_block.rs +++ b/components/style/properties/declaration_block.rs @@ -541,7 +541,7 @@ impl PropertyDeclarationBlock { Some(ref computed_values)) => unparsed .substitute_variables( id, - &computed_values.custom_properties(), + computed_values.get_custom_properties(), QuirksMode::NoQuirks, ) .to_css(dest), diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index 6f1b0d2a74ff..ed66770ad56a 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -11,7 +11,7 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> use app_units::Au; -use custom_properties::CustomPropertiesMap; +use properties_and_values::CustomPropertiesMap; use gecko_bindings::bindings; % for style_struct in data.style_structs: use gecko_bindings::structs::${style_struct.gecko_ffi_name}; @@ -324,7 +324,7 @@ impl ComputedValuesInner { } /// Gets a reference to the custom properties map (if one exists). - pub fn get_custom_properties(&self) -> Option<<&::custom_properties::CustomPropertiesMap> { + pub fn get_custom_properties(&self) -> Option<<&CustomPropertiesMap> { self.custom_properties.as_ref().map(|x| &**x) } diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index a97b8e26042d..a76979069f2b 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -598,9 +598,13 @@ impl AnimationValue { } }, PropertyDeclaration::WithVariables(id, ref unparsed) => { - let custom_props = context.style().custom_properties(); - let substituted = unparsed.substitute_variables(id, &custom_props, context.quirks_mode); - AnimationValue::from_declaration(&substituted, context, initial) + let substituted = { + let custom_props = context.style().get_custom_properties(); + unparsed.substitute_variables(id, custom_props, context.quirks_mode) + }; + AnimationValue::from_declaration( + &substituted, context, initial + ) }, _ => None // non animatable properties will get included because of shorthands. ignore. } 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 { diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index f64117e1bfc2..48ff1369cc08 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -23,6 +23,7 @@ use cssparser::ParserInput; #[cfg(feature = "servo")] use euclid::SideOffsets2D; use computed_values; use context::QuirksMode; +use custom_properties::OrderedMap; use error_reporting::NullReporter; use font_metrics::FontMetricsProvider; #[cfg(feature = "gecko")] use gecko_bindings::bindings; @@ -31,12 +32,13 @@ use font_metrics::FontMetricsProvider; use logical_geometry::WritingMode; use media_queries::Device; use parser::ParserContext; +use properties_and_values::{CustomPropertiesMap, RegisteredPropertySet}; use properties::animated_properties::AnimatableLonghand; #[cfg(feature = "gecko")] use properties::longhands::system_font::SystemFont; use selector_parser::PseudoElement; use selectors::parser::SelectorParseError; #[cfg(feature = "servo")] use servo_config::prefs::PREFS; -use shared_lock::StylesheetGuards; +use shared_lock::{Locked, StylesheetGuards}; use style_traits::{PARSING_MODE_DEFAULT, HasViewportPercentage, ToCss, ParseError}; use style_traits::{PropertyDeclarationParseError, StyleParseError, ValueParseError}; use stylesheets::{CssRuleType, MallocSizeOf, MallocSizeOfFn, Origin, UrlExtraData}; @@ -791,7 +793,7 @@ pub enum DeclaredValueOwned { Value(T), /// An unparsed value that contains `var()` functions. WithVariables(Arc), - /// An CSS-wide keyword. + /// A CSS-wide keyword. CSSWideKeyword(CSSWideKeyword), } @@ -821,10 +823,10 @@ pub struct UnparsedValue { impl UnparsedValue { fn substitute_variables(&self, longhand_id: LonghandId, - custom_properties: &Option>, + custom_properties: Option<<&CustomPropertiesMap>, quirks_mode: QuirksMode) -> PropertyDeclaration { - ::custom_properties::substitute(&self.css, self.first_token_type, custom_properties) + ::custom_properties::substitute(&self.css, self.first_token_type, custom_properties.map(|x| &**x)) .ok() .and_then(|css| { // As of this writing, only the base URL is used for property values: @@ -1299,7 +1301,9 @@ pub enum PropertyDeclaration { /// An unparsed value that contains `var()` functions. WithVariables(LonghandId, Arc), /// A custom property declaration, with the property name and the declared - /// value. + /// value. Custom properties should not use the + /// DeclaredValueOwned::WithVariables variant (see the assert in + /// ::custom_properties::cascade). Custom(::custom_properties::Name, DeclaredValueOwned>), } @@ -1973,7 +1977,7 @@ pub struct ComputedValuesInner { % for style_struct in data.active_style_structs(): ${style_struct.ident}: Arc, % endfor - custom_properties: Option>, + custom_properties: Option>, /// The writing mode of this computed values struct. pub writing_mode: WritingMode, @@ -2019,7 +2023,7 @@ impl ComputedValues { _: &Device, _: Option<<&ComputedValues>, _: Option<<&PseudoElement>, - custom_properties: Option>, + custom_properties: Option>, writing_mode: WritingMode, font_size_keyword: Option<(longhands::font_size::KeywordSize, f32)>, flags: ComputedValueFlags, @@ -2052,7 +2056,7 @@ impl ComputedValues { impl ComputedValuesInner { /// Construct a `ComputedValuesInner` instance. pub fn new( - custom_properties: Option>, + custom_properties: Option>, writing_mode: WritingMode, font_size_keyword: Option<(longhands::font_size::KeywordSize, f32)>, flags: ComputedValueFlags, @@ -2148,7 +2152,7 @@ impl ComputedValuesInner { // Aah! The << in the return type below is not valid syntax, but we must // escape < that way for Mako. /// Gets a reference to the custom properties map (if one exists). - pub fn get_custom_properties(&self) -> Option<<&::custom_properties::CustomPropertiesMap> { + pub fn get_custom_properties(&self) -> Option<<&CustomPropertiesMap> { self.custom_properties.as_ref().map(|x| &**x) } @@ -2156,7 +2160,7 @@ impl ComputedValuesInner { /// /// Cloning the Arc here is fine because it only happens in the case where /// we have custom properties, and those are both rare and expensive. - pub fn custom_properties(&self) -> Option> { + pub fn custom_properties(&self) -> Option> { self.custom_properties.clone() } @@ -2568,7 +2572,7 @@ pub struct StyleBuilder<'a> { /// node. rules: Option, - custom_properties: Option>, + custom_properties: Option>, /// The pseudo-element this style will represent. pseudo: Option<<&'a PseudoElement>, @@ -2599,7 +2603,7 @@ impl<'a> StyleBuilder<'a> { pseudo: Option<<&'a PseudoElement>, cascade_flags: CascadeFlags, rules: Option, - custom_properties: Option>, + custom_properties: Option>, writing_mode: WritingMode, font_size_keyword: Option<(longhands::font_size::KeywordSize, f32)>, flags: ComputedValueFlags, @@ -2859,12 +2863,14 @@ impl<'a> StyleBuilder<'a> { ) } - /// Get the custom properties map if necessary. - /// - /// Cloning the Arc here is fine because it only happens in the case where - /// we have custom properties, and those are both rare and expensive. - fn custom_properties(&self) -> Option> { - self.custom_properties.clone() + /// Gets a reference to the custom properties map (if one exists). + pub fn get_custom_properties(&self) -> Option<<&CustomPropertiesMap> { + self.custom_properties.as_ref().map(|x| &**x) + } + + /// Gets a reference to the parent's custom properties map (if one exists). + pub fn get_parent_custom_properties(&self) -> Option<<&CustomPropertiesMap> { + self.inherited_style.custom_properties.as_ref().map(|x| &**x) } /// Access to various information about our inherited styles. We don't @@ -3023,7 +3029,8 @@ pub fn cascade( cascade_info: Option<<&mut CascadeInfo>, font_metrics_provider: &FontMetricsProvider, flags: CascadeFlags, - quirks_mode: QuirksMode + quirks_mode: QuirksMode, + registered_property_set: &Locked, ) -> Arc { debug_assert_eq!(parent_style.is_some(), parent_style_ignoring_first_line.is_some()); #[cfg(feature = "gecko")] @@ -3083,9 +3090,338 @@ pub fn cascade( font_metrics_provider, flags, quirks_mode, + registered_property_set.read_with(guards.registered_property_set), ) } +/// Compute a fully resolved custom property declaration. Note that we are +/// turning a custom_properties::ComputedValue into a +/// custom_properties::ComputedValue and possibly a +/// properties_and_values::ComputedValue: the difference afterwards is that if +/// this is a typed custom property we have computed out things like em units. +/// Also return whether or not this property inherits. +fn compute_resolved_custom_property( + registered_property_set: &RegisteredPropertySet, + context: &computed::Context, + url_data: &UrlExtraData, + name: &::custom_properties::Name, + value: ::custom_properties::ComputedValue, +) -> Result<(::custom_properties::ComputedValue, + Option<::properties_and_values::ComputedValue>, + bool), + ()> { + let registration = match registered_property_set.get(name) { + None => return Ok((value, None, true)), + Some(registration) => registration + }; + let specified = { + let reporter = NullReporter; + let parser_context = ParserContext::new( + Origin::Author, + url_data, + &reporter, + /* rule_type */ None, + PARSING_MODE_DEFAULT, + context.quirks_mode, + ); + let mut input = ParserInput::new(&value.css); + let mut input = Parser::new(&mut input); + match registration.syntax.parse(&parser_context, &mut input) { + Ok(x) => x, + Err(_) => return Err(()), + } + }; + specified + .to_computed_value(context) + .map(|x| ((&x).into(), Some(x), registration.inherits)) +} + +/// Substitute `var()` functions in the declarations for the custom properties +/// specified in `to_substitute`. If `to_substitute` is `None`, substitute for +/// all custom properties. Computed values are added to `computed`. +/// +/// This function is called twice: before and after we compute early properties. +fn substitute_all( + context: &computed::Context, + registered_property_set: &RegisteredPropertySet, + specified: &OrderedMap<<&::custom_properties::Name, + ::custom_properties::BorrowedSpecifiedValue>, + inherited: &Option>, + to_substitute: &Option>, + computed: &mut CustomPropertiesMap, +) { + let mut computed_to_insert_typed = Vec::new(); + let mut invalid_to_insert_typed = Vec::new(); + let mut has_uninherited = false; + + ::custom_properties::substitute_all( + specified, + to_substitute, + &mut **computed, + /* compute */ &mut |name, value| { + use ::custom_properties::BorrowedExtraData; + let specified = specified.get(&name).unwrap(); + match specified.extra { + BorrowedExtraData::Specified(ref url_data) => { + // This specified value came straight from a declaration. + // We should try to compute with the `url_data` associated + // with the declaration. + compute_resolved_custom_property( + registered_property_set, + context, + url_data, + name, + value + ).map(|(untyped, maybe_typed, inherits)| { + has_uninherited = has_uninherited || !inherits; + if let Some(typed) = maybe_typed { + computed_to_insert_typed.push((name.clone(), typed)); + } + untyped + }) + }, + BorrowedExtraData::Precomputed(ref computed_value) => { + // This specified value came from an animation or an + // inherited typed custom property. We don't need to + // compute! + if let Some(ref registration) = registered_property_set.get(name) { + has_uninherited = has_uninherited || !registration.inherits; + computed_to_insert_typed.push((name.clone(), (*computed_value).clone())); + } + Ok(value) + }, + BorrowedExtraData::InheritedUntyped => { + // The specified value was inherited from an untyped custom + // property. We shouldn't insert any typed value here. + Ok(value) + }, + } + }, + /* handle_invalid */ &mut |name| { + registered_property_set + .get(name) + .and_then(|registration| { + if !registration.inherits { + registration.initial_value.as_ref() + .map(|x| { + let typed = + x.to_computed_value(context) + .expect("The initial value should never fail to compute."); + let untyped = (&typed).into(); + invalid_to_insert_typed.push((name.clone(), typed)); + untyped + }) + } else { + None + } + }) + .or_else(|| inherited.as_ref().and_then(|x| x.get(name).cloned())) + .ok_or(()) + } + ); + + for (name, typed) in computed_to_insert_typed { + computed.insert_typed(&name, None, typed); + } + for (name, typed) in invalid_to_insert_typed { + computed.insert_typed(&name, None, typed); + } + if has_uninherited { + computed.set_has_uninherited(); + } +} + +/// Determine and then compute those custom properties we want to compute before +/// early custom properties (might avoid doing any work if we haven't declared +/// any properties and there are no uninherited custom properties). +/// Returns the custom properties' specified nad computed values, as well as +/// whether or not the declaration for font-size is involved in a cycle. +fn compute_early_custom_properties<'a, 'b: 'a, I>( + context: &computed::Context, + inherited: &'a Option>, + declarations: I, + registered_property_set: &RegisteredPropertySet, +) -> ( + Option>>, + Option>, + bool +) +where I: Iterator +{ + // Will be populated by ::custom_properties::cascade if any of the + // specified custom properties end up being used. + let mut specified = None; + // parse_declaration_value_block wants Options. + let mut referenced_by_font_size = Some(HashSet::new()); + let mut referenced_by_other_early = Some(HashSet::new()); + let mut possibly_font_size_dependent = HashSet::new(); + let mut seen_custom = HashSet::new(); + // Don't really need this, but parse_declaration_value_block expects it. + let mut missing_closing_characters = String::new(); + for (declaration, _cascade_level) in declarations { + match *declaration { + PropertyDeclaration::Custom(ref name, ref value) => { + ::custom_properties::cascade( + &mut specified, + inherited.as_ref().map(|x| &***x), + /* inherited_computed */ &|name| { + inherited.as_ref().and_then(|inherited| inherited.get_typed(name)) + }, + /* handle_keyword */ |name, keyword| { + // We return true if we should inherit and false + // otherwise. + match keyword { + CSSWideKeyword::Initial => false, + CSSWideKeyword::Inherit => true, + CSSWideKeyword::Unset => { + if let Some(registration) = registered_property_set.get(name) { + registration.inherits + } else { + true + } + }, + } + }, + &mut seen_custom, + name, + value.borrow() + ); + if let Some(registration) = registered_property_set.get(name) { + if let ::properties_and_values::Syntax::Disjunction(ref terms) = registration.syntax { + use ::properties_and_values::{Term, Type}; + if terms.iter().any(|&Term { ref typ, .. }| { + match *typ { + Type::Length | Type::LengthPercentage | + Type::TransformList => true, + _ => false, + } + }) { + possibly_font_size_dependent.insert(name.clone()); + } + } + } + }, + PropertyDeclaration::WithVariables(id, ref value) if id.is_early_property() => { + let mut referenced_by = if id == LonghandId::FontSize { + &mut referenced_by_font_size + } else { + &mut referenced_by_other_early + }; + let mut input = ParserInput::new(&value.css); + let mut input = Parser::new(&mut input); + // We're just going to use parse_declaration_value_block to grab + // references. + let _ = ::custom_properties::parse_declaration_value_block( + &mut input, + &mut referenced_by, + &mut missing_closing_characters, + ); + }, + _ => (), + } + } + + let referenced_by_font_size = referenced_by_font_size.unwrap(); + let referenced_by_other_early = referenced_by_other_early.unwrap(); + + // Remove cycles and compute those custom properties depended on by early + // properties. + let (computed_early, font_size_cyclical) = { + if let Some(ref mut specified) = specified { + // Something was specified. + + let (font_size_cyclical, dependencies, cyclical) = { + ::custom_properties::compute_ordering( + specified, + &referenced_by_font_size, + &referenced_by_other_early, + &possibly_font_size_dependent, + ) + }; + + // Note that inherited properties will have already been inserted + // into `specified` by ::custom_properties::cascade. + // If a property isn't specified and is either + // a) uninherited or has no inherited value, and + // b) has an initial value, + // then we should set it to its initial value. + let mut computed = { + let has_value = specified.iter().map(|(name, _)| name.clone()).collect(); + registered_property_set.initial_values_except(context, Some(&has_value)) + }; + + for name in cyclical { + specified.remove(&name); + // "If there is a cycle in the dependency graph, all the custom + // properties in the cycle must compute to their initial value + // (which is a guaranteed-invalid value)." + // -- https://drafts.csswg.org/css-variables/#cycles + let initial_typed_value = + registered_property_set + .get(&name) + .and_then(|registration| registration.initial_value.clone()) + .map(|initial_value| initial_value.to_computed_value(context).unwrap()); + if let Some(initial_typed_value) = initial_typed_value { + let initial_untyped_value = (&initial_typed_value).into(); + computed.insert_typed(&name, Some(initial_untyped_value), initial_typed_value); + } + } + + substitute_all( + context, + &*registered_property_set, + specified, + inherited, + /* to_substitute */ &Some(dependencies), + &mut computed + ); + + (Some(Arc::new(computed)), font_size_cyclical) + } else if let &Some(ref inherited) = inherited { + // Nothing specified, but we did inherit something. + + if inherited.has_uninherited() { + // There were uninherited custom properties set to something + // other than their initial value, so we have to create a new + // CustomPropertiesMap with them removed. + let mut computed = CustomPropertiesMap::new(); + for (name, value) in inherited.iter() { + let registration = registered_property_set.get(name); + if registration.map(|x| x.inherits).unwrap_or(true) { + // This property inherits; just clone it over. + computed.insert(name.clone(), value.clone()); + if let Some(typed_value) = inherited.get_typed(name) { + computed.insert_typed(name, None, typed_value.clone()); + } + } else { + // The property doesn't inherit; set it to its initial + // value. + if let Some(ref initial) = registration.and_then(|x| x.initial_value.as_ref()) { + let typed_value = initial.clone().to_computed_value(context).unwrap(); + let untyped_value = (&typed_value).into(); + computed.insert_typed(name, Some(untyped_value), typed_value); + } + } + } + (Some(Arc::new(computed)), false) + } else { + // There weren't any uninherited custom properties set to + // something other than their initial value, so we can just + // clone the Arc, which should be cheap! + (Some((*inherited).clone()), false) + } + } else { + // There were no inherited or specified values. + // If there are any properties with initial values, we should set + // them. + (Some(Arc::new(registered_property_set.initial_values_except(context, None))), false) + } + }; + + (specified, computed_early, font_size_cyclical) +} + /// NOTE: This function expects the declaration with more priority to appear /// first. #[allow(unused_mut)] // conditionally compiled code for "position" @@ -3102,6 +3438,7 @@ pub fn apply_declarations<'a, F, I>( font_metrics_provider: &FontMetricsProvider, flags: CascadeFlags, quirks_mode: QuirksMode, + registered_property_set: &RegisteredPropertySet, ) -> Arc where F: Fn() -> I, @@ -3125,21 +3462,6 @@ where } }; - let inherited_custom_properties = inherited_style.custom_properties(); - let mut custom_properties = None; - let mut seen_custom = HashSet::new(); - for (declaration, _cascade_level) in iter_declarations() { - if let PropertyDeclaration::Custom(ref name, ref value) = *declaration { - ::custom_properties::cascade( - &mut custom_properties, &inherited_custom_properties, - &mut seen_custom, name, value.borrow()); - } - } - - let custom_properties = - ::custom_properties::finish_cascade( - custom_properties, &inherited_custom_properties); - let mut context = computed::Context { is_root_element: flags.contains(IS_ROOT_ELEMENT), // We'd really like to own the rules here to avoid refcount traffic, but @@ -3152,7 +3474,9 @@ where pseudo, flags, Some(rules.clone()), - custom_properties, + // We need the context to finish computing these; then we'll fill + // them in. + /* custom_properties */ None, WritingMode::empty(), inherited_style.font_computation_data.font_size_keyword, ComputedValueFlags::empty(), @@ -3165,6 +3489,24 @@ where for_smil_animation: false, }; + let inherited_custom_properties = inherited_style.custom_properties(); + let ( + specified_custom_properties, + mut computed_custom_properties, + font_size_cyclical + ) = { + compute_early_custom_properties( + &context, + &inherited_custom_properties, + iter_declarations(), + registered_property_set, + ) + }; + + // Should be enough to resolve early properties. Need to fill in the other + // ones later. + context.builder.custom_properties = computed_custom_properties; + let ignore_colors = !device.use_document_colors(); let default_background_color_decl = if ignore_colors { let color = device.default_background_color(); @@ -3192,12 +3534,39 @@ where let mut font_size = None; let mut font_family = None; % endif + %if category_to_cascade_now == "other": + // Now we can compute the other custom properties that + // weren't depended on by early properties and that might have + // depended on them. + if context.builder.custom_properties.is_some() && specified_custom_properties.is_some() { + // Swap out context.builder.custom_properties temporarily so we + // can update it. (We still need to pass context in to compute + // with). We'll swap it back in at the end! + let mut maybe_arc = None; + mem::swap(&mut maybe_arc, &mut context.builder.custom_properties); + { + let mut arc = maybe_arc.as_mut().unwrap(); + let mut computed_custom_properties = Arc::get_mut(arc).unwrap(); + let specified_custom_properties = specified_custom_properties.as_ref().unwrap(); + substitute_all( + &context, + &*registered_property_set, + specified_custom_properties, + &inherited_custom_properties, + // Compute everybody that isn't yet computed. + /* to_substitute */ &None, + computed_custom_properties + ); + } + mem::swap(&mut maybe_arc, &mut context.builder.custom_properties); + } + % endif for (declaration, cascade_level) in iter_declarations() { let mut declaration = match *declaration { PropertyDeclaration::WithVariables(id, ref unparsed) => { Cow::Owned(unparsed.substitute_variables( id, - &context.builder.custom_properties, + context.builder.custom_properties.as_ref().map(|x| &**x), context.quirks_mode )) } @@ -3209,6 +3578,10 @@ where PropertyDeclarationId::Custom(..) => continue, }; + if font_size_cyclical && longhand_id == LonghandId::FontSize { + continue + } + // Only a few properties are allowed to depend on the visited state // of links. When cascading visited styles, we can save time by // only processing these properties. 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