servo: Merge #17063 - style: Allow sharing styles across elements with presentational hints (from emilio:pres-hints-sharing); r=bholley

Source-Repo: https://github.com/servo/servo
Source-Revision: 38a6a3bff6f6e0e35eb592891d5e70e7cb897b69

--HG--
extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear
extra : subtree_revision : 5c6316d592dfa5ef57e7fc03f4c5211cbbc1b1d7
This commit is contained in:
Emilio Cobos Álvarez 2017-05-30 07:28:06 -05:00
Родитель 7e8a66dc9c
Коммит 7b2d94daee
9 изменённых файлов: 318 добавлений и 182 удалений

Просмотреть файл

@ -6,13 +6,13 @@
use context::LayoutContext; use context::LayoutContext;
use flow::{self, Flow}; use flow::{self, Flow};
use fnv::FnvHashMap;
use gfx::display_list::OpaqueNode; use gfx::display_list::OpaqueNode;
use ipc_channel::ipc::IpcSender; use ipc_channel::ipc::IpcSender;
use msg::constellation_msg::PipelineId; use msg::constellation_msg::PipelineId;
use opaque_node::OpaqueNodeMethods; use opaque_node::OpaqueNodeMethods;
use script_traits::{AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg}; use script_traits::{AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg};
use script_traits::UntrustedNodeAddress; use script_traits::UntrustedNodeAddress;
use std::collections::HashMap;
use std::sync::mpsc::Receiver; use std::sync::mpsc::Receiver;
use style::animation::{Animation, update_style_for_animation}; use style::animation::{Animation, update_style_for_animation};
use style::font_metrics::ServoMetricsProvider; use style::font_metrics::ServoMetricsProvider;
@ -24,8 +24,8 @@ use style::timer::Timer;
/// `expired_animations`. /// `expired_animations`.
pub fn update_animation_state(constellation_chan: &IpcSender<ConstellationMsg>, pub fn update_animation_state(constellation_chan: &IpcSender<ConstellationMsg>,
script_chan: &IpcSender<ConstellationControlMsg>, script_chan: &IpcSender<ConstellationControlMsg>,
running_animations: &mut HashMap<OpaqueNode, Vec<Animation>>, running_animations: &mut FnvHashMap<OpaqueNode, Vec<Animation>>,
expired_animations: &mut HashMap<OpaqueNode, Vec<Animation>>, expired_animations: &mut FnvHashMap<OpaqueNode, Vec<Animation>>,
mut newly_transitioning_nodes: Option<&mut Vec<UntrustedNodeAddress>>, mut newly_transitioning_nodes: Option<&mut Vec<UntrustedNodeAddress>>,
new_animations_receiver: &Receiver<Animation>, new_animations_receiver: &Receiver<Animation>,
pipeline_id: PipelineId, pipeline_id: PipelineId,
@ -149,7 +149,7 @@ pub fn update_animation_state(constellation_chan: &IpcSender<ConstellationMsg>,
// this should be made generic. // this should be made generic.
pub fn recalc_style_for_animations(context: &LayoutContext, pub fn recalc_style_for_animations(context: &LayoutContext,
flow: &mut Flow, flow: &mut Flow,
animations: &HashMap<OpaqueNode, animations: &FnvHashMap<OpaqueNode,
Vec<Animation>>) { Vec<Animation>>) {
let mut damage = RestyleDamage::empty(); let mut damage = RestyleDamage::empty();
flow.mutate_fragments(&mut |fragment| { flow.mutate_fragments(&mut |fragment| {

Просмотреть файл

@ -44,7 +44,7 @@ use euclid::point::Point2D;
use euclid::rect::Rect; use euclid::rect::Rect;
use euclid::scale_factor::ScaleFactor; use euclid::scale_factor::ScaleFactor;
use euclid::size::Size2D; use euclid::size::Size2D;
use fnv::FnvHasher; use fnv::FnvHashMap;
use gfx::display_list::{OpaqueNode, WebRenderImageInfo}; use gfx::display_list::{OpaqueNode, WebRenderImageInfo};
use gfx::font; use gfx::font;
use gfx::font_cache_thread::FontCacheThread; use gfx::font_cache_thread::FontCacheThread;
@ -99,7 +99,6 @@ use servo_url::ServoUrl;
use std::borrow::ToOwned; use std::borrow::ToOwned;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::BuildHasherDefault;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem as std_mem; use std::mem as std_mem;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
@ -203,10 +202,10 @@ pub struct LayoutThread {
document_shared_lock: Option<SharedRwLock>, document_shared_lock: Option<SharedRwLock>,
/// The list of currently-running animations. /// The list of currently-running animations.
running_animations: StyleArc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>, running_animations: StyleArc<RwLock<FnvHashMap<OpaqueNode, Vec<Animation>>>>,
/// The list of animations that have expired since the last style recalculation. /// The list of animations that have expired since the last style recalculation.
expired_animations: StyleArc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>, expired_animations: StyleArc<RwLock<FnvHashMap<OpaqueNode, Vec<Animation>>>>,
/// A counter for epoch messages /// A counter for epoch messages
epoch: Cell<Epoch>, epoch: Cell<Epoch>,
@ -224,9 +223,8 @@ pub struct LayoutThread {
/// The CSS error reporter for all CSS loaded in this layout thread /// The CSS error reporter for all CSS loaded in this layout thread
error_reporter: CSSErrorReporter, error_reporter: CSSErrorReporter,
webrender_image_cache: Arc<RwLock<HashMap<(ServoUrl, UsePlaceholder), webrender_image_cache: Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder),
WebRenderImageInfo, WebRenderImageInfo>>>,
BuildHasherDefault<FnvHasher>>>>,
/// Webrender interface. /// Webrender interface.
webrender_api: webrender_traits::RenderApi, webrender_api: webrender_traits::RenderApi,
@ -492,8 +490,8 @@ impl LayoutThread {
outstanding_web_fonts: outstanding_web_fonts_counter, outstanding_web_fonts: outstanding_web_fonts_counter,
root_flow: RefCell::new(None), root_flow: RefCell::new(None),
document_shared_lock: None, document_shared_lock: None,
running_animations: StyleArc::new(RwLock::new(HashMap::new())), running_animations: StyleArc::new(RwLock::new(FnvHashMap::default())),
expired_animations: StyleArc::new(RwLock::new(HashMap::new())), expired_animations: StyleArc::new(RwLock::new(FnvHashMap::default())),
epoch: Cell::new(Epoch(0)), epoch: Cell::new(Epoch(0)),
viewport_size: Size2D::new(Au(0), Au(0)), viewport_size: Size2D::new(Au(0), Au(0)),
webrender_api: webrender_api_sender.create_api(), webrender_api: webrender_api_sender.create_api(),
@ -521,7 +519,7 @@ impl LayoutThread {
script_chan: Arc::new(Mutex::new(script_chan)), script_chan: Arc::new(Mutex::new(script_chan)),
}, },
webrender_image_cache: webrender_image_cache:
Arc::new(RwLock::new(HashMap::with_hasher(Default::default()))), Arc::new(RwLock::new(FnvHashMap::default())),
timer: timer:
if PREFS.get("layout.animations.test.enabled") if PREFS.get("layout.animations.test.enabled")
.as_boolean().unwrap_or(false) { .as_boolean().unwrap_or(false) {

Просмотреть файл

@ -7,7 +7,6 @@
#[cfg(feature = "servo")] use animation::Animation; #[cfg(feature = "servo")] use animation::Animation;
use animation::PropertyAnimation; use animation::PropertyAnimation;
use app_units::Au; use app_units::Au;
use bit_vec::BitVec;
use bloom::StyleBloom; use bloom::StyleBloom;
use cache::LRUCache; use cache::LRUCache;
use data::ElementData; use data::ElementData;
@ -21,11 +20,8 @@ use font_metrics::FontMetricsProvider;
#[cfg(feature = "gecko")] use properties::ComputedValues; #[cfg(feature = "gecko")] use properties::ComputedValues;
use selector_parser::SnapshotMap; use selector_parser::SnapshotMap;
use selectors::matching::ElementSelectorFlags; use selectors::matching::ElementSelectorFlags;
#[cfg(feature = "servo")] use servo_config::opts;
use shared_lock::StylesheetGuards; use shared_lock::StylesheetGuards;
use sharing::StyleSharingCandidateCache; use sharing::{ValidationData, StyleSharingCandidateCache};
#[cfg(feature = "servo")] use std::collections::HashMap;
#[cfg(feature = "gecko")] use std::env;
use std::fmt; use std::fmt;
use std::ops::Add; use std::ops::Add;
#[cfg(feature = "servo")] use std::sync::Mutex; #[cfg(feature = "servo")] use std::sync::Mutex;
@ -79,6 +75,7 @@ pub struct StyleSystemOptions {
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
fn get_env(name: &str) -> bool { fn get_env(name: &str) -> bool {
use std::env;
match env::var(name) { match env::var(name) {
Ok(s) => !s.is_empty(), Ok(s) => !s.is_empty(),
Err(_) => false, Err(_) => false,
@ -88,6 +85,8 @@ fn get_env(name: &str) -> bool {
impl Default for StyleSystemOptions { impl Default for StyleSystemOptions {
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
fn default() -> Self { fn default() -> Self {
use servo_config::opts;
StyleSystemOptions { StyleSystemOptions {
disable_style_sharing_cache: opts::get().disable_share_style_cache, disable_style_sharing_cache: opts::get().disable_share_style_cache,
dump_style_statistics: opts::get().style_sharing_stats, dump_style_statistics: opts::get().style_sharing_stats,
@ -135,11 +134,11 @@ pub struct SharedStyleContext<'a> {
/// The animations that are currently running. /// The animations that are currently running.
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
pub running_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>, pub running_animations: Arc<RwLock<FnvHashMap<OpaqueNode, Vec<Animation>>>>,
/// The list of animations that have expired since the last style recalculation. /// The list of animations that have expired since the last style recalculation.
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
pub expired_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>, pub expired_animations: Arc<RwLock<FnvHashMap<OpaqueNode, Vec<Animation>>>>,
/// Data needed to create the thread-local style context from the shared one. /// Data needed to create the thread-local style context from the shared one.
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
@ -154,26 +153,27 @@ impl<'a> SharedStyleContext<'a> {
} }
} }
/// Information about the current element being processed. We group this together /// Information about the current element being processed. We group this
/// into a single struct within ThreadLocalStyleContext so that we can instantiate /// together into a single struct within ThreadLocalStyleContext so that we can
/// and destroy it easily at the beginning and end of element processing. /// instantiate and destroy it easily at the beginning and end of element
/// processing.
pub struct CurrentElementInfo { pub struct CurrentElementInfo {
/// The element being processed. Currently we use an OpaqueNode since we only /// The element being processed. Currently we use an OpaqueNode since we
/// use this for identity checks, but we could use SendElement if there were /// only use this for identity checks, but we could use SendElement if there
/// a good reason to. /// were a good reason to.
element: OpaqueNode, element: OpaqueNode,
/// Whether the element is being styled for the first time. /// Whether the element is being styled for the first time.
is_initial_style: bool, is_initial_style: bool,
/// Lazy cache of the result of matching the current element against the /// Lazy cache of the different data used for style sharing.
/// revalidation selectors. pub validation_data: ValidationData,
pub revalidation_match_results: Option<BitVec>,
/// A Vec of possibly expired animations. Used only by Servo. /// A Vec of possibly expired animations. Used only by Servo.
#[allow(dead_code)] #[allow(dead_code)]
pub possibly_expired_animations: Vec<PropertyAnimation>, pub possibly_expired_animations: Vec<PropertyAnimation>,
} }
/// Statistics gathered during the traversal. We gather statistics on each thread /// Statistics gathered during the traversal. We gather statistics on each
/// and then combine them after the threads join via the Add implementation below. /// thread and then combine them after the threads join via the Add
/// implementation below.
#[derive(Default)] #[derive(Default)]
pub struct TraversalStatistics { pub struct TraversalStatistics {
/// The total number of elements traversed. /// The total number of elements traversed.
@ -463,7 +463,7 @@ impl<E: TElement> ThreadLocalStyleContext<E> {
self.current_element_info = Some(CurrentElementInfo { self.current_element_info = Some(CurrentElementInfo {
element: element.as_node().opaque(), element: element.as_node().opaque(),
is_initial_style: !data.has_styles(), is_initial_style: !data.has_styles(),
revalidation_match_results: None, validation_data: ValidationData::new(),
possibly_expired_animations: Vec::new(), possibly_expired_animations: Vec::new(),
}); });
} }

Просмотреть файл

@ -23,7 +23,7 @@ use rule_tree::{CascadeLevel, StrongRuleNode};
use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl}; use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, StyleRelations}; use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, StyleRelations};
use selectors::matching::{VisitedHandlingMode, AFFECTED_BY_PSEUDO_ELEMENTS}; use selectors::matching::{VisitedHandlingMode, AFFECTED_BY_PSEUDO_ELEMENTS};
use sharing::{StyleSharingBehavior, StyleSharingResult}; use sharing::StyleSharingBehavior;
use stylearc::Arc; use stylearc::Arc;
use stylist::{ApplicableDeclarationList, RuleInclusion}; use stylist::{ApplicableDeclarationList, RuleInclusion};
@ -855,17 +855,19 @@ pub trait MatchMethods : TElement {
// //
// If we do have the results, grab them here to satisfy the borrow // If we do have the results, grab them here to satisfy the borrow
// checker. // checker.
let revalidation_match_results = context.thread_local let validation_data =
.current_element_info context.thread_local
.as_mut().unwrap() .current_element_info
.revalidation_match_results .as_mut().unwrap()
.take(); .validation_data
.take();
context.thread_local context.thread_local
.style_sharing_candidate_cache .style_sharing_candidate_cache
.insert_if_possible(self, .insert_if_possible(self,
data.styles().primary.values(), data.styles().primary.values(),
primary_results.relations, primary_results.relations,
revalidation_match_results); validation_data);
} }
child_cascade_requirement child_cascade_requirement
@ -1341,30 +1343,6 @@ pub trait MatchMethods : TElement {
false false
} }
/// Attempts to share a style with another node. This method is unsafe
/// because it depends on the `style_sharing_candidate_cache` having only
/// live nodes in it, and we have no way to guarantee that at the type
/// system level yet.
unsafe fn share_style_if_possible(&self,
context: &mut StyleContext<Self>,
data: &mut ElementData)
-> StyleSharingResult {
let shared_context = &context.shared;
let current_element_info =
context.thread_local.current_element_info.as_mut().unwrap();
let selector_flags_map = &mut context.thread_local.selector_flags;
let bloom_filter = context.thread_local.bloom_filter.filter();
context.thread_local
.style_sharing_candidate_cache
.share_style_if_possible(shared_context,
current_element_info,
selector_flags_map,
bloom_filter,
*self,
data)
}
/// Given the old and new style of this element, and whether it's a /// Given the old and new style of this element, and whether it's a
/// pseudo-element, compute the restyle damage used to determine which /// pseudo-element, compute the restyle damage used to determine which
/// kind of layout or painting operations we'll need. /// kind of layout or painting operations we'll need.

Просмотреть файл

@ -61,6 +61,12 @@ pub enum StyleSource {
Declarations(Arc<Locked<PropertyDeclarationBlock>>), Declarations(Arc<Locked<PropertyDeclarationBlock>>),
} }
impl PartialEq for StyleSource {
fn eq(&self, other: &Self) -> bool {
self.ptr_equals(other)
}
}
impl StyleSource { impl StyleSource {
#[inline] #[inline]
fn ptr_equals(&self, other: &Self) -> bool { fn ptr_equals(&self, other: &Self) -> bool {

Просмотреть файл

@ -6,14 +6,12 @@
//! quickly whether it's worth to share style, and whether two different //! quickly whether it's worth to share style, and whether two different
//! elements can indeed share the same style. //! elements can indeed share the same style.
use context::{CurrentElementInfo, SelectorFlagsMap, SharedStyleContext}; use context::{SelectorFlagsMap, SharedStyleContext};
use dom::TElement; use dom::TElement;
use element_state::*; use element_state::*;
use matching::MatchMethods;
use selectors::bloom::BloomFilter; use selectors::bloom::BloomFilter;
use selectors::matching::{ElementSelectorFlags, StyleRelations}; use selectors::matching::StyleRelations;
use sharing::StyleSharingCandidate; use sharing::{StyleSharingCandidate, StyleSharingTarget};
use sink::ForgetfulSink;
use stylearc::Arc; use stylearc::Arc;
/// Determines, based on the results of selector matching, whether it's worth to /// Determines, based on the results of selector matching, whether it's worth to
@ -24,8 +22,7 @@ pub fn relations_are_shareable(relations: &StyleRelations) -> bool {
use selectors::matching::*; use selectors::matching::*;
!relations.intersects(AFFECTED_BY_ID_SELECTOR | !relations.intersects(AFFECTED_BY_ID_SELECTOR |
AFFECTED_BY_PSEUDO_ELEMENTS | AFFECTED_BY_PSEUDO_ELEMENTS |
AFFECTED_BY_STYLE_ATTRIBUTE | AFFECTED_BY_STYLE_ATTRIBUTE)
AFFECTED_BY_PRESENTATIONAL_HINTS)
} }
/// Whether, given two elements, they have pointer-equal computed values. /// Whether, given two elements, they have pointer-equal computed values.
@ -52,33 +49,24 @@ pub fn same_computed_values<E>(first: Option<E>, second: Option<E>) -> bool
/// We consider not worth to share style with an element that has presentational /// We consider not worth to share style with an element that has presentational
/// hints, both because implementing the code that compares that the hints are /// hints, both because implementing the code that compares that the hints are
/// equal is somewhat annoying, and also because it'd be expensive enough. /// equal is somewhat annoying, and also because it'd be expensive enough.
pub fn has_presentational_hints<E>(element: E) -> bool pub fn have_same_presentational_hints<E>(
target: &mut StyleSharingTarget<E>,
candidate: &mut StyleSharingCandidate<E>
) -> bool
where E: TElement, where E: TElement,
{ {
let mut hints = ForgetfulSink::new(); target.pres_hints() == candidate.pres_hints()
element.synthesize_presentational_hints_for_legacy_attributes(&mut hints);
!hints.is_empty()
} }
/// Whether a given element has the same class attribute than a given candidate. /// Whether a given element has the same class attribute than a given candidate.
/// ///
/// We don't try to share style across elements with different class attributes. /// We don't try to share style across elements with different class attributes.
pub fn have_same_class<E>(element: E, pub fn have_same_class<E>(target: &mut StyleSharingTarget<E>,
candidate: &mut StyleSharingCandidate<E>) candidate: &mut StyleSharingCandidate<E>)
-> bool -> bool
where E: TElement, where E: TElement,
{ {
// XXX Efficiency here, I'm only validating ideas. target.class_list() == candidate.class_list()
let mut element_class_attributes = vec![];
element.each_class(|c| element_class_attributes.push(c.clone()));
if candidate.class_attributes.is_none() {
let mut attrs = vec![];
candidate.element.each_class(|c| attrs.push(c.clone()));
candidate.class_attributes = Some(attrs)
}
element_class_attributes == *candidate.class_attributes.as_ref().unwrap()
} }
/// Compare element and candidate state, but ignore visitedness. Styles don't /// Compare element and candidate state, but ignore visitedness. Styles don't
@ -102,50 +90,20 @@ pub fn have_same_state_ignoring_visitedness<E>(element: E,
/// :first-child, etc, or on attributes that we don't check off-hand (pretty /// :first-child, etc, or on attributes that we don't check off-hand (pretty
/// much every attribute selector except `id` and `class`. /// much every attribute selector except `id` and `class`.
#[inline] #[inline]
pub fn revalidate<E>(element: E, pub fn revalidate<E>(target: &mut StyleSharingTarget<E>,
candidate: &mut StyleSharingCandidate<E>, candidate: &mut StyleSharingCandidate<E>,
shared_context: &SharedStyleContext, shared_context: &SharedStyleContext,
bloom: &BloomFilter, bloom: &BloomFilter,
info: &mut CurrentElementInfo,
selector_flags_map: &mut SelectorFlagsMap<E>) selector_flags_map: &mut SelectorFlagsMap<E>)
-> bool -> bool
where E: TElement, where E: TElement,
{ {
let stylist = &shared_context.stylist; let stylist = &shared_context.stylist;
if info.revalidation_match_results.is_none() { let for_element =
// It's important to set the selector flags. Otherwise, if we succeed in target.revalidation_match_results(stylist, bloom, selector_flags_map);
// sharing the style, we may not set the slow selector flags for the
// right elements (which may not necessarily be |element|), causing
// missed restyles after future DOM mutations.
//
// Gecko's test_bug534804.html exercises this. A minimal testcase is:
// <style> #e:empty + span { ... } </style>
// <span id="e">
// <span></span>
// </span>
// <span></span>
//
// The style sharing cache will get a hit for the second span. When the
// child span is subsequently removed from the DOM, missing selector
// flags would cause us to miss the restyle on the second span.
let mut set_selector_flags = |el: &E, flags: ElementSelectorFlags| {
element.apply_selector_flags(selector_flags_map, el, flags);
};
info.revalidation_match_results =
Some(stylist.match_revalidation_selectors(&element, bloom,
&mut set_selector_flags));
}
if candidate.revalidation_match_results.is_none() { let for_candidate = candidate.revalidation_match_results(stylist, bloom);
let results =
stylist.match_revalidation_selectors(&*candidate.element, bloom,
&mut |_, _| {});
candidate.revalidation_match_results = Some(results);
}
let for_element = info.revalidation_match_results.as_ref().unwrap();
let for_candidate = candidate.revalidation_match_results.as_ref().unwrap();
// This assert "ensures", to some extent, that the two candidates have // This assert "ensures", to some extent, that the two candidates have
// matched the same rulehash buckets, and as such, that the bits we're // matched the same rulehash buckets, and as such, that the bits we're

Просмотреть файл

@ -8,14 +8,16 @@
use Atom; use Atom;
use bit_vec::BitVec; use bit_vec::BitVec;
use cache::{LRUCache, LRUCacheMutIterator}; use cache::{LRUCache, LRUCacheMutIterator};
use context::{CurrentElementInfo, SelectorFlagsMap, SharedStyleContext}; use context::{SelectorFlagsMap, SharedStyleContext, StyleContext};
use data::{ComputedStyle, ElementData, ElementStyles}; use data::{ComputedStyle, ElementData, ElementStyles};
use dom::{TElement, SendElement}; use dom::{TElement, SendElement};
use matching::{ChildCascadeRequirement, MatchMethods}; use matching::{ChildCascadeRequirement, MatchMethods};
use properties::ComputedValues; use properties::ComputedValues;
use selectors::bloom::BloomFilter; use selectors::bloom::BloomFilter;
use selectors::matching::StyleRelations; use selectors::matching::{ElementSelectorFlags, StyleRelations};
use sink::ForgetfulSink; use smallvec::SmallVec;
use std::ops::Deref;
use stylist::{ApplicableDeclarationBlock, Stylist};
mod checks; mod checks;
@ -32,23 +34,127 @@ pub enum StyleSharingBehavior {
Disallow, Disallow,
} }
/// Some data we want to avoid recomputing all the time while trying to share
/// style.
#[derive(Debug)]
pub struct ValidationData {
/// The class list of this element.
///
/// TODO(emilio): See if it's worth to sort them, or doing something else in
/// a similar fashion as what Boris is doing for the ID attribute.
class_list: Option<SmallVec<[Atom; 5]>>,
/// The list of presentational attributes of the element.
pres_hints: Option<SmallVec<[ApplicableDeclarationBlock; 5]>>,
/// The cached result of matching this entry against the revalidation
/// selectors.
revalidation_match_results: Option<BitVec>,
}
impl ValidationData {
/// Trivially construct an empty `ValidationData` with nothing on
/// it.
pub fn new() -> Self {
Self {
class_list: None,
pres_hints: None,
revalidation_match_results: None,
}
}
/// Move the cached data to a new instance, and return it.
pub fn take(&mut self) -> Self {
Self {
class_list: self.class_list.take(),
pres_hints: self.pres_hints.take(),
revalidation_match_results: self.revalidation_match_results.take(),
}
}
/// Get or compute the list of presentational attributes associated with
/// this element.
pub fn pres_hints<E>(&mut self, element: E) -> &[ApplicableDeclarationBlock]
where E: TElement,
{
if self.pres_hints.is_none() {
let mut pres_hints = SmallVec::new();
element.synthesize_presentational_hints_for_legacy_attributes(&mut pres_hints);
self.pres_hints = Some(pres_hints);
}
&*self.pres_hints.as_ref().unwrap()
}
/// Get or compute the class-list associated with this element.
pub fn class_list<E>(&mut self, element: E) -> &[Atom]
where E: TElement,
{
if self.class_list.is_none() {
let mut class_list = SmallVec::new();
element.each_class(|c| class_list.push(c.clone()));
self.class_list = Some(class_list);
}
&*self.class_list.as_ref().unwrap()
}
/// Computes the revalidation results if needed, and returns it.
fn revalidation_match_results<E, F>(
&mut self,
element: E,
stylist: &Stylist,
bloom: &BloomFilter,
flags_setter: &mut F
) -> &BitVec
where E: TElement,
F: FnMut(&E, ElementSelectorFlags),
{
if self.revalidation_match_results.is_none() {
self.revalidation_match_results =
Some(stylist.match_revalidation_selectors(&element, bloom,
flags_setter));
}
self.revalidation_match_results.as_ref().unwrap()
}
}
/// Information regarding a style sharing candidate, that is, an entry in the /// Information regarding a style sharing candidate, that is, an entry in the
/// style sharing cache. /// style sharing cache.
/// ///
/// Note that this information is stored in TLS and cleared after the traversal, /// Note that this information is stored in TLS and cleared after the traversal,
/// and once here, the style information of the element is immutable, so it's /// and once here, the style information of the element is immutable, so it's
/// safe to access. /// safe to access.
///
/// TODO: We can stick a lot more info here.
#[derive(Debug)] #[derive(Debug)]
pub struct StyleSharingCandidate<E: TElement> { pub struct StyleSharingCandidate<E: TElement> {
/// The element. We use SendElement here so that the cache may live in /// The element. We use SendElement here so that the cache may live in
/// ScopedTLS. /// ScopedTLS.
element: SendElement<E>, element: SendElement<E>,
/// The cached class names. validation_data: ValidationData,
class_attributes: Option<Vec<Atom>>, }
/// The cached result of matching this entry against the revalidation selectors.
revalidation_match_results: Option<BitVec>, impl<E: TElement> StyleSharingCandidate<E> {
/// Get the classlist of this candidate.
fn class_list(&mut self) -> &[Atom] {
self.validation_data.class_list(*self.element)
}
/// Get the pres hints of this candidate.
fn pres_hints(&mut self) -> &[ApplicableDeclarationBlock] {
self.validation_data.pres_hints(*self.element)
}
/// Get the classlist of this candidate.
fn revalidation_match_results(
&mut self,
stylist: &Stylist,
bloom: &BloomFilter,
) -> &BitVec {
self.validation_data.revalidation_match_results(
*self.element,
stylist,
bloom,
&mut |_, _| {})
}
} }
impl<E: TElement> PartialEq<StyleSharingCandidate<E>> for StyleSharingCandidate<E> { impl<E: TElement> PartialEq<StyleSharingCandidate<E>> for StyleSharingCandidate<E> {
@ -57,6 +163,102 @@ impl<E: TElement> PartialEq<StyleSharingCandidate<E>> for StyleSharingCandidate<
} }
} }
/// An element we want to test against the style sharing cache.
pub struct StyleSharingTarget<E: TElement> {
element: E,
validation_data: ValidationData,
}
impl<E: TElement> Deref for StyleSharingTarget<E> {
type Target = E;
fn deref(&self) -> &Self::Target {
&self.element
}
}
impl<E: TElement> StyleSharingTarget<E> {
/// Trivially construct a new StyleSharingTarget to test against the cache.
pub fn new(element: E) -> Self {
Self {
element: element,
validation_data: ValidationData::new(),
}
}
fn class_list(&mut self) -> &[Atom] {
self.validation_data.class_list(self.element)
}
/// Get the pres hints of this candidate.
fn pres_hints(&mut self) -> &[ApplicableDeclarationBlock] {
self.validation_data.pres_hints(self.element)
}
fn revalidation_match_results(
&mut self,
stylist: &Stylist,
bloom: &BloomFilter,
selector_flags_map: &mut SelectorFlagsMap<E>
) -> &BitVec {
// It's important to set the selector flags. Otherwise, if we succeed in
// sharing the style, we may not set the slow selector flags for the
// right elements (which may not necessarily be |element|), causing
// missed restyles after future DOM mutations.
//
// Gecko's test_bug534804.html exercises this. A minimal testcase is:
// <style> #e:empty + span { ... } </style>
// <span id="e">
// <span></span>
// </span>
// <span></span>
//
// The style sharing cache will get a hit for the second span. When the
// child span is subsequently removed from the DOM, missing selector
// flags would cause us to miss the restyle on the second span.
let element = self.element;
let mut set_selector_flags = |el: &E, flags: ElementSelectorFlags| {
element.apply_selector_flags(selector_flags_map, el, flags);
};
self.validation_data.revalidation_match_results(
self.element,
stylist,
bloom,
&mut set_selector_flags)
}
/// Attempts to share a style with another node.
pub fn share_style_if_possible(
mut self,
context: &mut StyleContext<E>,
data: &mut ElementData)
-> StyleSharingResult
{
use std::mem;
let shared_context = &context.shared;
let selector_flags_map = &mut context.thread_local.selector_flags;
let bloom_filter = context.thread_local.bloom_filter.filter();
let result = context.thread_local
.style_sharing_candidate_cache
.share_style_if_possible(shared_context,
selector_flags_map,
bloom_filter,
&mut self,
data);
mem::swap(&mut self.validation_data,
&mut context
.thread_local
.current_element_info.as_mut().unwrap()
.validation_data);
result
}
}
/// A cache miss result. /// A cache miss result.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum CacheMiss { pub enum CacheMiss {
@ -133,7 +335,9 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
element: &E, element: &E,
style: &ComputedValues, style: &ComputedValues,
relations: StyleRelations, relations: StyleRelations,
revalidation_match_results: Option<BitVec>) { mut validation_data: ValidationData) {
use selectors::matching::AFFECTED_BY_PRESENTATIONAL_HINTS;
let parent = match element.parent_element() { let parent = match element.parent_element() {
Some(element) => element, Some(element) => element,
None => { None => {
@ -154,14 +358,6 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
return; return;
} }
// Make sure we noted any presentational hints in the StyleRelations.
if cfg!(debug_assertions) {
let mut hints = ForgetfulSink::new();
element.synthesize_presentational_hints_for_legacy_attributes(&mut hints);
debug_assert!(hints.is_empty(),
"Style relations should not be shareable!");
}
let box_style = style.get_box(); let box_style = style.get_box();
if box_style.specifies_transitions() { if box_style.specifies_transitions() {
debug!("Failing to insert to the cache: transitions"); debug!("Failing to insert to the cache: transitions");
@ -173,12 +369,18 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
return; return;
} }
// Take advantage of the information we've learned during
// selector-matching.
if !relations.intersects(AFFECTED_BY_PRESENTATIONAL_HINTS) {
debug_assert!(validation_data.pres_hints.as_ref().map_or(true, |v| v.is_empty()));
validation_data.pres_hints = Some(SmallVec::new());
}
debug!("Inserting into cache: {:?} with parent {:?}", element, parent); debug!("Inserting into cache: {:?} with parent {:?}", element, parent);
self.cache.insert(StyleSharingCandidate { self.cache.insert(StyleSharingCandidate {
element: unsafe { SendElement::new(*element) }, element: unsafe { SendElement::new(*element) },
class_attributes: None, validation_data: validation_data,
revalidation_match_results: revalidation_match_results,
}); });
} }
@ -193,43 +395,39 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
} }
/// Attempts to share a style with another node. /// Attempts to share a style with another node.
/// fn share_style_if_possible(
/// This method is unsafe because it depends on the
/// `style_sharing_candidate_cache` having only live nodes in it, and we
/// have no way to guarantee that at the type system level yet.
pub unsafe fn share_style_if_possible(
&mut self, &mut self,
shared_context: &SharedStyleContext, shared_context: &SharedStyleContext,
current_element_info: &mut CurrentElementInfo,
selector_flags_map: &mut SelectorFlagsMap<E>, selector_flags_map: &mut SelectorFlagsMap<E>,
bloom_filter: &BloomFilter, bloom_filter: &BloomFilter,
element: E, target: &mut StyleSharingTarget<E>,
data: &mut ElementData data: &mut ElementData
) -> StyleSharingResult { ) -> StyleSharingResult {
if shared_context.options.disable_style_sharing_cache { if shared_context.options.disable_style_sharing_cache {
debug!("{:?} Cannot share style: style sharing cache disabled", debug!("{:?} Cannot share style: style sharing cache disabled",
element); target.element);
return StyleSharingResult::CannotShare return StyleSharingResult::CannotShare
} }
if element.parent_element().is_none() { if target.parent_element().is_none() {
debug!("{:?} Cannot share style: element has no parent", element); debug!("{:?} Cannot share style: element has no parent",
target.element);
return StyleSharingResult::CannotShare return StyleSharingResult::CannotShare
} }
if element.is_native_anonymous() { if target.is_native_anonymous() {
debug!("{:?} Cannot share style: NAC", element); debug!("{:?} Cannot share style: NAC", target.element);
return StyleSharingResult::CannotShare; return StyleSharingResult::CannotShare;
} }
if element.style_attribute().is_some() { if target.style_attribute().is_some() {
debug!("{:?} Cannot share style: element has style attribute", debug!("{:?} Cannot share style: element has style attribute",
element); target.element);
return StyleSharingResult::CannotShare return StyleSharingResult::CannotShare
} }
if element.get_id().is_some() { if target.get_id().is_some() {
debug!("{:?} Cannot share style: element has id", element); debug!("{:?} Cannot share style: element has id", target.element);
return StyleSharingResult::CannotShare return StyleSharingResult::CannotShare
} }
@ -237,11 +435,10 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
for (i, candidate) in self.iter_mut().enumerate() { for (i, candidate) in self.iter_mut().enumerate() {
let sharing_result = let sharing_result =
Self::test_candidate( Self::test_candidate(
element, target,
candidate, candidate,
&shared_context, &shared_context,
bloom_filter, bloom_filter,
current_element_info,
selector_flags_map selector_flags_map
); );
@ -254,7 +451,7 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
let old_values = data.get_styles_mut() let old_values = data.get_styles_mut()
.and_then(|s| s.primary.values.take()); .and_then(|s| s.primary.values.take());
let child_cascade_requirement = let child_cascade_requirement =
element.accumulate_damage( target.accumulate_damage(
&shared_context, &shared_context,
data.get_restyle_mut(), data.get_restyle_mut(),
old_values.as_ref().map(|v| &**v), old_values.as_ref().map(|v| &**v),
@ -293,7 +490,7 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
} }
} }
debug!("{:?} Cannot share style: {} cache entries", element, debug!("{:?} Cannot share style: {} cache entries", target.element,
self.cache.num_entries()); self.cache.num_entries());
if should_clear_cache { if should_clear_cache {
@ -303,11 +500,10 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
StyleSharingResult::CannotShare StyleSharingResult::CannotShare
} }
fn test_candidate(element: E, fn test_candidate(target: &mut StyleSharingTarget<E>,
candidate: &mut StyleSharingCandidate<E>, candidate: &mut StyleSharingCandidate<E>,
shared: &SharedStyleContext, shared: &SharedStyleContext,
bloom: &BloomFilter, bloom: &BloomFilter,
info: &mut CurrentElementInfo,
selector_flags_map: &mut SelectorFlagsMap<E>) selector_flags_map: &mut SelectorFlagsMap<E>)
-> Result<ComputedStyle, CacheMiss> { -> Result<ComputedStyle, CacheMiss> {
macro_rules! miss { macro_rules! miss {
@ -319,66 +515,66 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
// Check that we have the same parent, or at least the same pointer // Check that we have the same parent, or at least the same pointer
// identity for parent computed style. The latter check allows us to // identity for parent computed style. The latter check allows us to
// share style between cousins if the parents shared style. // share style between cousins if the parents shared style.
let parent = element.parent_element(); let parent = target.parent_element();
let candidate_parent = candidate.element.parent_element(); let candidate_parent = candidate.element.parent_element();
if parent != candidate_parent && if parent != candidate_parent &&
!checks::same_computed_values(parent, candidate_parent) { !checks::same_computed_values(parent, candidate_parent) {
miss!(Parent) miss!(Parent)
} }
if element.is_native_anonymous() { if target.is_native_anonymous() {
debug_assert!(!candidate.element.is_native_anonymous(), debug_assert!(!candidate.element.is_native_anonymous(),
"Why inserting NAC into the cache?"); "Why inserting NAC into the cache?");
miss!(NativeAnonymousContent) miss!(NativeAnonymousContent)
} }
if *element.get_local_name() != *candidate.element.get_local_name() { if *target.get_local_name() != *candidate.element.get_local_name() {
miss!(LocalName) miss!(LocalName)
} }
if *element.get_namespace() != *candidate.element.get_namespace() { if *target.get_namespace() != *candidate.element.get_namespace() {
miss!(Namespace) miss!(Namespace)
} }
if element.is_link() != candidate.element.is_link() { if target.is_link() != candidate.element.is_link() {
miss!(Link) miss!(Link)
} }
if element.matches_user_and_author_rules() != if target.matches_user_and_author_rules() !=
candidate.element.matches_user_and_author_rules() { candidate.element.matches_user_and_author_rules() {
miss!(UserAndAuthorRules) miss!(UserAndAuthorRules)
} }
if !checks::have_same_state_ignoring_visitedness(element, candidate) { if !checks::have_same_state_ignoring_visitedness(target.element, candidate) {
miss!(State) miss!(State)
} }
if element.get_id() != candidate.element.get_id() { if target.get_id() != candidate.element.get_id() {
miss!(IdAttr) miss!(IdAttr)
} }
if element.style_attribute().is_some() { if target.style_attribute().is_some() {
miss!(StyleAttr) miss!(StyleAttr)
} }
if !checks::have_same_class(element, candidate) { if !checks::have_same_class(target, candidate) {
miss!(Class) miss!(Class)
} }
if checks::has_presentational_hints(element) { if !checks::have_same_presentational_hints(target, candidate) {
miss!(PresHints) miss!(PresHints)
} }
if !checks::revalidate(element, candidate, shared, bloom, info, if !checks::revalidate(target, candidate, shared, bloom,
selector_flags_map) { selector_flags_map) {
miss!(Revalidation) miss!(Revalidation)
} }
let data = candidate.element.borrow_data().unwrap(); let data = candidate.element.borrow_data().unwrap();
debug_assert!(element.has_current_styles(&data)); debug_assert!(target.has_current_styles(&data));
debug!("Sharing style between {:?} and {:?}", debug!("Sharing style between {:?} and {:?}",
element, candidate.element); target.element, candidate.element);
Ok(data.styles().primary.clone()) Ok(data.styles().primary.clone())
} }
} }

Просмотреть файл

@ -1397,7 +1397,7 @@ impl Rule {
/// This represents the declarations in a given declaration block for a given /// This represents the declarations in a given declaration block for a given
/// importance. /// importance.
#[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub struct ApplicableDeclarationBlock { pub struct ApplicableDeclarationBlock {
/// The style source, either a style rule, or a property declaration block. /// The style source, either a style rule, or a property declaration block.
#[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")] #[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")]

Просмотреть файл

@ -11,7 +11,7 @@ use dom::{DirtyDescendants, NodeInfo, OpaqueNode, TElement, TNode};
use matching::{ChildCascadeRequirement, MatchMethods}; use matching::{ChildCascadeRequirement, MatchMethods};
use restyle_hints::{HintComputationContext, RestyleHint}; use restyle_hints::{HintComputationContext, RestyleHint};
use selector_parser::RestyleDamage; use selector_parser::RestyleDamage;
use sharing::StyleSharingBehavior; use sharing::{StyleSharingBehavior, StyleSharingTarget};
#[cfg(feature = "servo")] use servo_config::opts; #[cfg(feature = "servo")] use servo_config::opts;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::borrow::BorrowMut; use std::borrow::BorrowMut;
@ -800,9 +800,9 @@ fn compute_style<E, D>(_traversal: &D,
// First, try the style sharing cache. If we get a match we can skip the rest // First, try the style sharing cache. If we get a match we can skip the rest
// of the work. // of the work.
if let MatchAndCascade = kind { if let MatchAndCascade = kind {
let sharing_result = unsafe { let target = StyleSharingTarget::new(element);
element.share_style_if_possible(context, data) let sharing_result = target.share_style_if_possible(context, data);
};
if let StyleWasShared(index, had_damage) = sharing_result { if let StyleWasShared(index, had_damage) = sharing_result {
context.thread_local.statistics.styles_shared += 1; context.thread_local.statistics.styles_shared += 1;
context.thread_local.style_sharing_candidate_cache.touch(index); context.thread_local.style_sharing_candidate_cache.touch(index);