Bug 1434130 part 2 - Add collect_values function to SpecifiedValueInfo trait for collecting possible values. r=emilio
authorXidorn Quan <me@upsuper.org>
Sun, 29 Apr 2018 09:03:31 +1000
changeset 416174 5edb30a5ac7b2382b6d412ce90817e7bc782d529
parent 416173 44c33a20df275d8b7760493b8427f97f01ea3008
child 416175 cebd5132edfc1f48cba9ebb4561925ed4c710876
push id33918
push usernerli@mozilla.com
push dateSun, 29 Apr 2018 09:47:13 +0000
treeherdermozilla-central@afbec7f03bd8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
bugs1434130
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1434130 part 2 - Add collect_values function to SpecifiedValueInfo trait for collecting possible values. r=emilio This is the basic structure of the stuff. Following patches will fill the gap between Gecko and Servo on value generating, and finally hook it into InspectorUtils. MozReview-Commit-ID: KNLAfFBiY6e
layout/style/ServoBindingList.h
servo/components/style/properties/properties.mako.rs
servo/components/style_derive/specified_value_info.rs
servo/components/style_traits/lib.rs
servo/components/style_traits/specified_value_info.rs
servo/ports/geckolib/glue.rs
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -873,16 +873,18 @@ SERVO_BINDING_FUNC(Servo_ParseFontShorth
                    nsCSSValueBorrowedMut weight);
 
 SERVO_BINDING_FUNC(Servo_Property_IsShorthand, bool,
                    const nsACString* name, bool* found);
 SERVO_BINDING_FUNC(Servo_Property_IsInherited, bool,
                    const nsACString* name);
 SERVO_BINDING_FUNC(Servo_Property_SupportsType, bool,
                    const nsACString* name, uint32_t ty, bool* found);
+SERVO_BINDING_FUNC(Servo_Property_GetCSSValuesForProperty, void,
+                   const nsACString* name, bool* found, nsTArray<nsString>* result)
 SERVO_BINDING_FUNC(Servo_PseudoClass_GetStates, uint64_t,
                    const nsACString* name)
 
 // AddRef / Release functions
 #define SERVO_ARC_TYPE(name_, type_)                                \
   SERVO_BINDING_FUNC(Servo_##name_##_AddRef, void, type_##Borrowed) \
   SERVO_BINDING_FUNC(Servo_##name_##_Release, void, type_##Borrowed)
 #include "mozilla/ServoArcTypeList.h"
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -37,17 +37,17 @@ use logical_geometry::WritingMode;
 use media_queries::Device;
 use parser::ParserContext;
 #[cfg(feature = "gecko")] use properties::longhands::system_font::SystemFont;
 use rule_cache::{RuleCache, RuleCacheConditions};
 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;
 use values::generics::text::LineHeight;
 use values::computed;
 use values::computed::NonNegativeLength;
 use rule_tree::{CascadeLevel, StrongRuleNode};
 use self::computed_value_flags::*;
@@ -555,16 +555,35 @@ impl NonCustomPropertyId {
                 0, // 'all' accepts no value other than CSS-wide keywords
             % else:
                 <shorthands::${prop.ident}::Longhands as SpecifiedValueInfo>::SUPPORTED_TYPES,
             % endif
             % endfor
         ];
         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:
+                &<shorthands::${prop.ident}::Longhands as SpecifiedValueInfo>::
+                    collect_completion_keywords,
+            % endif
+            % endfor
+        ];
+        COLLECT_FUNCTIONS[self.0](f);
+    }
 }
 
 impl From<LonghandId> for NonCustomPropertyId {
     fn from(id: LonghandId) -> Self {
         NonCustomPropertyId(id as usize)
     }
 }
 
@@ -738,17 +757,18 @@ impl LonghandIdSet {
     /// Returns whether the set is empty.
     #[inline]
     pub fn is_empty(&self) -> bool {
         self.storage.iter().all(|c| *c == 0)
     }
 }
 
 /// 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,
     /// The `inherit` keyword.
     Inherit,
     /// The `unset` keyword.
     Unset,
 }
