Bug 1336646 - Apply selector flags during traversal. r=emilio

This commit is contained in:
Bobby Holley 2017-02-04 13:11:02 -08:00
parent 37b8d5231d
commit 9e860df9df
17 changed files with 295 additions and 192 deletions

View file

@ -9,12 +9,13 @@ use animation::Animation;
use app_units::Au;
use bloom::StyleBloom;
use data::ElementData;
use dom::{OpaqueNode, TNode, TElement};
use dom::{OpaqueNode, TNode, TElement, SendElement};
use error_reporting::ParseErrorReporter;
use euclid::Size2D;
use matching::StyleSharingCandidateCache;
use parking_lot::RwLock;
use properties::ComputedValues;
use selectors::matching::ElementSelectorFlags;
use servo_config::opts;
use std::collections::HashMap;
use std::env;
@ -23,6 +24,7 @@ use std::ops::Add;
use std::sync::{Arc, Mutex};
use std::sync::mpsc::Sender;
use stylist::Stylist;
use thread_state;
use timer::Timer;
/// This structure is used to create a local style context from a shared one.
@ -165,6 +167,34 @@ impl TraversalStatistics {
}
}
/// A task to be run in sequential mode on the parent (non-worker) thread. This
/// is used by the style system to queue up work which is not safe to do during
/// the parallel traversal.
pub enum SequentialTask<E: TElement> {
/// Sets selector flags. This is used when we need to set flags on an
/// element that we don't have exclusive access to (i.e. the parent).
SetSelectorFlags(SendElement<E>, ElementSelectorFlags),
}
impl<E: TElement> SequentialTask<E> {
/// Executes this task.
pub fn execute(self) {
use self::SequentialTask::*;
debug_assert!(thread_state::get() == thread_state::LAYOUT);
match self {
SetSelectorFlags(el, flags) => {
unsafe { el.set_selector_flags(flags) };
}
}
}
/// Creates a task to set the selector flags on an element.
pub fn set_selector_flags(el: E, flags: ElementSelectorFlags) -> Self {
use self::SequentialTask::*;
SetSelectorFlags(unsafe { SendElement::new(el) }, flags)
}
}
/// A thread-local style context.
///
/// This context contains data that needs to be used during restyling, but is
@ -178,6 +208,10 @@ pub struct ThreadLocalStyleContext<E: TElement> {
/// A channel on which new animations that have been triggered by style
/// recalculation can be sent.
pub new_animations_sender: Sender<Animation>,
/// A set of tasks to be run (on the parent thread) in sequential mode after
/// the rest of the styling is complete. This is useful for infrequently-needed
/// non-threadsafe operations.
pub tasks: Vec<SequentialTask<E>>,
/// Statistics about the traversal.
pub statistics: TraversalStatistics,
/// Information related to the current element, non-None during processing.
@ -191,6 +225,7 @@ impl<E: TElement> ThreadLocalStyleContext<E> {
style_sharing_candidate_cache: StyleSharingCandidateCache::new(),
bloom_filter: StyleBloom::new(),
new_animations_sender: shared.local_context_creation_data.lock().unwrap().new_animations_sender.clone(),
tasks: Vec::new(),
statistics: TraversalStatistics::default(),
current_element_info: None,
}
@ -222,10 +257,15 @@ impl<E: TElement> ThreadLocalStyleContext<E> {
}
}
#[cfg(debug_assertions)]
impl<E: TElement> Drop for ThreadLocalStyleContext<E> {
fn drop(&mut self) {
debug_assert!(self.current_element_info.is_none());
// Execute any enqueued sequential tasks.
debug_assert!(thread_state::get() == thread_state::LAYOUT);
for task in self.tasks.drain(..) {
task.execute();
}
}
}

View file

