Auto merge of #10815 - emilio:anonbox-gcs, r=SimonSapin,bholley

style: Support anonymous box pseudo-elements

This is a work-in-progress that:

 * Adds support for some pseudo-elements to skip the cascade entirely, in an analogous way to Gecko's anonymous box pseudo-elements.
 * Takes rid of `StylistWrapper`, and uses `Arc::get_mut` instead.
 * Uses the first bullet to precompute the `-servo-details-content` pseudo's style.

I'd like @bholley to take a look before following, do you think that the aproach is the correct?
Also, @SimonSapin could want to put some eyes on it.

Depends on https://github.com/servo/rust-selectors/pull/81

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/10815)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2016-04-29 14:27:16 -07:00
commit 407f991c8a
33 changed files with 762 additions and 336 deletions

View file

@ -16,12 +16,6 @@ use std::collections::HashMap;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex, RwLock};
pub struct StylistWrapper<Impl: SelectorImplExt>(pub *const Stylist<Impl>);
// FIXME(#6569) This implementation is unsound.
#[allow(unsafe_code)]
unsafe impl<Impl: SelectorImplExt> Sync for StylistWrapper<Impl> {}
pub struct SharedStyleContext<Impl: SelectorImplExt> {
/// The current viewport size.
pub viewport_size: Size2D<Au>,
@ -30,9 +24,7 @@ pub struct SharedStyleContext<Impl: SelectorImplExt> {
pub screen_size_changed: bool,
/// The CSS selector stylist.
///
/// FIXME(#2604): Make this no longer an unsafe pointer once we have fast `RWArc`s.
pub stylist: StylistWrapper<Impl>,
pub stylist: Arc<Stylist<Impl>>,
/// Starts at zero, and increased by one every time a layout completes.
/// This can be used to easily check for invalid stale data.
@ -60,10 +52,9 @@ pub struct LocalStyleContext<C: ComputedValues> {
pub style_sharing_candidate_cache: RefCell<StyleSharingCandidateCache<C>>,
}
pub trait StyleContext<'a, Impl: SelectorImplExt, C: ComputedValues> {
pub trait StyleContext<'a, Impl: SelectorImplExt> {
fn shared_context(&self) -> &'a SharedStyleContext<Impl>;
fn local_context(&self) -> &LocalStyleContext<C>;
fn local_context(&self) -> &LocalStyleContext<Impl::ComputedValues>;
}
/// Why we're doing reflow.
@ -74,4 +65,3 @@ pub enum ReflowGoal {
/// We're reflowing in order to satisfy a script query. No display list will be created.
ForScriptQuery,
}

View file

@ -4,11 +4,12 @@
#![allow(unsafe_code)]
use context::SharedStyleContext;
use data::PrivateStyleData;
use element_state::ElementState;
use properties::{ComputedValues, PropertyDeclaration, PropertyDeclarationBlock};
use restyle_hints::{ElementSnapshot, RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint};
use selector_impl::ElementExt;
use selector_impl::{ElementExt, SelectorImplExt};
use selectors::Element;
use selectors::matching::DeclarationBlock;
use smallvec::VecLike;
@ -137,19 +138,19 @@ pub trait TNode : Sized + Copy + Clone {
#[inline(always)]
unsafe fn borrow_data_unchecked(&self)
-> Option<*const PrivateStyleData<<Self::ConcreteElement as Element>::Impl,
Self::ConcreteComputedValues>>;
Self::ConcreteComputedValues>>;
/// Borrows the PrivateStyleData immutably. Fails on a conflicting borrow.
#[inline(always)]
fn borrow_data(&self)
-> Option<Ref<PrivateStyleData<<Self::ConcreteElement as Element>::Impl,
Self::ConcreteComputedValues>>>;
Self::ConcreteComputedValues>>>;
/// Borrows the PrivateStyleData mutably. Fails on a conflicting borrow.
#[inline(always)]
fn mutate_data(&self)
-> Option<RefMut<PrivateStyleData<<Self::ConcreteElement as Element>::Impl,
Self::ConcreteComputedValues>>>;
Self::ConcreteComputedValues>>>;
/// Get the description of how to account for recent style changes.
fn restyle_damage(self) -> Self::ConcreteRestyleDamage;
@ -170,7 +171,10 @@ pub trait TNode : Sized + Copy + Clone {
/// Returns the style results for the given node. If CSS selector matching
/// has not yet been performed, fails.
fn style(&self) -> Ref<Arc<Self::ConcreteComputedValues>> {
fn style(&self,
_context: &SharedStyleContext<<Self::ConcreteElement as Element>::Impl>)
-> Ref<Arc<Self::ConcreteComputedValues>>
where <Self::ConcreteElement as Element>::Impl: SelectorImplExt<ComputedValues=Self::ConcreteComputedValues> {
Ref::map(self.borrow_data().unwrap(), |data| data.style.as_ref().unwrap())
}

View file

@ -136,7 +136,7 @@ impl<'a, E> Element for ElementWrapper<'a, E>
fn get_local_name(&self) -> &Atom {
self.element.get_local_name()
}
fn get_namespace<'b>(&self) -> &Namespace {
fn get_namespace(&self) -> &Namespace {
self.element.get_namespace()
}
fn get_id(&self) -> Option<Atom> {

View file

@ -2,6 +2,7 @@
* 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 element_state::ElementState;
use properties::{self, ServoComputedValues};
use selector_matching::{USER_OR_USER_AGENT_STYLESHEETS, QUIRKS_MODE_STYLESHEET};
use selectors::Element;
use selectors::parser::{ParserContext, SelectorImpl};
@ -12,9 +13,51 @@ pub trait ElementExt: Element {
}
pub trait SelectorImplExt : SelectorImpl + Sized {
fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F)
type ComputedValues: properties::ComputedValues;
fn each_pseudo_element<F>(mut fun: F)
where F: FnMut(<Self as SelectorImpl>::PseudoElement);
/// This function determines if a pseudo-element is eagerly cascaded or not.
///
/// Eagerly cascaded pseudo-elements are "normal" pseudo-elements (i.e.
/// `::before` and `::after`). They inherit styles normally as another
/// selector would do.
///
/// Non-eagerly cascaded ones skip the cascade process entirely, mostly as
/// an optimisation since they are private pseudo-elements (like
/// `::-servo-details-content`). This pseudo-elements are resolved on the
/// fly using global rules (rules of the form `*|*`), and applying them to
/// the parent style.
///
/// If you're implementing a public selector that the end-user might
/// customize, then you probably need doing the whole cascading process and
/// return true in this function for that pseudo.
///
/// But if you are implementing a private pseudo-element, please consider if
/// it might be possible to skip the cascade for it.
fn is_eagerly_cascaded_pseudo_element(pseudo: &<Self as SelectorImpl>::PseudoElement) -> bool;
#[inline]
fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F)
where F: FnMut(<Self as SelectorImpl>::PseudoElement) {
Self::each_pseudo_element(|pseudo| {
if Self::is_eagerly_cascaded_pseudo_element(&pseudo) {
fun(pseudo)
}
})
}
#[inline]
fn each_non_eagerly_cascaded_pseudo_element<F>(mut fun: F)
where F: FnMut(<Self as SelectorImpl>::PseudoElement) {
Self::each_pseudo_element(|pseudo| {
if !Self::is_eagerly_cascaded_pseudo_element(&pseudo) {
fun(pseudo)
}
})
}
fn pseudo_class_state_flag(pc: &Self::NonTSPseudoClass) -> ElementState;
fn get_user_or_user_agent_stylesheets() -> &'static [Stylesheet<Self>];
@ -31,6 +74,19 @@ pub enum PseudoElement {
DetailsContent,
}
impl PseudoElement {
#[inline]
pub fn is_eagerly_cascaded(&self) -> bool {
match *self {
PseudoElement::Before |
PseudoElement::After |
PseudoElement::Selection |
PseudoElement::DetailsSummary => true,
PseudoElement::DetailsContent => false,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, HeapSizeOf, Hash)]
pub enum NonTSPseudoClass {
AnyLink,
@ -112,15 +168,17 @@ impl SelectorImpl for ServoSelectorImpl {
"before" => Before,
"after" => After,
"selection" => Selection,
"-servo-details-summary" => if context.in_user_agent_stylesheet {
"-servo-details-summary" => {
if !context.in_user_agent_stylesheet {
return Err(())
}
DetailsSummary
} else {
return Err(())
},
"-servo-details-content" => if context.in_user_agent_stylesheet {
"-servo-details-content" => {
if !context.in_user_agent_stylesheet {
return Err(())
}
DetailsContent
} else {
return Err(())
},
_ => return Err(())
};
@ -129,15 +187,16 @@ impl SelectorImpl for ServoSelectorImpl {
}
}
impl<E: Element<Impl=ServoSelectorImpl>> ElementExt for E {
fn is_link(&self) -> bool {
self.match_non_ts_pseudo_class(NonTSPseudoClass::AnyLink)
}
}
impl SelectorImplExt for ServoSelectorImpl {
type ComputedValues = ServoComputedValues;
#[inline]
fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F)
fn is_eagerly_cascaded_pseudo_element(pseudo: &PseudoElement) -> bool {
pseudo.is_eagerly_cascaded()
}
#[inline]
fn each_pseudo_element<F>(mut fun: F)
where F: FnMut(PseudoElement) {
fun(PseudoElement::Before);
fun(PseudoElement::After);
@ -161,3 +220,9 @@ impl SelectorImplExt for ServoSelectorImpl {
Some(&*QUIRKS_MODE_STYLESHEET)
}
}
impl<E: Element<Impl=ServoSelectorImpl>> ElementExt for E {
fn is_link(&self) -> bool {
self.match_non_ts_pseudo_class(NonTSPseudoClass::AnyLink)
}
}

View file

@ -8,8 +8,9 @@
use dom::TElement;
use element_state::*;
use error_reporting::{ParseErrorReporter, StdoutErrorReporter};
use euclid::Size2D;
use media_queries::{Device, MediaType};
use properties::{PropertyDeclaration, PropertyDeclarationBlock};
use properties::{self, ComputedValues, PropertyDeclaration, PropertyDeclarationBlock};
use restyle_hints::{ElementSnapshot, RestyleHint, DependencySet};
use selector_impl::{SelectorImplExt, ServoSelectorImpl};
use selectors::Element;
@ -116,11 +117,20 @@ pub struct Stylist<Impl: SelectorImplExt> {
/// The current selector maps, after evaluating media
/// rules against the current device.
element_map: PerPseudoElementSelectorMap<Impl>,
/// The selector maps corresponding to a given pseudo-element
/// (depending on the implementation)
pseudos_map: HashMap<Impl::PseudoElement,
PerPseudoElementSelectorMap<Impl>,
BuildHasherDefault<::fnv::FnvHasher>>,
/// Applicable declarations for a given non-eagerly cascaded pseudo-element.
/// These are eagerly computed once, and then used to resolve the new
/// computed values on the fly on layout.
non_eagerly_cascaded_pseudo_element_decls: HashMap<Impl::PseudoElement,
Vec<DeclarationBlock>,
BuildHasherDefault<::fnv::FnvHasher>>,
rules_source_order: usize,
/// Selector dependencies used to compute restyle hints.
@ -138,6 +148,7 @@ impl<Impl: SelectorImplExt> Stylist<Impl> {
element_map: PerPseudoElementSelectorMap::new(),
pseudos_map: HashMap::with_hasher(Default::default()),
non_eagerly_cascaded_pseudo_element_decls: HashMap::with_hasher(Default::default()),
rules_source_order: 0,
state_deps: DependencySet::new(),
};
@ -160,6 +171,11 @@ impl<Impl: SelectorImplExt> Stylist<Impl> {
self.element_map = PerPseudoElementSelectorMap::new();
self.pseudos_map = HashMap::with_hasher(Default::default());
Impl::each_eagerly_cascaded_pseudo_element(|pseudo| {
self.pseudos_map.insert(pseudo, PerPseudoElementSelectorMap::new());
});
self.non_eagerly_cascaded_pseudo_element_decls = HashMap::with_hasher(Default::default());
self.rules_source_order = 0;
self.state_deps.clear();
@ -182,8 +198,7 @@ impl<Impl: SelectorImplExt> Stylist<Impl> {
}
fn add_stylesheet(&mut self, stylesheet: &Stylesheet<Impl>) {
let device = &self.device;
if !stylesheet.is_effective_for_device(device) {
if !stylesheet.is_effective_for_device(&self.device) {
return;
}
let mut rules_source_order = self.rules_source_order;
@ -195,20 +210,21 @@ impl<Impl: SelectorImplExt> Stylist<Impl> {
if !$style_rule.declarations.$priority.is_empty() {
for selector in &$style_rule.selectors {
let map = if let Some(ref pseudo) = selector.pseudo_element {
self.pseudos_map.entry(pseudo.clone())
.or_insert_with(PerPseudoElementSelectorMap::new)
.borrow_for_origin(&stylesheet.origin)
self.pseudos_map
.entry(pseudo.clone())
.or_insert_with(PerPseudoElementSelectorMap::new)
.borrow_for_origin(&stylesheet.origin)
} else {
self.element_map.borrow_for_origin(&stylesheet.origin)
};
map.$priority.insert(Rule {
selector: selector.compound_selectors.clone(),
declarations: DeclarationBlock {
specificity: selector.specificity,
declarations: $style_rule.declarations.$priority.clone(),
source_order: rules_source_order,
},
selector: selector.compound_selectors.clone(),
declarations: DeclarationBlock {
specificity: selector.specificity,
declarations: $style_rule.declarations.$priority.clone(),
source_order: rules_source_order,
},
});
}
}
@ -223,7 +239,40 @@ impl<Impl: SelectorImplExt> Stylist<Impl> {
self.state_deps.note_selector(selector.compound_selectors.clone());
}
}
self.rules_source_order = rules_source_order;
Impl::each_non_eagerly_cascaded_pseudo_element(|pseudo| {
// TODO: Don't precompute this, compute it on demand instead and
// cache it.
//
// This is actually kind of hard, because the stylist is shared
// between threads.
if let Some(map) = self.pseudos_map.remove(&pseudo) {
let mut declarations = vec![];
map.user_agent.normal.get_universal_rules(&mut declarations);
map.user_agent.important.get_universal_rules(&mut declarations);
self.non_eagerly_cascaded_pseudo_element_decls.insert(pseudo, declarations);
}
})
}
pub fn computed_values_for_pseudo(&self,
pseudo: &Impl::PseudoElement,
parent: Option<&Arc<Impl::ComputedValues>>) -> Option<Arc<Impl::ComputedValues>> {
debug_assert!(!Impl::is_eagerly_cascaded_pseudo_element(pseudo));
if let Some(declarations) = self.non_eagerly_cascaded_pseudo_element_decls.get(pseudo) {
let (computed, _) =
properties::cascade::<Impl::ComputedValues>(Size2D::zero(),
&declarations, false,
parent.map(|p| &**p), None,
box StdoutErrorReporter);
Some(Arc::new(computed))
} else {
parent.map(|p| p.clone())
}
}
pub fn compute_restyle_hint<E>(&self, element: &E,
@ -284,20 +333,16 @@ impl<Impl: SelectorImplExt> Stylist<Impl> {
assert!(!self.is_device_dirty);
assert!(style_attribute.is_none() || pseudo_element.is_none(),
"Style attributes do not apply to pseudo-elements");
debug_assert!(pseudo_element.is_none() ||
Impl::is_eagerly_cascaded_pseudo_element(pseudo_element.as_ref().unwrap()));
let map = match pseudo_element {
Some(ref pseudo) => match self.pseudos_map.get(pseudo) {
Some(map) => map,
// TODO(emilio): get non eagerly-cascaded pseudo-element rules here.
// Actually assume there are no rules applicable.
None => return true,
},
Some(ref pseudo) => self.pseudos_map.get(pseudo).unwrap(),
None => &self.element_map,
};
let mut shareable = true;
// Step 1: Normal user-agent rules.
map.user_agent.normal.get_all_matching_rules(element,
parent_bf,

View file

@ -12,5 +12,4 @@ use stylesheets;
pub type Stylesheet = stylesheets::Stylesheet<ServoSelectorImpl>;
pub type PrivateStyleData = data::PrivateStyleData<ServoSelectorImpl, ServoComputedValues>;
pub type Stylist = selector_matching::Stylist<ServoSelectorImpl>;
pub type StylistWrapper = context::StylistWrapper<ServoSelectorImpl>;
pub type SharedStyleContext = context::SharedStyleContext<ServoSelectorImpl>;

View file

@ -123,8 +123,8 @@ pub fn recalc_style_at<'a, N, C>(context: &'a C,
root: OpaqueNode,
node: N)
where N: TNode,
C: StyleContext<'a, <N::ConcreteElement as Element>::Impl, N::ConcreteComputedValues>,
<N::ConcreteElement as Element>::Impl: SelectorImplExt + 'a {
C: StyleContext<'a, <N::ConcreteElement as Element>::Impl>,
<N::ConcreteElement as Element>::Impl: SelectorImplExt<ComputedValues=N::ConcreteComputedValues> + 'a {
// Initialize layout data.
//
// FIXME(pcwalton): Stop allocating here. Ideally this should just be done by the HTML
@ -167,8 +167,9 @@ pub fn recalc_style_at<'a, N, C>(context: &'a C,
let shareable_element = match node.as_element() {
Some(element) => {
// Perform the CSS selector matching.
let stylist = unsafe { &*context.shared_context().stylist.0 };
if element.match_element(stylist,
let stylist = &context.shared_context().stylist;
if element.match_element(&**stylist,
Some(&*bf),
&mut applicable_declarations) {
Some(element)