@@ -1706,16 +1726,28 @@ impl PropertyId {
             PropertyId::Custom(_) => return None,
             PropertyId::Shorthand(shorthand_id) => shorthand_id.into(),
             PropertyId::Longhand(longhand_id) => longhand_id.into(),
             PropertyId::ShorthandAlias(_, alias_id) => alias_id.into(),
             PropertyId::LonghandAlias(_, alias_id) => alias_id.into(),
         })
     }
 
+    /// Returns non-alias NonCustomPropertyId corresponding to this
+    /// property id.
+    fn non_custom_non_alias_id(&self) -> Option<NonCustomPropertyId> {
+        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]
     pub fn enabled_for_all_content(&self) -> bool {
         let id = match self.non_custom_id() {
             // Custom properties are allowed everywhere
             None => return true,
             Some(id) => id,
@@ -1731,24 +1763,29 @@ impl PropertyId {
             Some(id) => id,
         };
         id.allowed_in(context)
     }
 
     /// 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);
     }
 }
 
 /// A declaration using a CSS-wide keyword.
 #[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
 #[derive(Clone, PartialEq, ToCss)]
 pub struct WideKeywordDeclaration {
     #[css(skip)]
--- a/servo/components/style_derive/specified_value_info.rs
+++ b/servo/components/style_derive/specified_value_info.rs
@@ -1,68 +1,124 @@
 /* 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/. */
 
 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::<CssInputAttrs>(&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(
                 &mut where_clause,
                 parse_quote!(#param: ::style_traits::SpecifiedValueInfo),
             );
         }
         input.generics.where_clause = where_clause;
 
         match input.data {
             Data::Enum(ref e) => {
                 for v in e.variants.iter() {
                     let attrs = cg::parse_variant_attrs::<CssVariantAttrs>(&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! {
         impl #impl_generics ::style_traits::SpecifiedValueInfo for #name #ty_generics
         #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<String>,
+) -> 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::<CssFieldAttrs>(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
 }
--- a/servo/components/style_traits/lib.rs
+++ b/servo/components/style_traits/lib.rs
@@ -74,17 +74,17 @@ pub enum CSSPixel {}
 
 pub mod cursor;
 pub mod specified_value_info;
 #[macro_use]
 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.
 pub type ParseError<'i> = cssparser::ParseError<'i, StyleParseErrorKind<'i>>;
 
 /// Error in property value parsing
 pub type ValueParseError<'i> = cssparser::ParseError<'i, ValueParseErrorKind<'i>>;
 
--- a/servo/components/style_traits/specified_value_info.rs
+++ b/servo/components/style_traits/specified_value_info.rs
@@ -18,54 +18,80 @@ pub mod CssType {
     /// <color>
     pub const COLOR: u8 = 1 << 0;
     /// <gradient>
     pub const GRADIENT: u8 = 1 << 1;
     /// <timing-function>
     pub const TIMING_FUNCTION: u8 = 1 << 2;
 }
 
+/// See SpecifiedValueInfo::collect_completion_keywords.
+pub type KeywordsCollectFn<'a> = &'a mut FnMut(&[&'static str]);
+
 /// Information of values of a given specified value type.
 pub trait SpecifiedValueInfo {
     /// Supported CssTypes by the given value type.
     ///
     /// XXX This should be typed CssType when that becomes a bitflags.
     /// Currently we cannot do so since bitflags cannot be used in constant.
     const SUPPORTED_TYPES: u8 = 0;
+
+    /// Collect value starting words for the given specified value type.
+    /// This includes keyword and function names which can appear at the
+    /// beginning of a value of this type.
+    ///
+    /// Caller should pass in a callback function to accept the list of
+    /// values. The callback function can be called multiple times, and
+    /// some values passed to the callback may be duplicate.
+    fn collect_completion_keywords(_f: KeywordsCollectFn) {}
 }
 
 impl SpecifiedValueInfo for bool {}
 impl SpecifiedValueInfo for f32 {}
 impl SpecifiedValueInfo for i8 {}
 impl SpecifiedValueInfo for i32 {}
 impl SpecifiedValueInfo for u8 {}
 impl SpecifiedValueInfo for u16 {}
 impl SpecifiedValueInfo for u32 {}
 impl SpecifiedValueInfo for str {}
 impl SpecifiedValueInfo for String {}
 
 impl<T: SpecifiedValueInfo + ?Sized> SpecifiedValueInfo for Box<T> {
     const SUPPORTED_TYPES: u8 = T::SUPPORTED_TYPES;
+    fn collect_completion_keywords(f: KeywordsCollectFn) {
+        T::collect_completion_keywords(f);
+    }
 }
 
 impl<T: SpecifiedValueInfo> SpecifiedValueInfo for [T] {
     const SUPPORTED_TYPES: u8 = T::SUPPORTED_TYPES;
+    fn collect_completion_keywords(f: KeywordsCollectFn) {
+        T::collect_completion_keywords(f);
+    }
 }
 
 macro_rules! impl_generic_specified_value_info {
     ($ty:ident<$param:ident>) => {
         impl<$param: SpecifiedValueInfo> SpecifiedValueInfo for $ty<$param> {
             const SUPPORTED_TYPES: u8 = $param::SUPPORTED_TYPES;
+            fn collect_completion_keywords(f: KeywordsCollectFn) {
+                $param::collect_completion_keywords(f);
+            }
         }
     }
 }
 impl_generic_specified_value_info!(Option<T>);
 impl_generic_specified_value_info!(Vec<T>);
 impl_generic_specified_value_info!(Arc<T>);
 impl_generic_specified_value_info!(Range<Idx>);
 
 impl<T1, T2> SpecifiedValueInfo for (T1, T2)
 where
     T1: SpecifiedValueInfo,
     T2: SpecifiedValueInfo,
 {
     const SUPPORTED_TYPES: u8 = T1::SUPPORTED_TYPES | T2::SUPPORTED_TYPES;
+
+    fn collect_completion_keywords(f: KeywordsCollectFn) {
+        T1::collect_completion_keywords(f);
+        T2::collect_completion_keywords(f);
+    }
 }
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -1,21 +1,22 @@
 /* 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/. */
 
 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;
 use std::os::raw::c_void;
 use std::ptr;
 use style::applicable_declarations::ApplicableDeclarationBlock;
 use style::author_styles::AuthorStyles;
 use style::context::{CascadeInputs, QuirksMode, SharedStyleContext, StyleContext};
@@ -923,30 +924,46 @@ pub extern "C" fn Servo_ComputedValues_E
     };
 
     match AnimationValue::from_computed_values(property, &computed_values) {
         Some(v) => Arc::new(v).into_strong(),
         None => Strong::null(),
     }
 }
 
+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()
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn Servo_Property_IsInherited(
     prop_name: *const nsACString,
 ) -> bool {
     let prop_name = prop_name.as_ref().unwrap().as_str_unchecked();
@@ -965,40 +982,49 @@ pub unsafe extern "C" fn Servo_Property_
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn Servo_Property_SupportsType(
     prop_name: *const nsACString,
     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
     // caught by the property-db test anyway.)
     let ty = match ty {
         1 => CssType::COLOR,
         2 => CssType::GRADIENT,
         3 => CssType::TIMING_FUNCTION,
         _ => unreachable!("unknown CSS type {}", ty),
     };
     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<nsStringRepr>,
+) {
+    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;
     animated_properties::nscsspropertyid_is_animatable(property)
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_Property_IsTransitionable(property: nsCSSPropertyID) -> bool {
     use style::properties::animated_properties;