@ -14,6 +14,7 @@ use element_state::ElementState;
use parking_lot::RwLock;
use properties::{ComputedValues, PropertyDeclarationBlock};
use selector_parser::{ElementExt, PreExistingComputedValues, PseudoElement};
use selectors::matching::ElementSelectorFlags;
use sink::Push;
use std::fmt;
use std::fmt::Debug;
@ -321,6 +322,19 @@ pub trait TElement : PartialEq + Debug + Sized + Copy + Clone + ElementExt + Pre
/// blockification on this element. (This function exists so that Gecko
/// native anonymous content can opt out of this style fixup.)
fn skip_root_and_item_based_display_fixup(&self) -> bool;
/// Sets selector flags, which indicate what kinds of selectors may have
/// matched on this element and therefore what kind of work may need to
/// be performed when DOM state changes.
///
/// This is unsafe, like all the flag-setting methods, because it's only safe
/// to call with exclusive access to the element. When setting flags on the
/// parent during parallel traversal, we use SequentialTask to queue up the
/// set to run after the threads join.
unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags);
/// Returns true if the element has all the specified selector flags.
fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool;
}
/// TNode and TElement aren't Send because we want to be careful and explicit

View file

@ -46,6 +46,7 @@ use properties::PropertyDeclarationBlock;
use rule_tree::CascadeLevel as ServoCascadeLevel;
use selector_parser::{ElementExt, Snapshot};
use selectors::Element;
use selectors::matching::ElementSelectorFlags;
use selectors::parser::{AttrSelector, NamespaceConstraint};
use servo_url::ServoUrl;
use sink::Push;
@ -379,6 +380,29 @@ lazy_static! {
};
}
/// Converts flags from the layout used by rust-selectors to the layout used
/// by Gecko. We could align these and then do this without conditionals, but
/// it's probably not worth the trouble.
fn selector_flags_to_node_flags(flags: ElementSelectorFlags) -> u32 {
use gecko_bindings::structs::*;
use selectors::matching::*;
let mut gecko_flags = 0u32;
if flags.contains(HAS_SLOW_SELECTOR) {
gecko_flags |= NODE_HAS_SLOW_SELECTOR as u32;
}
if flags.contains(HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
gecko_flags |= NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS as u32;
}
if flags.contains(HAS_EDGE_CHILD_SELECTOR) {
gecko_flags |= NODE_HAS_EDGE_CHILD_SELECTOR as u32;
}
if flags.contains(HAS_EMPTY_SELECTOR) {
gecko_flags |= NODE_HAS_EMPTY_SELECTOR as u32;
}
gecko_flags
}
impl<'le> TElement for GeckoElement<'le> {
type ConcreteNode = GeckoNode<'le>;
@ -476,6 +500,16 @@ impl<'le> TElement for GeckoElement<'le> {
// are NAC handles both cases.
self.flags() & (NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE as u32) != 0
}
unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags) {
debug_assert!(!flags.is_empty());
self.set_flags(selector_flags_to_node_flags(flags));
}
fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
let node_flags = selector_flags_to_node_flags(flags);
(self.flags() & node_flags) == node_flags
}
}
impl<'le> PartialEq for GeckoElement<'le> {

View file

@ -12,7 +12,7 @@ use animation::{self, Animation, PropertyAnimation};
use atomic_refcell::AtomicRefMut;
use cache::LRUCache;
use cascade_info::CascadeInfo;
use context::{SharedStyleContext, StyleContext};
use context::{SequentialTask, SharedStyleContext, StyleContext};
use data::{ComputedStyle, ElementData, ElementStyles, PseudoRuleNodes, PseudoStyles};
use dom::{SendElement, TElement, TNode};
use properties::{CascadeFlags, ComputedValues, SHAREABLE, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, cascade};
@ -22,7 +22,8 @@ use rule_tree::{CascadeLevel, StrongRuleNode};
use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
use selectors::MatchAttr;
use selectors::bloom::BloomFilter;
use selectors::matching::{AFFECTED_BY_PSEUDO_ELEMENTS, AFFECTED_BY_STYLE_ATTRIBUTE, MatchingReason, StyleRelations};
use selectors::matching::{AFFECTED_BY_PSEUDO_ELEMENTS, AFFECTED_BY_STYLE_ATTRIBUTE};
use selectors::matching::{ElementSelectorFlags, StyleRelations};
use servo_config::opts;
use sink::ForgetfulSink;
use std::collections::HashMap;
@ -586,6 +587,7 @@ pub trait MatchMethods : TElement {
let stylist = &context.shared.stylist;
let style_attribute = self.style_attribute();
let animation_rules = self.get_animation_rules(None);
let mut flags = ElementSelectorFlags::empty();
// Compute the primary rule node.
let mut primary_relations =
@ -595,7 +597,7 @@ pub trait MatchMethods : TElement {
animation_rules,
None,
&mut applicable_declarations,
MatchingReason::ForStyling);
&mut flags);
let primary_rule_node = compute_rule_node(context, &mut applicable_declarations);
// Compute the pseudo rule nodes.
@ -608,7 +610,7 @@ pub trait MatchMethods : TElement {
None, pseudo_animation_rules,
Some(&pseudo),
&mut applicable_declarations,
MatchingReason::ForStyling);
&mut flags);
if !applicable_declarations.is_empty() {
let rule_node = compute_rule_node(context, &mut applicable_declarations);
@ -621,6 +623,22 @@ pub trait MatchMethods : TElement {
primary_relations |= AFFECTED_BY_PSEUDO_ELEMENTS;
}
// Apply the selector flags.
let self_flags = flags.for_self();
if !self_flags.is_empty() {
unsafe { self.set_selector_flags(self_flags); }
}
let parent_flags = flags.for_parent();
if !parent_flags.is_empty() {
if let Some(p) = self.parent_element() {
// Avoid the overhead of the SequentialTask if the flags are already set.
if !p.has_selector_flags(parent_flags) {
let task = SequentialTask::set_selector_flags(p, parent_flags);
context.thread_local.tasks.push(task);
}
}
}
MatchResults {
primary: primary_rule_node,
relations: primary_relations,

View file

@ -15,7 +15,7 @@ use gecko_bindings::structs::nsRestyleHint;
use heapsize::HeapSizeOf;
use selector_parser::{AttrValue, NonTSPseudoClass, Snapshot, SelectorImpl};
use selectors::{Element, MatchAttr};
use selectors::matching::{MatchingReason, StyleRelations};
use selectors::matching::{ElementSelectorFlags, StyleRelations};
use selectors::matching::matches_complex_selector;
use selectors::parser::{AttrSelector, Combinator, ComplexSelector, SimpleSelector};
use std::clone::Clone;
@ -524,14 +524,16 @@ impl DependencySet {
(attrs_changed && dep.sensitivities.attrs),
"Testing a known ineffective dependency?");
if (attrs_changed || state_changes.intersects(dep.sensitivities.states)) && !hint.intersects(dep.hint) {
// We can ignore the selector flags, since they would have already been set during
// original matching for any element that might change its matching behavior here.
let matched_then =
matches_complex_selector(&dep.selector, snapshot, None,
&mut StyleRelations::empty(),
MatchingReason::Other);
&mut ElementSelectorFlags::empty());
let matches_now =
matches_complex_selector(&dep.selector, element, None,
&mut StyleRelations::empty(),
MatchingReason::Other);
&mut ElementSelectorFlags::empty());
if matched_then != matches_now {
hint.insert(dep.hint);
}

View file

@ -18,12 +18,12 @@ use properties::{self, CascadeFlags, ComputedValues, INHERIT_ALL};
use properties::PropertyDeclarationBlock;
use restyle_hints::{RestyleHint, DependencySet};
use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource};
use selector_parser::{ElementExt, SelectorImpl, PseudoElement, Snapshot};
use selector_parser::{SelectorImpl, PseudoElement, Snapshot};
use selectors::Element;
use selectors::bloom::BloomFilter;
use selectors::matching::{AFFECTED_BY_ANIMATIONS, AFFECTED_BY_TRANSITIONS};
use selectors::matching::{AFFECTED_BY_STYLE_ATTRIBUTE, AFFECTED_BY_PRESENTATIONAL_HINTS};
use selectors::matching::{MatchingReason, StyleRelations, matches_complex_selector};
use selectors::matching::{ElementSelectorFlags, StyleRelations, matches_complex_selector};
use selectors::parser::{Selector, SimpleSelector, LocalName as LocalNameSelector, ComplexSelector};
use sink::Push;
use smallvec::VecLike;
@ -34,6 +34,7 @@ use std::hash::Hash;
use std::sync::Arc;
use style_traits::viewport::ViewportConstraints;
use stylesheets::{CssRule, Origin, StyleRule, Stylesheet, UserAgentStylesheets};
use thread_state;
use viewport::{self, MaybeNew, ViewportRule};
pub use ::fnv::FnvHashMap;
@ -368,7 +369,7 @@ impl Stylist {
parent: &Arc<ComputedValues>,
default: &Arc<ComputedValues>)
-> Option<ComputedStyle>
where E: ElementExt +
where E: TElement +
fmt::Debug +
PresentationalHintsSynthetizer
{
@ -379,13 +380,14 @@ impl Stylist {
let mut declarations = vec![];
let mut flags = ElementSelectorFlags::empty();
self.push_applicable_declarations(element,
None,
None,
AnimationRules(None, None),
Some(pseudo),
&mut declarations,
MatchingReason::ForStyling);
&mut flags);
let rule_node =
self.rule_tree.insert_ordered_rules(
@ -400,6 +402,28 @@ impl Stylist {
Box::new(StdoutErrorReporter),
CascadeFlags::empty());
// Apply the selector flags. We should be in sequential mode already,
// so we can directly apply the parent flags.
if cfg!(feature = "servo") {
// Servo calls this function from the worker, but only for internal
// pseudos, so we should never generate selector flags here.
debug_assert!(flags.is_empty());
} else {
// Gecko calls this from sequential mode, so we can directly apply
// the flags.
debug_assert!(thread_state::get() == thread_state::LAYOUT);
let self_flags = flags.for_self();
if !self_flags.is_empty() {
unsafe { element.set_selector_flags(self_flags); }
}
let parent_flags = flags.for_parent();
if !parent_flags.is_empty() {
if let Some(p) = element.parent_element() {
unsafe { p.set_selector_flags(parent_flags); }
}
}
}
Some(ComputedStyle::new(rule_node, Arc::new(computed)))
}
@ -495,8 +519,8 @@ impl Stylist {
animation_rules: AnimationRules,
pseudo_element: Option<&PseudoElement>,
applicable_declarations: &mut V,
reason: MatchingReason) -> StyleRelations
where E: ElementExt +
flags: &mut ElementSelectorFlags) -> StyleRelations
where E: TElement +
fmt::Debug +
PresentationalHintsSynthetizer,
V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock>
@ -521,7 +545,7 @@ impl Stylist {
parent_bf,
applicable_declarations,
&mut relations,
reason,
flags,
CascadeLevel::UANormal);
debug!("UA normal: {:?}", relations);
@ -545,14 +569,14 @@ impl Stylist {
parent_bf,
applicable_declarations,
&mut relations,
reason,
flags,
CascadeLevel::UserNormal);
debug!("user normal: {:?}", relations);
map.author.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut relations,
reason,
flags,
CascadeLevel::AuthorNormal);
debug!("author normal: {:?}", relations);
@ -586,7 +610,7 @@ impl Stylist {
parent_bf,
applicable_declarations,
&mut relations,
reason,
flags,
CascadeLevel::AuthorImportant);
debug!("author important: {:?}", relations);
@ -609,7 +633,7 @@ impl Stylist {
parent_bf,
applicable_declarations,
&mut relations,
reason,
flags,
CascadeLevel::UserImportant);
debug!("user important: {:?}", relations);
@ -622,7 +646,7 @@ impl Stylist {
parent_bf,
applicable_declarations,
&mut relations,
reason,
flags,
CascadeLevel::UAImportant);
debug!("UA important: {:?}", relations);
@ -663,7 +687,7 @@ impl Stylist {
pub fn match_same_not_common_style_affecting_attributes_rules<E>(&self,
element: &E,
candidate: &E) -> bool
where E: ElementExt,
where E: TElement,
{
use selectors::matching::StyleRelations;
use selectors::matching::matches_complex_selector;
@ -678,11 +702,11 @@ impl Stylist {
let element_matches =
matches_complex_selector(&selector.complex_selector, element,
None, &mut StyleRelations::empty(),
MatchingReason::Other);
&mut ElementSelectorFlags::empty());
let candidate_matches =
matches_complex_selector(&selector.complex_selector, candidate,
None, &mut StyleRelations::empty(),
MatchingReason::Other);
&mut ElementSelectorFlags::empty());
if element_matches != candidate_matches {
return false;
@ -704,7 +728,7 @@ impl Stylist {
pub fn match_same_sibling_affecting_rules<E>(&self,
element: &E,
candidate: &E) -> bool
where E: ElementExt,
where E: TElement,
{
use selectors::matching::StyleRelations;
use selectors::matching::matches_complex_selector;
@ -717,12 +741,12 @@ impl Stylist {
let element_matches =
matches_complex_selector(&selector.complex_selector, element,
None, &mut StyleRelations::empty(),
MatchingReason::Other);
&mut ElementSelectorFlags::empty());
let candidate_matches =
matches_complex_selector(&selector.complex_selector, candidate,
None, &mut StyleRelations::empty(),
MatchingReason::Other);
&mut ElementSelectorFlags::empty());
if element_matches != candidate_matches {
debug!("match_same_sibling_affecting_rules: Failure due to {:?}",
@ -860,7 +884,7 @@ impl SelectorMap {
parent_bf: Option<&BloomFilter>,
matching_rules_list: &mut V,
relations: &mut StyleRelations,
reason: MatchingReason,
flags: &mut ElementSelectorFlags,
cascade_level: CascadeLevel)
where E: Element<Impl=SelectorImpl>,
V: VecLike<ApplicableDeclarationBlock>
@ -878,7 +902,7 @@ impl SelectorMap {
&id,
matching_rules_list,
relations,
reason,
flags,
cascade_level)
}
@ -889,7 +913,7 @@ impl SelectorMap {
class,
matching_rules_list,
relations,
reason,
flags,
cascade_level);
});
@ -904,7 +928,7 @@ impl SelectorMap {
element.get_local_name(),
matching_rules_list,
relations,
reason,
flags,
cascade_level);
SelectorMap::get_matching_rules(element,
@ -912,7 +936,7 @@ impl SelectorMap {
&self.other_rules,
matching_rules_list,
relations,
reason,
flags,
cascade_level);
// Sort only the rules we just added.
@ -963,7 +987,7 @@ impl SelectorMap {
key: &BorrowedStr,
matching_rules: &mut Vector,
relations: &mut StyleRelations,
reason: MatchingReason,
flags: &mut ElementSelectorFlags,
cascade_level: CascadeLevel)
where E: Element<Impl=SelectorImpl>,
Str: Borrow<BorrowedStr> + Eq + Hash,
@ -976,7 +1000,7 @@ impl SelectorMap {
rules,
matching_rules,
relations,
reason,
flags,
cascade_level)
}
}
@ -987,7 +1011,7 @@ impl SelectorMap {
rules: &[Rule],
matching_rules: &mut V,
relations: &mut StyleRelations,
reason: MatchingReason,
flags: &mut ElementSelectorFlags,
cascade_level: CascadeLevel)
where E: Element<Impl=SelectorImpl>,
V: VecLike<ApplicableDeclarationBlock>
@ -1002,7 +1026,7 @@ impl SelectorMap {
};
if any_declaration_for_importance &&
matches_complex_selector(&*rule.selector, element, parent_bf,
relations, reason) {
relations, flags) {
matching_rules.push(
rule.to_applicable_declaration_block(cascade_level));
}