diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index da1bbb9f149..a1d2ee9d085 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -42,7 +42,7 @@ use selector_parser::PseudoElement; use selectors::parser::SelectorParseErrorKind; #[cfg(feature = "servo")] use servo_config::prefs::PREFS; use shared_lock::StylesheetGuards; -use style_traits::{CssWriter, ParseError, ParsingMode}; +use style_traits::{CssWriter, KeywordsCollectFn, ParseError, ParsingMode}; use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss}; use stylesheets::{CssRuleType, Origin, UrlExtraData}; #[cfg(feature = "servo")] use values::Either; @@ -560,6 +560,25 @@ impl NonCustomPropertyId { ]; SUPPORTED_TYPES[self.0] } + + /// See PropertyId::collect_property_completion_keywords. + fn collect_property_completion_keywords(&self, f: KeywordsCollectFn) { + const COLLECT_FUNCTIONS: [&Fn(KeywordsCollectFn); + ${len(data.longhands) + len(data.shorthands)}] = [ + % for prop in data.longhands: + &<${prop.specified_type()} as SpecifiedValueInfo>::collect_completion_keywords, + % endfor + % for prop in data.shorthands: + % if prop.name == "all": + &|_f| {}, // 'all' accepts no value other than CSS-wide keywords + % else: + &:: + collect_completion_keywords, + % endif + % endfor + ]; + COLLECT_FUNCTIONS[self.0](f); + } } impl From for NonCustomPropertyId { @@ -743,7 +762,8 @@ impl LonghandIdSet { } /// An enum to represent a CSS Wide keyword. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss)] +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, + ToCss)] pub enum CSSWideKeyword { /// The `initial` keyword. Initial, @@ -1711,6 +1731,18 @@ impl PropertyId { }) } + /// Returns non-alias NonCustomPropertyId corresponding to this + /// property id. + fn non_custom_non_alias_id(&self) -> Option { + Some(match *self { + PropertyId::Custom(_) => return None, + PropertyId::Shorthand(id) => id.into(), + PropertyId::Longhand(id) => id.into(), + PropertyId::ShorthandAlias(id, _) => id.into(), + PropertyId::LonghandAlias(id, _) => id.into(), + }) + } + /// Whether the property is enabled for all content regardless of the /// stylesheet it was declared on (that is, in practice only checks prefs). #[inline] @@ -1736,14 +1768,19 @@ impl PropertyId { /// Whether the property supports the given CSS type. /// `ty` should a bitflags of constants in style_traits::CssType. pub fn supports_type(&self, ty: u8) -> bool { - let non_custom_id: NonCustomPropertyId = match *self { - PropertyId::Custom(_) => return false, - PropertyId::Shorthand(id) => id.into(), - PropertyId::Longhand(id) => id.into(), - PropertyId::ShorthandAlias(id, _) => id.into(), - PropertyId::LonghandAlias(id, _) => id.into(), - }; - non_custom_id.supported_types() & ty != 0 + let id = self.non_custom_non_alias_id(); + id.map_or(0, |id| id.supported_types()) & ty != 0 + } + + /// Collect supported starting word of values of this property. + /// + /// See style_traits::SpecifiedValueInfo::collect_completion_keywords for more + /// details. + pub fn collect_property_completion_keywords(&self, f: KeywordsCollectFn) { + if let Some(id) = self.non_custom_non_alias_id() { + id.collect_property_completion_keywords(f); + } + CSSWideKeyword::collect_completion_keywords(f); } } diff --git a/components/style_derive/specified_value_info.rs b/components/style_derive/specified_value_info.rs index be3e00f994b..e5e9ac328da 100644 --- a/components/style_derive/specified_value_info.rs +++ b/components/style_derive/specified_value_info.rs @@ -4,15 +4,21 @@ use cg; use quote::Tokens; -use syn::{Data, DeriveInput, Fields}; +use syn::{Data, DeriveInput, Fields, Type}; use to_css::{CssFieldAttrs, CssInputAttrs, CssVariantAttrs}; pub fn derive(mut input: DeriveInput) -> Tokens { let attrs = cg::parse_input_attrs::(&input); - let mut types_value = quote!(0); - // If the whole value is wrapped in a function, value types of its - // fields should not be propagated. - if attrs.function.is_none() { + let mut types = vec![]; + let mut values = vec![]; + + let input_ident = input.ident; + let input_name = || cg::to_css_identifier(input_ident.as_ref()); + if let Some(function) = attrs.function { + values.push(function.explicit().unwrap_or_else(input_name)); + // If the whole value is wrapped in a function, value types of + // its fields should not be propagated. + } else { let mut where_clause = input.generics.where_clause.take(); for param in input.generics.type_params() { cg::add_predicate( @@ -26,18 +32,55 @@ pub fn derive(mut input: DeriveInput) -> Tokens { Data::Enum(ref e) => { for v in e.variants.iter() { let attrs = cg::parse_variant_attrs::(&v); - if attrs.function.is_none() { - derive_struct_fields(&v.fields, &mut types_value); + if attrs.skip { + continue; + } + if let Some(aliases) = attrs.aliases { + for alias in aliases.split(",") { + values.push(alias.to_string()); + } + } + if let Some(keyword) = attrs.keyword { + values.push(keyword); + continue; + } + let variant_name = || cg::to_css_identifier(v.ident.as_ref()); + if let Some(function) = attrs.function { + values.push(function.explicit().unwrap_or_else(variant_name)); + } else { + if !derive_struct_fields(&v.fields, &mut types, &mut values) { + values.push(variant_name()); + } } } } Data::Struct(ref s) => { - derive_struct_fields(&s.fields, &mut types_value) + if !derive_struct_fields(&s.fields, &mut types, &mut values) { + values.push(input_name()); + } } Data::Union(_) => unreachable!("union is not supported"), } } + let mut types_value = quote!(0); + types_value.append_all(types.iter().map(|ty| quote! { + | <#ty as ::style_traits::SpecifiedValueInfo>::SUPPORTED_TYPES + })); + + let mut nested_collects = quote!(); + nested_collects.append_all(types.iter().map(|ty| quote! { + <#ty as ::style_traits::SpecifiedValueInfo>::collect_completion_keywords(_f); + })); + + let append_values = if values.is_empty() { + quote!() + } else { + let mut value_list = quote!(); + value_list.append_separated(values.iter(), quote!(,)); + quote! { _f(&[#value_list]); } + }; + let name = &input.ident; let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); quote! { @@ -45,24 +88,37 @@ pub fn derive(mut input: DeriveInput) -> Tokens { #where_clause { const SUPPORTED_TYPES: u8 = #types_value; + + fn collect_completion_keywords(_f: &mut FnMut(&[&'static str])) { + #nested_collects + #append_values + } } } } -fn derive_struct_fields(fields: &Fields, supports_body: &mut Tokens) { +/// Derive from the given fields. Return false if the fields is a Unit, +/// true otherwise. +fn derive_struct_fields<'a>( + fields: &'a Fields, + types: &mut Vec<&'a Type>, + values: &mut Vec, +) -> bool { let fields = match *fields { - Fields::Unit => return, + Fields::Unit => return false, Fields::Named(ref fields) => fields.named.iter(), Fields::Unnamed(ref fields) => fields.unnamed.iter(), }; - supports_body.append_all(fields.map(|field| { + types.extend(fields.filter_map(|field| { let attrs = cg::parse_field_attrs::(field); - if attrs.skip { - return quote!(); + if let Some(if_empty) = attrs.if_empty { + values.push(if_empty); } - let ty = &field.ty; - quote! { - | <#ty as ::style_traits::SpecifiedValueInfo>::SUPPORTED_TYPES + if !attrs.skip { + Some(&field.ty) + } else { + None } })); + true } diff --git a/components/style_traits/lib.rs b/components/style_traits/lib.rs index bf7a9b59cd9..8d4e0846cc9 100644 --- a/components/style_traits/lib.rs +++ b/components/style_traits/lib.rs @@ -79,7 +79,7 @@ pub mod values; #[macro_use] pub mod viewport; -pub use specified_value_info::{CssType, SpecifiedValueInfo}; +pub use specified_value_info::{CssType, KeywordsCollectFn, SpecifiedValueInfo}; pub use values::{Comma, CommaWithSpace, CssWriter, OneOrMoreSeparated, Separator, Space, ToCss}; /// The error type for all CSS parsing routines. diff --git a/components/style_traits/specified_value_info.rs b/components/style_traits/specified_value_info.rs index d6bc5011483..55690ebea5c 100644 --- a/components/style_traits/specified_value_info.rs +++ b/components/style_traits/specified_value_info.rs @@ -23,6 +23,9 @@ pub mod CssType { pub const TIMING_FUNCTION: u8 = 1 << 2; } +/// See SpecifiedValueInfo::collect_completion_keywords. +pub type KeywordsCollectFn<'a> = &'a mut FnMut(&[&'static str]); + /// Information of values of a given specified value type. pub trait SpecifiedValueInfo { /// Supported CssTypes by the given value type. @@ -30,6 +33,15 @@ pub trait SpecifiedValueInfo { /// XXX This should be typed CssType when that becomes a bitflags. /// Currently we cannot do so since bitflags cannot be used in constant. const SUPPORTED_TYPES: u8 = 0; + + /// Collect value starting words for the given specified value type. + /// This includes keyword and function names which can appear at the + /// beginning of a value of this type. + /// + /// Caller should pass in a callback function to accept the list of + /// values. The callback function can be called multiple times, and + /// some values passed to the callback may be duplicate. + fn collect_completion_keywords(_f: KeywordsCollectFn) {} } impl SpecifiedValueInfo for bool {} @@ -44,16 +56,25 @@ impl SpecifiedValueInfo for String {} impl SpecifiedValueInfo for Box { const SUPPORTED_TYPES: u8 = T::SUPPORTED_TYPES; + fn collect_completion_keywords(f: KeywordsCollectFn) { + T::collect_completion_keywords(f); + } } impl SpecifiedValueInfo for [T] { const SUPPORTED_TYPES: u8 = T::SUPPORTED_TYPES; + fn collect_completion_keywords(f: KeywordsCollectFn) { + T::collect_completion_keywords(f); + } } macro_rules! impl_generic_specified_value_info { ($ty:ident<$param:ident>) => { impl<$param: SpecifiedValueInfo> SpecifiedValueInfo for $ty<$param> { const SUPPORTED_TYPES: u8 = $param::SUPPORTED_TYPES; + fn collect_completion_keywords(f: KeywordsCollectFn) { + $param::collect_completion_keywords(f); + } } } } @@ -68,4 +89,9 @@ where T2: SpecifiedValueInfo, { const SUPPORTED_TYPES: u8 = T1::SUPPORTED_TYPES | T2::SUPPORTED_TYPES; + + fn collect_completion_keywords(f: KeywordsCollectFn) { + T1::collect_completion_keywords(f); + T2::collect_completion_keywords(f); + } } diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index d6d6b3d026b..07d3615d13d 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -5,12 +5,13 @@ use cssparser::{ParseErrorKind, Parser, ParserInput, SourceLocation}; use cssparser::ToCss as ParserToCss; use malloc_size_of::MallocSizeOfOps; -use nsstring::nsCString; +use nsstring::{nsCString, nsStringRepr}; use selectors::{NthIndexCache, SelectorList}; use selectors::matching::{MatchingContext, MatchingMode, matches_selector}; use servo_arc::{Arc, ArcBorrow, RawOffsetArc}; use smallvec::SmallVec; use std::cell::RefCell; +use std::collections::BTreeSet; use std::fmt::Write; use std::iter; use std::mem; @@ -928,20 +929,36 @@ pub extern "C" fn Servo_ComputedValues_ExtractAnimationValue( } } +macro_rules! parse_enabled_property_name { + ($prop_name:ident, $found:ident, $default:expr) => {{ + let prop_name = $prop_name.as_ref().unwrap().as_str_unchecked(); + // XXX This can be simplified once Option::filter is stable. + let prop_id = PropertyId::parse(prop_name).ok().and_then(|p| { + if p.enabled_for_all_content() { + Some(p) + } else { + None + } + }); + match prop_id { + Some(p) => { + *$found = true; + p + } + None => { + *$found = false; + return $default; + } + } + }} +} + #[no_mangle] pub unsafe extern "C" fn Servo_Property_IsShorthand( prop_name: *const nsACString, found: *mut bool ) -> bool { - let prop_id = PropertyId::parse(prop_name.as_ref().unwrap().as_str_unchecked()); - let prop_id = match prop_id { - Ok(ref p) if p.enabled_for_all_content() => p, - _ => { - *found = false; - return false; - } - }; - *found = true; + let prop_id = parse_enabled_property_name!(prop_name, found, false); prop_id.is_shorthand() } @@ -970,16 +987,7 @@ pub unsafe extern "C" fn Servo_Property_SupportsType( ty: u32, found: *mut bool, ) -> bool { - let prop_id = PropertyId::parse(prop_name.as_ref().unwrap().as_str_unchecked()); - let prop_id = match prop_id { - Ok(ref p) if p.enabled_for_all_content() => p, - _ => { - *found = false; - return false; - } - }; - - *found = true; + let prop_id = parse_enabled_property_name!(prop_name, found, false); // This should match the constants in InspectorUtils. // (Let's don't bother importing InspectorUtilsBinding into bindings // because it is not used anywhere else, and issue here would be @@ -993,6 +1001,24 @@ pub unsafe extern "C" fn Servo_Property_SupportsType( prop_id.supports_type(ty) } +#[no_mangle] +pub unsafe extern "C" fn Servo_Property_GetCSSValuesForProperty( + prop_name: *const nsACString, + found: *mut bool, + result: *mut nsTArray, +) { + let prop_id = parse_enabled_property_name!(prop_name, found, ()); + // Use B-tree set for unique and sorted result. + let mut values = BTreeSet::<&'static str>::new(); + prop_id.collect_property_completion_keywords(&mut |list| values.extend(list.iter())); + + let result = result.as_mut().unwrap(); + bindings::Gecko_ResizeTArrayForStrings(result, values.len() as u32); + for (src, dest) in values.iter().zip(result.iter_mut()) { + dest.write_str(src).unwrap(); + } +} + #[no_mangle] pub extern "C" fn Servo_Property_IsAnimatable(property: nsCSSPropertyID) -> bool { use style::properties::animated_properties;