From 4062899fd8ff98e54a683f79e6d491bd916d841e Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Thu, 18 Aug 2016 18:54:36 +0200 Subject: [PATCH] Import SelectorMap back from the selectors crate. Nobody else uses it, and I want to make breaking changes to it. --- components/script/dom/element.rs | 9 +- components/script/layout_wrapper.rs | 9 +- components/servo/Cargo.lock | 1 + components/style/Cargo.toml | 1 + components/style/animation.rs | 2 +- components/style/dom.rs | 6 +- components/style/lib.rs | 1 + .../style/properties/properties.mako.rs | 6 +- components/style/selector_matching.rs | 311 +++++++++++++++++- docs/components/style.md | 4 +- ports/cef/Cargo.lock | 1 + ports/geckolib/Cargo.lock | 1 + ports/geckolib/wrapper.rs | 4 +- 13 files changed, 332 insertions(+), 24 deletions(-) diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 2972a9465f4..5141c8d5adf 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -71,7 +71,7 @@ use html5ever::serialize::TraversalScope; use html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode}; use html5ever::tree_builder::{LimitedQuirks, NoQuirks, Quirks}; use ref_filter_map::ref_filter_map; -use selectors::matching::{DeclarationBlock, ElementFlags, matches}; +use selectors::matching::{ElementFlags, matches}; use selectors::matching::{HAS_SLOW_SELECTOR, HAS_EDGE_CHILD_SELECTOR, HAS_SLOW_SELECTOR_LATER_SIBLINGS}; use selectors::parser::{AttrSelector, NamespaceConstraint, parse_author_origin_selector_list_from_str}; use std::ascii::AsciiExt; @@ -92,6 +92,7 @@ use style::properties::longhands::{self, background_image, border_spacing, font_ use style::properties::{DeclaredValue, Importance}; use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, parse_style_attribute}; use style::selector_impl::{NonTSPseudoClass, ServoSelectorImpl}; +use style::selector_matching::DeclarationBlock; use style::sink::Push; use style::values::CSSFloat; use style::values::specified::{self, CSSColor, CSSRGBA, LengthOrPercentage}; @@ -291,7 +292,7 @@ pub trait LayoutElementHelpers { #[allow(unsafe_code)] unsafe fn synthesize_presentational_hints_for_legacy_attributes(&self, &mut V) - where V: Push>>; + where V: Push; #[allow(unsafe_code)] unsafe fn get_colspan(self) -> u32; #[allow(unsafe_code)] @@ -324,10 +325,10 @@ impl LayoutElementHelpers for LayoutJS { #[allow(unsafe_code)] unsafe fn synthesize_presentational_hints_for_legacy_attributes(&self, hints: &mut V) - where V: Push>> + where V: Push { #[inline] - fn from_declaration(rule: PropertyDeclaration) -> DeclarationBlock> { + fn from_declaration(rule: PropertyDeclaration) -> DeclarationBlock { DeclarationBlock::from_declarations(Arc::new(vec![rule])) } diff --git a/components/script/layout_wrapper.rs b/components/script/layout_wrapper.rs index 431fe55f9b0..b4e54fe5bd9 100644 --- a/components/script/layout_wrapper.rs +++ b/components/script/layout_wrapper.rs @@ -47,7 +47,7 @@ use script_layout_interface::wrapper_traits::{DangerousThreadSafeLayoutNode, Lay use script_layout_interface::wrapper_traits::{ThreadSafeLayoutNode, ThreadSafeLayoutElement}; use script_layout_interface::{HTMLCanvasData, LayoutNodeType, TrustedNodeAddress}; use script_layout_interface::{OpaqueStyleAndLayoutData, PartialStyleAndLayoutData}; -use selectors::matching::{DeclarationBlock, ElementFlags}; +use selectors::matching::ElementFlags; use selectors::parser::{AttrSelector, NamespaceConstraint}; use std::fmt; use std::marker::PhantomData; @@ -60,9 +60,10 @@ use style::context::SharedStyleContext; use style::data::PrivateStyleData; use style::dom::{PresentationalHintsSynthetizer, OpaqueNode, TDocument, TElement, TNode, UnsafeNode}; use style::element_state::*; -use style::properties::{ComputedValues, PropertyDeclaration, PropertyDeclarationBlock}; +use style::properties::{ComputedValues, PropertyDeclarationBlock}; use style::refcell::{Ref, RefCell, RefMut}; use style::selector_impl::{ElementSnapshot, NonTSPseudoClass, PseudoElement, ServoSelectorImpl}; +use style::selector_matching::DeclarationBlock; use style::sink::Push; use style::str::is_whitespace; use url::Url; @@ -418,7 +419,7 @@ impl<'le> fmt::Debug for ServoLayoutElement<'le> { impl<'le> PresentationalHintsSynthetizer for ServoLayoutElement<'le> { fn synthesize_presentational_hints_for_legacy_attributes(&self, hints: &mut V) - where V: Push>> + where V: Push { unsafe { self.element.synthesize_presentational_hints_for_legacy_attributes(hints); @@ -1070,5 +1071,5 @@ impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> { impl<'le> PresentationalHintsSynthetizer for ServoThreadSafeLayoutElement<'le> { fn synthesize_presentational_hints_for_legacy_attributes(&self, _hints: &mut V) - where V: Push>> {} + where V: Push {} } diff --git a/components/servo/Cargo.lock b/components/servo/Cargo.lock index cff42fe802f..fef485423eb 100644 --- a/components/servo/Cargo.lock +++ b/components/servo/Cargo.lock @@ -2236,6 +2236,7 @@ dependencies = [ "num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", "ordered-float 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "plugins 0.0.1", + "quickersort 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "selectors 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index 76e6c8728c1..f19a0b3381b 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -36,6 +36,7 @@ log = "0.3.5" matches = "0.1" num-traits = "0.1.32" ordered-float = "0.2.2" +quickersort = "2.0.0" rand = "0.3" rustc-serialize = "0.3" selectors = "0.11" diff --git a/components/style/animation.rs b/components/style/animation.rs index 63ddc9e8c69..2764511c7f3 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -16,7 +16,7 @@ use properties::longhands::animation_play_state::computed_value::AnimationPlaySt use properties::longhands::transition_timing_function::computed_value::StartEnd; use properties::longhands::transition_timing_function::computed_value::TransitionTimingFunction; use properties::{self, ComputedValues}; -use selectors::matching::DeclarationBlock; +use selector_matching::DeclarationBlock; use std::sync::Arc; use std::sync::mpsc::Sender; use string_cache::Atom; diff --git a/components/style/dom.rs b/components/style/dom.rs index bb96b45e9fd..3b0940c59ef 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -9,11 +9,11 @@ use context::SharedStyleContext; use data::PrivateStyleData; use element_state::ElementState; -use properties::{ComputedValues, PropertyDeclaration, PropertyDeclarationBlock}; +use properties::{ComputedValues, PropertyDeclarationBlock}; use refcell::{Ref, RefMut}; use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint}; use selector_impl::{ElementExt, PseudoElement}; -use selectors::matching::DeclarationBlock; +use selector_matching::DeclarationBlock; use sink::Push; use std::fmt::Debug; use std::ops::BitOr; @@ -198,7 +198,7 @@ pub trait TDocument : Sized + Copy + Clone { pub trait PresentationalHintsSynthetizer { fn synthesize_presentational_hints_for_legacy_attributes(&self, hints: &mut V) - where V: Push>>; + where V: Push; } pub trait TElement : PartialEq + Debug + Sized + Copy + Clone + ElementExt + PresentationalHintsSynthetizer { diff --git a/components/style/lib.rs b/components/style/lib.rs index 3869b7867e9..3386f812186 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -58,6 +58,7 @@ extern crate log; extern crate matches; extern crate num_traits; extern crate ordered_float; +extern crate quickersort; extern crate rand; extern crate rustc_serialize; extern crate selectors; diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index e42f6ce09d8..ff96363127b 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -31,7 +31,7 @@ use computed_values; #[cfg(feature = "servo")] use logical_geometry::{LogicalMargin, PhysicalSide}; use logical_geometry::WritingMode; use parser::{ParserContext, ParserContextExtraData, log_css_error}; -use selectors::matching::DeclarationBlock; +use selector_matching::DeclarationBlock; use stylesheets::Origin; use values::LocalToCss; use values::HasViewportPercentage; @@ -1670,7 +1670,7 @@ mod lazy_static_module { #[allow(unused_mut, unused_imports)] fn cascade_with_cached_declarations( viewport_size: Size2D, - applicable_declarations: &[DeclarationBlock>], + applicable_declarations: &[DeclarationBlock], shareable: bool, parent_style: &ComputedValues, cached_style: &ComputedValues, @@ -1815,7 +1815,7 @@ static CASCADE_PROPERTY: [CascadePropertyFn; ${len(data.longhands)}] = [ /// /// Returns the computed values and a boolean indicating whether the result is cacheable. pub fn cascade(viewport_size: Size2D, - applicable_declarations: &[DeclarationBlock>], + applicable_declarations: &[DeclarationBlock], shareable: bool, parent_style: Option<<&ComputedValues>, cached_style: Option<<&ComputedValues>, diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs index e4b1f3cdc52..f6ea546e2e5 100644 --- a/components/style/selector_matching.rs +++ b/components/style/selector_matching.rs @@ -10,19 +10,22 @@ use error_reporting::StdoutErrorReporter; use keyframes::KeyframesAnimation; use media_queries::{Device, MediaType}; use properties::{self, PropertyDeclaration, PropertyDeclarationBlock, ComputedValues}; +use quickersort::sort_by; use restyle_hints::{RestyleHint, DependencySet}; use selector_impl::{ElementExt, TheSelectorImpl, PseudoElement}; use selectors::Element; use selectors::bloom::BloomFilter; -use selectors::matching::DeclarationBlock as GenericDeclarationBlock; use selectors::matching::{AFFECTED_BY_STYLE_ATTRIBUTE, AFFECTED_BY_PRESENTATIONAL_HINTS}; -use selectors::matching::{Rule, SelectorMap, StyleRelations}; -use selectors::parser::Selector; +use selectors::matching::{StyleRelations, matches_compound_selector}; +use selectors::parser::{Selector, SelectorImpl, SimpleSelector, LocalName, CompoundSelector}; use sink::Push; use smallvec::VecLike; +use std::borrow::Borrow; +use std::cmp::Ordering; use std::collections::HashMap; use std::fmt; use std::hash::BuildHasherDefault; +use std::hash::Hash; use std::sync::Arc; use string_cache::Atom; use style_traits::viewport::ViewportConstraints; @@ -276,7 +279,7 @@ impl Stylist { parent: &Arc) -> Option> where E: Element + - Debug + + fmt::Debug + PresentationalHintsSynthetizer { debug_assert!(TheSelectorImpl::pseudo_element_cascade_type(pseudo).is_lazy()); @@ -345,7 +348,7 @@ impl Stylist { pseudo_element: Option<&PseudoElement>, applicable_declarations: &mut V) -> StyleRelations where E: Element + - fmt::Debug + + fmt::Debug + PresentationalHintsSynthetizer, V: Push + VecLike { @@ -577,3 +580,301 @@ impl PerPseudoElementSelectorMap { } } } + +/// Map element data to Rules whose last simple selector starts with them. +/// +/// e.g., +/// "p > img" would go into the set of Rules corresponding to the +/// element "img" +/// "a .foo .bar.baz" would go into the set of Rules corresponding to +/// the class "bar" +/// +/// Because we match Rules right-to-left (i.e., moving up the tree +/// from an element), we need to compare the last simple selector in the +/// Rule with the element. +/// +/// So, if an element has ID "id1" and classes "foo" and "bar", then all +/// the rules it matches will have their last simple selector starting +/// either with "#id1" or with ".foo" or with ".bar". +/// +/// Hence, the union of the rules keyed on each of element's classes, ID, +/// element name, etc. will contain the Rules that actually match that +/// element. +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub struct SelectorMap { + // TODO: Tune the initial capacity of the HashMap + id_hash: HashMap>>, + class_hash: HashMap>>, + local_name_hash: HashMap>>, + /// Same as local_name_hash, but keys are lower-cased. + /// For HTML elements in HTML documents. + lower_local_name_hash: HashMap>>, + /// Rules that don't have ID, class, or element selectors. + other_rules: Vec>, + /// Whether this hash is empty. + empty: bool, +} + +#[inline] +fn compare(a: &GenericDeclarationBlock, b: &GenericDeclarationBlock) -> Ordering { + (a.specificity, a.source_order).cmp(&(b.specificity, b.source_order)) +} + +impl SelectorMap { + pub fn new() -> SelectorMap { + SelectorMap { + id_hash: HashMap::default(), + class_hash: HashMap::default(), + local_name_hash: HashMap::default(), + lower_local_name_hash: HashMap::default(), + other_rules: vec!(), + empty: true, + } + } + + /// Append to `rule_list` all Rules in `self` that match element. + /// + /// Extract matching rules as per element's ID, classes, tag name, etc.. + /// Sort the Rules at the end to maintain cascading order. + pub fn get_all_matching_rules(&self, + element: &E, + parent_bf: Option<&BloomFilter>, + matching_rules_list: &mut V, + relations: &mut StyleRelations) + where E: Element, + V: VecLike> + { + if self.empty { + return + } + + // At the end, we're going to sort the rules that we added, so remember where we began. + let init_len = matching_rules_list.len(); + if let Some(id) = element.get_id() { + SelectorMap::get_matching_rules_from_hash(element, + parent_bf, + &self.id_hash, + &id, + matching_rules_list, + relations) + } + + element.each_class(|class| { + SelectorMap::get_matching_rules_from_hash(element, + parent_bf, + &self.class_hash, + class, + matching_rules_list, + relations); + }); + + let local_name_hash = if element.is_html_element_in_html_document() { + &self.lower_local_name_hash + } else { + &self.local_name_hash + }; + SelectorMap::get_matching_rules_from_hash(element, + parent_bf, + local_name_hash, + &element.get_local_name(), + matching_rules_list, + relations); + + SelectorMap::get_matching_rules(element, + parent_bf, + &self.other_rules, + matching_rules_list, + relations); + + // Sort only the rules we just added. + sort_by(&mut matching_rules_list[init_len..], &compare); + } + + /// Append to `rule_list` all universal Rules (rules with selector `*|*`) in + /// `self` sorted by specifity and source order. + pub fn get_universal_rules(&self, + matching_rules_list: &mut V) + where V: VecLike> + { + if self.empty { + return + } + + let init_len = matching_rules_list.len(); + + for rule in self.other_rules.iter() { + if rule.selector.simple_selectors.is_empty() && + rule.selector.next.is_none() { + matching_rules_list.push(rule.declarations.clone()); + } + } + + sort_by(&mut matching_rules_list[init_len..], &compare); + } + + fn get_matching_rules_from_hash( + element: &E, + parent_bf: Option<&BloomFilter>, + hash: &HashMap>>, + key: &BorrowedStr, + matching_rules: &mut Vector, + relations: &mut StyleRelations) + where E: Element, + Str: Borrow + Eq + Hash, + BorrowedStr: Eq + Hash, + Vector: VecLike> + { + if let Some(rules) = hash.get(key) { + SelectorMap::get_matching_rules(element, + parent_bf, + rules, + matching_rules, + relations) + } + } + + /// Adds rules in `rules` that match `element` to the `matching_rules` list. + fn get_matching_rules(element: &E, + parent_bf: Option<&BloomFilter>, + rules: &[Rule], + matching_rules: &mut V, + relations: &mut StyleRelations) + where E: Element, + V: VecLike> + { + for rule in rules.iter() { + if matches_compound_selector(&*rule.selector, + element, parent_bf, relations) { + matching_rules.push(rule.declarations.clone()); + } + } + } + + /// Insert rule into the correct hash. + /// Order in which to try: id_hash, class_hash, local_name_hash, other_rules. + pub fn insert(&mut self, rule: Rule) { + self.empty = false; + + if let Some(id_name) = SelectorMap::get_id_name(&rule) { + find_push(&mut self.id_hash, id_name, rule); + return; + } + + if let Some(class_name) = SelectorMap::get_class_name(&rule) { + find_push(&mut self.class_hash, class_name, rule); + return; + } + + if let Some(LocalName { name, lower_name }) = SelectorMap::get_local_name(&rule) { + find_push(&mut self.local_name_hash, name, rule.clone()); + find_push(&mut self.lower_local_name_hash, lower_name, rule); + return; + } + + self.other_rules.push(rule); + } + + /// Retrieve the first ID name in Rule, or None otherwise. + fn get_id_name(rule: &Rule) -> Option { + for ss in &rule.selector.simple_selectors { + // TODO(pradeep): Implement case-sensitivity based on the + // document type and quirks mode. + if let SimpleSelector::ID(ref id) = *ss { + return Some(id.clone()); + } + } + + None + } + + /// Retrieve the FIRST class name in Rule, or None otherwise. + fn get_class_name(rule: &Rule) -> Option { + for ss in &rule.selector.simple_selectors { + // TODO(pradeep): Implement case-sensitivity based on the + // document type and quirks mode. + if let SimpleSelector::Class(ref class) = *ss { + return Some(class.clone()); + } + } + + None + } + + /// Retrieve the name if it is a type selector, or None otherwise. + fn get_local_name(rule: &Rule) -> Option> { + for ss in &rule.selector.simple_selectors { + if let SimpleSelector::LocalName(ref n) = *ss { + return Some(LocalName { + name: n.name.clone(), + lower_name: n.lower_name.clone(), + }) + } + } + + None + } +} + +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub struct Rule { + // This is an Arc because Rule will essentially be cloned for every element + // that it matches. Selector contains an owned vector (through + // CompoundSelector) and we want to avoid the allocation. + pub selector: Arc>, + pub declarations: GenericDeclarationBlock, +} + +/// A property declaration together with its precedence among rules of equal specificity so that +/// we can sort them. +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Debug)] +pub struct GenericDeclarationBlock { + pub declarations: Arc, + pub source_order: usize, + pub specificity: u32, +} + +// FIXME(https://github.com/rust-lang/rust/issues/7671) +// derive(Clone) requires T: Clone, even though Arc: T regardless. +impl Clone for GenericDeclarationBlock { + fn clone(&self) -> Self { + GenericDeclarationBlock { + declarations: self.declarations.clone(), + source_order: self.source_order, + specificity: self.specificity, + } + } +} + +// FIXME(https://github.com/rust-lang/rust/issues/7671) +impl Clone for Rule { + fn clone(&self) -> Rule { + Rule { + selector: self.selector.clone(), + declarations: self.declarations.clone(), + } + } +} + +impl GenericDeclarationBlock { + #[inline] + pub fn from_declarations(declarations: Arc) -> Self { + GenericDeclarationBlock { + declarations: declarations, + source_order: 0, + specificity: 0, + } + } +} + +fn find_push( + map: &mut HashMap>>, + key: Str, + value: Rule +) { + if let Some(vec) = map.get_mut(&key) { + vec.push(value); + return + } + map.insert(key, vec![value]); +} diff --git a/docs/components/style.md b/docs/components/style.md index 0bf82713de1..a4a83d4e8f2 100644 --- a/docs/components/style.md +++ b/docs/components/style.md @@ -149,8 +149,8 @@ that you didn't find it here so it can be added :) [mdn-pseudo-after]: https://developer.mozilla.org/en/docs/Web/CSS/::after [mdn-pseudo-selection]: https://developer.mozilla.org/en/docs/Web/CSS/::selection [stylist]: http://doc.servo.org/style/selector_matching/struct.Stylist.html -[selectors-selectormap]: http://doc.servo.org/selectors/matching/struct.SelectorMap.html -[selectors-rule]: http://doc.servo.org/selectors/matching/struct.Rule.html +[selectors-selectormap]: http://doc.servo.org/style/selector_matching/struct.SelectorMap.html +[selectors-rule]: http://doc.servo.org/style/selector_matching/struct.Rule.html [per-pseudo-selectormap]: http://doc.servo.org/style/selector_matching/struct.PerPseudoElementSelectorMap.html [per-origin-selectormap]: http://doc.servo.org/style/selector_matching/struct.PerOriginSelectorMap.html [docs-pipeline]: https://github.com/servo/servo/blob/master/docs/glossary.md#pipeline diff --git a/ports/cef/Cargo.lock b/ports/cef/Cargo.lock index 9991d9f7905..9ff1099e997 100644 --- a/ports/cef/Cargo.lock +++ b/ports/cef/Cargo.lock @@ -2119,6 +2119,7 @@ dependencies = [ "num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", "ordered-float 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "plugins 0.0.1", + "quickersort 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "selectors 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ports/geckolib/Cargo.lock b/ports/geckolib/Cargo.lock index 3caf13d5c08..01001e5f8b7 100644 --- a/ports/geckolib/Cargo.lock +++ b/ports/geckolib/Cargo.lock @@ -365,6 +365,7 @@ dependencies = [ "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", "ordered-float 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "quickersort 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "selectors 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ports/geckolib/wrapper.rs b/ports/geckolib/wrapper.rs index ab0d27a5d85..551e7999154 100644 --- a/ports/geckolib/wrapper.rs +++ b/ports/geckolib/wrapper.rs @@ -30,7 +30,6 @@ use gecko_string_cache::{Atom, Namespace, WeakAtom, WeakNamespace}; use glue::GeckoDeclarationBlock; use libc::uintptr_t; use selectors::Element; -use selectors::matching::DeclarationBlock; use selectors::parser::{AttrSelector, NamespaceConstraint}; use snapshot::GeckoElementSnapshot; use snapshot_helpers; @@ -50,6 +49,7 @@ use style::properties::{ComputedValues, parse_style_attribute}; use style::properties::{PropertyDeclaration, PropertyDeclarationBlock}; use style::refcell::{Ref, RefCell, RefMut}; use style::selector_impl::ElementExt; +use style::selector_matching::DeclarationBlock; use style::sink::Push; use url::Url; @@ -473,7 +473,7 @@ impl<'le> PartialEq for GeckoElement<'le> { impl<'le> PresentationalHintsSynthetizer for GeckoElement<'le> { fn synthesize_presentational_hints_for_legacy_attributes(&self, _hints: &mut V) - where V: Push>> + where V: Push { // FIXME(bholley) - Need to implement this. }