Auto merge of #16778 - emilio:snapshots, r=bholley

Take all the snapshots into account in the style system

See [bug 1355343](https://bugzilla.mozilla.org/show_bug.cgi?id=1355343).

The servo part of this patch presumably needs some polishing, let's see.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/16778)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2017-05-10 15:08:59 -05:00 committed by GitHub
commit c8171ed5d7
22 changed files with 2194 additions and 1137 deletions

12
Cargo.lock generated
View file

@ -164,13 +164,13 @@ dependencies = [
[[package]]
name = "bindgen"
version = "0.24.0"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aster 0.41.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cexpr 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clang-sys 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clang-sys 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.20.5 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
@ -357,7 +357,7 @@ dependencies = [
[[package]]
name = "clang-sys"
version = "0.16.0"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2784,7 +2784,7 @@ version = "0.0.1"
dependencies = [
"app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bindgen 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bindgen 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3383,7 +3383,7 @@ dependencies = [
"checksum base64 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "979d348dc50dfcd050a87df408ec61f01a0a27ee9b4ebdc6085baba8275b2c7f"
"checksum binary-space-partition 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "df65281d9b2b5c332f5bfbd9bb5e5f2e62f627c259cf9dc9cd10fecb758be33d"
"checksum bincode 1.0.0-alpha6 (registry+https://github.com/rust-lang/crates.io-index)" = "fb0cdeac1c5d567fdb487ae5853c024e4acf1ea85ba6a6552fe084e0805fea5d"
"checksum bindgen 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)" = "21a1de90068c1e58dd31b71daab70e1a1e54212b43cc6c4714e7c8acefb28992"
"checksum bindgen 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ccaf8958532d7e570e905266ee2dc1094c3e5c3c3cfc2c299368747a30a5e654"
"checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c"
"checksum bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5b97c2c8e8bbb4251754f559df8af22fb264853c7d009084a576cdf12565089d"
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
@ -3402,7 +3402,7 @@ dependencies = [
"checksum cexpr 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "393a5f0088efbe41f9d1fcd062f24e83c278608420e62109feb2c8abee07de7d"
"checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c"
"checksum cgl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "86765cb42c2a2c497e142af72517c1b4d7ae5bb2f25dfa77a5c69642f2342d89"
"checksum clang-sys 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5f4f6aa0c4cfa318cd4d2940afae57e48b94d44d3aced603501df24f3c2a414f"
"checksum clang-sys 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "33d47b0ea88a529a570490efbb79403e416e89864ce8a96bf23e2a0f23d7e9eb"
"checksum clap 2.20.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7db281b0520e97fbd15cd615dcd8f8bcad0c26f5f7d5effe705f090f39e9a758"
"checksum cmake 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)" = "d18d68987ed4c516dcc3e7913659bfa4076f5182eea4a7e0038bb060953e76ac"
"checksum cocoa 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0a5d0bcb4d345adf9b4ada6c5bb3e2b87c8150b79c46f3f26446de5f4d48de4b"

View file

@ -113,6 +113,7 @@ use style::dom::{ShowSubtree, ShowSubtreeDataAndPrimaryValues, TElement, TNode};
use style::error_reporting::{NullReporter, RustLogReporter};
use style::logical_geometry::LogicalPoint;
use style::media_queries::{Device, MediaList, MediaType};
use style::selector_parser::SnapshotMap;
use style::servo::restyle_damage::{REFLOW, REFLOW_OUT_OF_FLOW, REPAINT, REPOSITION, STORE_OVERFLOW};
use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard, StylesheetGuards};
use style::stylearc::Arc as StyleArc;
@ -509,7 +510,8 @@ impl LayoutThread {
fn build_layout_context<'a>(&self,
guards: StylesheetGuards<'a>,
rw_data: &LayoutThreadData,
request_images: bool)
request_images: bool,
snapshot_map: &'a SnapshotMap)
-> LayoutContext<'a> {
let thread_local_style_context_creation_data =
ThreadLocalStyleContextCreationInfo::new(self.new_animations_sender.clone());
@ -527,6 +529,7 @@ impl LayoutThread {
timer: self.timer.clone(),
quirks_mode: self.quirks_mode.unwrap(),
traversal_flags: TraversalFlags::empty(),
snapshot_map: snapshot_map,
},
image_cache: self.image_cache.clone(),
font_cache_thread: Mutex::new(self.font_cache_thread.clone()),
@ -1111,8 +1114,16 @@ impl LayoutThread {
}
let restyles = document.drain_pending_restyles();
debug!("Draining restyles: {} (needs dirtying? {:?})", restyles.len(), needs_dirtying);
if !needs_dirtying {
debug!("Draining restyles: {} (needs dirtying? {:?})",
restyles.len(), needs_dirtying);
let mut map = SnapshotMap::new();
let elements_with_snapshot: Vec<_> =
restyles
.iter()
.filter(|r| r.1.snapshot.is_some())
.map(|r| r.0)
.collect();
for (el, restyle) in restyles {
// Propagate the descendant bit up the ancestors. Do this before
// the restyle calculation so that we can also do it for new
@ -1121,27 +1132,33 @@ impl LayoutThread {
unsafe { parent.note_dirty_descendant() };
}
// If we haven't styled this node yet, we don't need to track a restyle.
// If we haven't styled this node yet, we don't need to track a
// restyle.
let mut data = match el.mutate_layout_data() {
Some(d) => d,
None => continue,
None => {
unsafe { el.unset_snapshot_flags() };
continue;
}
};
if let Some(s) = restyle.snapshot {
unsafe { el.set_has_snapshot() };
map.insert(el.as_node().opaque(), s);
}
let mut style_data = &mut data.base.style_data;
debug_assert!(style_data.has_current_styles());
let mut restyle_data = style_data.ensure_restyle();
// Stash the data on the element for processing by the style system.
restyle_data.hint = restyle.hint.into();
restyle_data.hint.insert(&restyle.hint.into());
restyle_data.damage = restyle.damage;
if let Some(s) = restyle.snapshot {
restyle_data.snapshot.ensure(move || s);
}
debug!("Noting restyle for {:?}: {:?}", el, restyle_data);
}
}
// Create a layout context for use throughout the following passes.
let mut layout_context = self.build_layout_context(guards.clone(), &*rw_data, true);
let mut layout_context =
self.build_layout_context(guards.clone(), &*rw_data, true, &map);
// NB: Type inference falls apart here for some reason, so we need to be very verbose. :-(
let traversal_driver = if self.parallel_flag && self.parallel_traversal.is_some() {
@ -1152,10 +1169,12 @@ impl LayoutThread {
let traversal = RecalcStyleAndConstructFlows::new(layout_context, traversal_driver);
let token = {
let stylist = &<RecalcStyleAndConstructFlows as
DomTraversal<ServoLayoutElement>>::shared_context(&traversal).stylist;
let context = <RecalcStyleAndConstructFlows as
DomTraversal<ServoLayoutElement>>::shared_context(&traversal);
<RecalcStyleAndConstructFlows as
DomTraversal<ServoLayoutElement>>::pre_traverse(element, stylist, TraversalFlags::empty())
DomTraversal<ServoLayoutElement>>::pre_traverse(element,
context,
TraversalFlags::empty())
};
if token.should_traverse() {
@ -1192,6 +1211,10 @@ impl LayoutThread {
self.root_flow = self.try_get_layout_root(element.as_node());
}
for element in elements_with_snapshot {
unsafe { element.unset_snapshot_flags() }
}
layout_context = traversal.destroy();
if opts::get().dump_style_tree {
@ -1382,7 +1405,11 @@ impl LayoutThread {
author: &author_guard,
ua_or_user: &ua_or_user_guard,
};
let mut layout_context = self.build_layout_context(guards, &*rw_data, false);
let snapshots = SnapshotMap::new();
let mut layout_context = self.build_layout_context(guards,
&*rw_data,
false,
&snapshots);
{
// Perform an abbreviated style recalc that operates without access to the DOM.

View file

@ -144,29 +144,40 @@ pub struct Node {
bitflags! {
#[doc = "Flags for node items."]
#[derive(JSTraceable, HeapSizeOf)]
pub flags NodeFlags: u8 {
pub flags NodeFlags: u16 {
#[doc = "Specifies whether this node is in a document."]
const IS_IN_DOC = 0x01,
const IS_IN_DOC = 1 << 0,
#[doc = "Specifies whether this node needs style recalc on next reflow."]
const HAS_DIRTY_DESCENDANTS = 0x08,
const HAS_DIRTY_DESCENDANTS = 1 << 1,
// TODO: find a better place to keep this (#4105)
// https://critic.hoppipolla.co.uk/showcomment?chain=8873
// Perhaps using a Set in Document?
#[doc = "Specifies whether or not there is an authentic click in progress on \
this element."]
const CLICK_IN_PROGRESS = 0x10,
const CLICK_IN_PROGRESS = 1 << 2,
#[doc = "Specifies whether this node is focusable and whether it is supposed \
to be reachable with using sequential focus navigation."]
const SEQUENTIALLY_FOCUSABLE = 0x20,
const SEQUENTIALLY_FOCUSABLE = 1 << 3,
/// Whether any ancestor is a fragmentation container
const CAN_BE_FRAGMENTED = 0x40,
const CAN_BE_FRAGMENTED = 1 << 4,
#[doc = "Specifies whether this node needs to be dirted when viewport size changed."]
const DIRTY_ON_VIEWPORT_SIZE_CHANGE = 0x80,
const DIRTY_ON_VIEWPORT_SIZE_CHANGE = 1 << 5,
#[doc = "Specifies whether the parser has set an associated form owner for \
this element. Only applicable for form-associatable elements."]
const PARSER_ASSOCIATED_FORM_OWNER = 0x90,
const PARSER_ASSOCIATED_FORM_OWNER = 1 << 6,
/// Whether this element has a snapshot stored due to a style or
/// attribute change.
///
/// See the `style::restyle_hints` module.
const HAS_SNAPSHOT = 1 << 7,
/// Whether this element has already handled the stored snapshot.
const HANDLED_SNAPSHOT = 1 << 8,
}
}
@ -289,7 +300,9 @@ impl Node {
for node in child.traverse_preorder() {
// Out-of-document elements never have the descendants flag set.
node.set_flag(IS_IN_DOC | HAS_DIRTY_DESCENDANTS, false);
node.set_flag(IS_IN_DOC | HAS_DIRTY_DESCENDANTS |
HAS_SNAPSHOT | HANDLED_SNAPSHOT,
false);
}
for node in child.traverse_preorder() {
// This needs to be in its own loop, because unbind_from_tree may

View file

@ -39,6 +39,7 @@ use dom::characterdata::LayoutCharacterDataHelpers;
use dom::document::{Document, LayoutDocumentHelpers, PendingRestyle};
use dom::element::{Element, LayoutElementHelpers, RawLayoutElementHelpers};
use dom::node::{CAN_BE_FRAGMENTED, DIRTY_ON_VIEWPORT_SIZE_CHANGE, HAS_DIRTY_DESCENDANTS, IS_IN_DOC};
use dom::node::{HANDLED_SNAPSHOT, HAS_SNAPSHOT};
use dom::node::{LayoutNodeHelpers, Node};
use dom::text::Text;
use gfx_traits::ByteIndex;
@ -413,6 +414,18 @@ impl<'le> TElement for ServoLayoutElement<'le> {
unsafe { self.as_node().node.get_flag(HAS_DIRTY_DESCENDANTS) }
}
fn has_snapshot(&self) -> bool {
unsafe { self.as_node().node.get_flag(HAS_SNAPSHOT) }
}
fn handled_snapshot(&self) -> bool {
unsafe { self.as_node().node.get_flag(HANDLED_SNAPSHOT) }
}
unsafe fn set_handled_snapshot(&self) {
self.as_node().node.set_flag(HANDLED_SNAPSHOT, true);
}
unsafe fn note_descendants<B: DescendantsBit<Self>>(&self) {
debug_assert!(self.get_data().is_some());
style::dom::raw_note_descendants::<Self, B>(*self);
@ -509,6 +522,14 @@ impl<'le> ServoLayoutElement<'le> {
}
}
pub unsafe fn unset_snapshot_flags(&self) {
self.as_node().node.set_flag(HAS_SNAPSHOT | HANDLED_SNAPSHOT, false);
}
pub unsafe fn set_has_snapshot(&self) {
self.as_node().node.set_flag(HAS_SNAPSHOT, true);
}
// FIXME(bholley): This should be merged with TElement::note_descendants,
// but that requires re-testing and possibly fixing the broken callers given
// the FIXME below, which I don't have time to do right now.

View file

@ -65,6 +65,6 @@ kernel32-sys = "0.2"
[build-dependencies]
lazy_static = "0.2"
log = "0.3"
bindgen = { version = "0.24", optional = true }
bindgen = { version = "0.25", optional = true }
regex = {version = "0.2", optional = true}
walkdir = "1.0"

View file

@ -305,6 +305,7 @@ mod bindings {
.include(add_include("mozilla/ComputedTimingFunction.h"))
.include(add_include("mozilla/Keyframe.h"))
.include(add_include("mozilla/ServoElementSnapshot.h"))
.include(add_include("mozilla/ServoElementSnapshotTable.h"))
.include(add_include("mozilla/dom/Element.h"))
.include(add_include("mozilla/dom/NameSpaceConstants.h"))
.include(add_include("mozilla/LookAndFeel.h"))
@ -329,6 +330,7 @@ mod bindings {
"NS_AUTHOR_SPECIFIED_.*",
"NS_THEME_.*",
"NODE_.*",
"ELEMENT_.*",
"NS_FONT_.*",
"NS_STYLE_.*",
"NS_MATHML_.*",
@ -670,6 +672,7 @@ mod bindings {
"Keyframe",
"ServoBundledURI",
"ServoElementSnapshot",
"ServoElementSnapshotTable",
"SheetParsingMode",
"StyleBasicShape",
"StyleBasicShapeType",

View file

@ -3,7 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! The context within which style is calculated.
#![deny(missing_docs)]
use animation::{Animation, PropertyAnimation};
use app_units::Au;
@ -20,6 +19,7 @@ use font_metrics::FontMetricsProvider;
use matching::StyleSharingCandidateCache;
use parking_lot::RwLock;
#[cfg(feature = "gecko")] use properties::ComputedValues;
use selector_parser::SnapshotMap;
use selectors::matching::ElementSelectorFlags;
#[cfg(feature = "servo")] use servo_config::opts;
use shared_lock::StylesheetGuards;
@ -135,6 +135,9 @@ pub struct SharedStyleContext<'a> {
/// Flags controlling how we traverse the tree.
pub traversal_flags: TraversalFlags,
/// A map with our snapshots in order to handle restyle hints.
pub snapshot_map: &'a SnapshotMap,
}
impl<'a> SharedStyleContext<'a> {

View file

@ -6,19 +6,17 @@
#![deny(missing_docs)]
use context::SharedStyleContext;
use dom::TElement;
use properties::ComputedValues;
use properties::longhands::display::computed_value as display;
use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint};
use rule_tree::StrongRuleNode;
use selector_parser::{EAGER_PSEUDO_COUNT, PseudoElement, RestyleDamage, Snapshot};
use selector_parser::{EAGER_PSEUDO_COUNT, PseudoElement, RestyleDamage};
#[cfg(feature = "servo")] use std::collections::HashMap;
use std::fmt;
#[cfg(feature = "servo")] use std::hash::BuildHasherDefault;
use std::ops::Deref;
use stylearc::Arc;
use stylist::Stylist;
use thread_state;
use traversal::TraversalFlags;
/// The structure that represents the result of style computation. This is
@ -199,9 +197,7 @@ impl StoredRestyleHint {
// In the middle of an animation only restyle, we don't need to
// propagate any restyle hints, and we need to remove ourselves.
if traversal_flags.for_animation_only() {
if self.0.intersects(RestyleHint::for_animations()) {
self.0.remove(RestyleHint::for_animations());
}
return Self::empty();
}
@ -275,55 +271,6 @@ impl From<RestyleHint> for StoredRestyleHint {
}
}
static NO_SNAPSHOT: Option<Snapshot> = None;
/// We really want to store an Option<Snapshot> here, but we can't drop Gecko
/// Snapshots off-main-thread. So we make a convenient little wrapper to provide
/// the semantics of Option<Snapshot>, while deferring the actual drop.
#[derive(Debug, Default)]
pub struct SnapshotOption {
snapshot: Option<Snapshot>,
destroyed: bool,
}
impl SnapshotOption {
/// An empty snapshot.
pub fn empty() -> Self {
SnapshotOption {
snapshot: None,
destroyed: false,
}
}
/// Destroy this snapshot.
pub fn destroy(&mut self) {
self.destroyed = true;
debug_assert!(self.is_none());
}
/// Ensure a snapshot is available and return a mutable reference to it.
pub fn ensure<F: FnOnce() -> Snapshot>(&mut self, create: F) -> &mut Snapshot {
debug_assert!(thread_state::get().is_layout());
if self.is_none() {
self.snapshot = Some(create());
self.destroyed = false;
}
self.snapshot.as_mut().unwrap()
}
}
impl Deref for SnapshotOption {
type Target = Option<Snapshot>;
fn deref(&self) -> &Option<Snapshot> {
if self.destroyed {
&NO_SNAPSHOT
} else {
&self.snapshot
}
}
}
/// Transient data used by the restyle algorithm. This structure is instantiated
/// either before or during restyle traversal, and is cleared at the end of node
/// processing.
@ -350,54 +297,17 @@ pub struct RestyleData {
/// for Servo for now.
#[cfg(feature = "gecko")]
pub damage_handled: RestyleDamage,
/// An optional snapshot of the original state and attributes of the element,
/// from which we may compute additional restyle hints at traversal time.
pub snapshot: SnapshotOption,
}
impl RestyleData {
/// Computes the final restyle hint for this element.
///
/// This expands the snapshot (if any) into a restyle hint, and handles
/// explicit sibling restyle hints from the stored restyle hint.
///
/// Returns true if later siblings must be restyled.
pub fn compute_final_hint<E: TElement>(&mut self,
element: E,
stylist: &Stylist)
-> bool {
let mut hint = self.hint.0;
if let Some(snapshot) = self.snapshot.as_ref() {
hint |= stylist.compute_restyle_hint(&element, snapshot);
}
// If the hint includes a directive for later siblings, strip it out and
// notify the caller to modify the base hint for future siblings.
let later_siblings = hint.contains(RESTYLE_LATER_SIBLINGS);
hint.remove(RESTYLE_LATER_SIBLINGS);
// Insert the hint, overriding the previous hint. This effectively takes
// care of removing the later siblings restyle hint.
self.hint = hint.into();
// Destroy the snapshot.
self.snapshot.destroy();
later_siblings
}
/// Returns true if this RestyleData might invalidate the current style.
pub fn has_invalidations(&self) -> bool {
self.hint.has_self_invalidations() ||
self.recascade ||
self.snapshot.is_some()
self.hint.has_self_invalidations() || self.recascade
}
/// Returns true if this RestyleData might invalidate sibling styles.
pub fn has_sibling_invalidations(&self) -> bool {
self.hint.has_sibling_invalidations() || self.snapshot.is_some()
self.hint.has_sibling_invalidations()
}
/// Returns damage handled.
@ -452,6 +362,51 @@ pub enum RestyleKind {
}
impl ElementData {
/// Computes the final restyle hint for this element, potentially allocating
/// a `RestyleData` if we need to.
///
/// This expands the snapshot (if any) into a restyle hint, and handles
/// explicit sibling restyle hints from the stored restyle hint.
///
/// Returns true if later siblings must be restyled.
pub fn compute_final_hint<E: TElement>(
&mut self,
element: E,
context: &SharedStyleContext)
-> bool
{
debug!("compute_final_hint: {:?}, {:?}",
element,
context.traversal_flags);
let mut hint = match self.get_restyle() {
Some(r) => r.hint.0,
None => RestyleHint::empty(),
};
if element.has_snapshot() && !element.handled_snapshot() {
hint |= context.stylist.compute_restyle_hint(&element, context.snapshot_map);
unsafe { element.set_handled_snapshot() }
debug_assert!(element.handled_snapshot());
}
let empty_hint = hint.is_empty();
// If the hint includes a directive for later siblings, strip it out and
// notify the caller to modify the base hint for future siblings.
let later_siblings = hint.contains(RESTYLE_LATER_SIBLINGS);
hint.remove(RESTYLE_LATER_SIBLINGS);
// Insert the hint, overriding the previous hint. This effectively takes
// care of removing the later siblings restyle hint.
if !empty_hint {
self.ensure_restyle().hint = hint.into();
}
later_siblings
}
/// Trivially construct an ElementData.
pub fn new(existing: Option<ElementStyles>) -> Self {
ElementData {
@ -460,22 +415,21 @@ impl ElementData {
}
}
/// Returns true if this element has a computed styled.
/// Returns true if this element has a computed style.
pub fn has_styles(&self) -> bool {
self.styles.is_some()
}
/// Returns true if this element's style is up-to-date and has no potential
/// invalidation.
pub fn has_current_styles(&self) -> bool {
self.has_styles() &&
self.restyle.as_ref().map_or(true, |r| !r.has_invalidations())
/// Returns whether we have any outstanding style invalidation.
pub fn has_invalidations(&self) -> bool {
self.restyle.as_ref().map_or(false, |r| r.has_invalidations())
}
/// Returns the kind of restyling that we're going to need to do on this
/// element, based of the stored restyle hint.
pub fn restyle_kind(&self) -> RestyleKind {
debug_assert!(!self.has_current_styles(), "Should've stopped earlier");
debug_assert!(!self.has_styles() || self.has_invalidations(),
"Should've stopped earlier");
if !self.has_styles() {
return RestyleKind::MatchAndCascade;
}
@ -527,8 +481,6 @@ impl ElementData {
/// Sets the computed element styles.
pub fn set_styles(&mut self, styles: ElementStyles) {
debug_assert!(self.get_restyle().map_or(true, |r| r.snapshot.is_none()),
"Traversal should have expanded snapshots");
self.styles = Some(styles);
}

View file

@ -385,6 +385,26 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
/// the actual restyle traversal.
fn has_dirty_descendants(&self) -> bool;
/// Returns whether state or attributes that may change style have changed
/// on the element, and thus whether the element has been snapshotted to do
/// restyle hint computation.
fn has_snapshot(&self) -> bool;
/// Returns whether the current snapshot if present has been handled.
fn handled_snapshot(&self) -> bool;
/// Flags this element as having handled already its snapshot.
unsafe fn set_handled_snapshot(&self);
/// Returns whether the element's styles are up-to-date.
fn has_current_styles(&self, data: &ElementData) -> bool {
if self.has_snapshot() && !self.handled_snapshot() {
return false;
}
data.has_styles() && !data.has_invalidations()
}
/// Flags an element and its ancestors with a given `DescendantsBit`.
///
/// TODO(emilio): We call this conservatively from restyle_element_internal

View file

@ -37,6 +37,7 @@ use gecko_bindings::structs::GeckoFontMetrics;
use gecko_bindings::structs::Keyframe;
use gecko_bindings::structs::ServoBundledURI;
use gecko_bindings::structs::ServoElementSnapshot;
use gecko_bindings::structs::ServoElementSnapshotTable;
use gecko_bindings::structs::SheetParsingMode;
use gecko_bindings::structs::StyleBasicShape;
use gecko_bindings::structs::StyleBasicShapeType;
@ -902,8 +903,9 @@ extern "C" {
-> nsChangeHint;
}
extern "C" {
pub fn Gecko_CreateElementSnapshot(element: RawGeckoElementBorrowed)
-> ServoElementSnapshotOwned;
pub fn Gecko_GetElementSnapshot(table: *const ServoElementSnapshotTable,
element: RawGeckoElementBorrowed)
-> *const ServoElementSnapshot;
}
extern "C" {
pub fn Gecko_DropElementSnapshot(snapshot: ServoElementSnapshotOwned);
@ -2171,10 +2173,6 @@ extern "C" {
extern "C" {
pub fn Servo_Shutdown();
}
extern "C" {
pub fn Servo_Element_GetSnapshot(element: RawGeckoElementBorrowed)
-> *mut ServoElementSnapshot;
}
extern "C" {
pub fn Servo_Element_GetStyleRuleList(element: RawGeckoElementBorrowed,
rules:
@ -2215,12 +2213,15 @@ extern "C" {
extern "C" {
pub fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed,
pseudo_tag: *mut nsIAtom,
snapshots:
*const ServoElementSnapshotTable,
set: RawServoStyleSetBorrowed)
-> ServoComputedValuesStrong;
}
extern "C" {
pub fn Servo_TraverseSubtree(root: RawGeckoElementBorrowed,
set: RawServoStyleSetBorrowed,
snapshots: *const ServoElementSnapshotTable,
root_behavior: TraversalRootBehavior,
restyle_behavior: TraversalRestyleBehavior)
-> bool;
@ -2233,6 +2234,8 @@ extern "C" {
RawServoStyleSetBorrowed,
element:
RawGeckoElementBorrowed,
snapshots:
*const ServoElementSnapshotTable,
pseudo_tag:
*mut nsIAtom)
-> ServoComputedValuesStrong;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -16,6 +16,8 @@ use std::fmt;
use std::ptr;
use string_cache::{Atom, Namespace, WeakAtom, WeakNamespace};
pub use gecko::snapshot::SnapshotMap;
/// A representation of a CSS pseudo-element.
///
/// In Gecko, we represent pseudo-elements as plain `Atom`s.

View file

@ -5,61 +5,60 @@
//! A gecko snapshot, that stores the element attributes and state before they
//! change in order to properly calculate restyle hints.
use dom::TElement;
use element_state::ElementState;
use gecko::snapshot_helpers;
use gecko::wrapper::{AttrSelectorHelpers, GeckoElement};
use gecko_bindings::bindings;
use gecko_bindings::structs::ServoElementSnapshot;
use gecko_bindings::structs::ServoElementSnapshotFlags as Flags;
use gecko_bindings::structs::ServoElementSnapshotTable;
use restyle_hints::ElementSnapshot;
use selector_parser::SelectorImpl;
use selectors::parser::AttrSelector;
use std::ptr;
use string_cache::Atom;
/// A snapshot of a Gecko element.
///
/// This is really a Gecko type (see `ServoElementSnapshot.h` in Gecko) we wrap
/// here.
#[derive(Debug)]
pub struct GeckoElementSnapshot(bindings::ServoElementSnapshotOwned);
pub type GeckoElementSnapshot = ServoElementSnapshot;
// FIXME(bholley): Add support for *OwnedConst type, and then we get Sync
// automatically.
unsafe impl Sync for GeckoElementSnapshot {}
/// A map from elements to snapshots for Gecko's style back-end.
pub type SnapshotMap = ServoElementSnapshotTable;
impl SnapshotMap {
/// Gets the snapshot for this element, if any.
///
/// FIXME(emilio): The transmute() business we do here is kind of nasty, but
/// it's a consequence of the map being a OpaqueNode -> Snapshot table in
/// Servo and an Element -> Snapshot table in Gecko.
///
/// We should be able to make this a more type-safe with type annotations by
/// making SnapshotMap a trait and moving the implementations outside, but
/// that's a pain because it implies parameterizing SharedStyleContext.
pub fn get<E: TElement>(&self, element: &E) -> Option<&GeckoElementSnapshot> {
debug_assert!(element.has_snapshot());
impl Drop for GeckoElementSnapshot {
fn drop(&mut self) {
unsafe {
bindings::Gecko_DropElementSnapshot(ptr::read(&self.0 as *const _));
let element =
unsafe { ::std::mem::transmute::<&E, &GeckoElement>(element) };
bindings::Gecko_GetElementSnapshot(self, element.0).as_ref()
}
}
}
impl GeckoElementSnapshot {
/// Create a new snapshot of the given element.
pub fn new<'le>(el: GeckoElement<'le>) -> Self {
unsafe { GeckoElementSnapshot(bindings::Gecko_CreateElementSnapshot(el.0)) }
}
/// Get a mutable reference to the snapshot.
pub fn borrow_mut_raw(&mut self) -> bindings::ServoElementSnapshotBorrowedMut {
&mut *self.0
}
/// Get the pointer to the actual snapshot.
pub fn ptr(&self) -> *const ServoElementSnapshot {
&*self.0
}
#[inline]
fn is_html_element_in_html_document(&self) -> bool {
unsafe { (*self.0).mIsHTMLElementInHTMLDocument }
self.mIsHTMLElementInHTMLDocument
}
#[inline]
fn has_any(&self, flags: Flags) -> bool {
unsafe { ((*self.0).mContains as u8 & flags as u8) != 0 }
(self.mContains as u8 & flags as u8) != 0
}
fn as_ptr(&self) -> *const Self {
self
}
}
@ -68,7 +67,7 @@ impl ::selectors::MatchAttr for GeckoElementSnapshot {
fn match_attr_has(&self, attr: &AttrSelector<SelectorImpl>) -> bool {
unsafe {
bindings::Gecko_SnapshotHasAttr(self.ptr(),
bindings::Gecko_SnapshotHasAttr(self,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()))
}
@ -76,7 +75,7 @@ impl ::selectors::MatchAttr for GeckoElementSnapshot {
fn match_attr_equals(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_SnapshotAttrEquals(self.ptr(),
bindings::Gecko_SnapshotAttrEquals(self,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr(),
@ -86,7 +85,7 @@ impl ::selectors::MatchAttr for GeckoElementSnapshot {
fn match_attr_equals_ignore_ascii_case(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_SnapshotAttrEquals(self.ptr(),
bindings::Gecko_SnapshotAttrEquals(self,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr(),
@ -95,7 +94,7 @@ impl ::selectors::MatchAttr for GeckoElementSnapshot {
}
fn match_attr_includes(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_SnapshotAttrIncludes(self.ptr(),
bindings::Gecko_SnapshotAttrIncludes(self,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr())
@ -103,7 +102,7 @@ impl ::selectors::MatchAttr for GeckoElementSnapshot {
}
fn match_attr_dash(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_SnapshotAttrDashEquals(self.ptr(),
bindings::Gecko_SnapshotAttrDashEquals(self,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr())
@ -111,7 +110,7 @@ impl ::selectors::MatchAttr for GeckoElementSnapshot {
}
fn match_attr_prefix(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_SnapshotAttrHasPrefix(self.ptr(),
bindings::Gecko_SnapshotAttrHasPrefix(self,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr())
@ -119,7 +118,7 @@ impl ::selectors::MatchAttr for GeckoElementSnapshot {
}
fn match_attr_substring(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_SnapshotAttrHasSubstring(self.ptr(),
bindings::Gecko_SnapshotAttrHasSubstring(self,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr())
@ -127,7 +126,7 @@ impl ::selectors::MatchAttr for GeckoElementSnapshot {
}
fn match_attr_suffix(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_SnapshotAttrHasSuffix(self.ptr(),
bindings::Gecko_SnapshotAttrHasSuffix(self,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr())
@ -138,7 +137,7 @@ impl ::selectors::MatchAttr for GeckoElementSnapshot {
impl ElementSnapshot for GeckoElementSnapshot {
fn state(&self) -> Option<ElementState> {
if self.has_any(Flags::State) {
Some(ElementState::from_bits_truncate(unsafe { (*self.0).mState }))
Some(ElementState::from_bits_truncate(self.mState))
} else {
None
}
@ -151,7 +150,7 @@ impl ElementSnapshot for GeckoElementSnapshot {
fn id_attr(&self) -> Option<Atom> {
let ptr = unsafe {
bindings::Gecko_SnapshotAtomAttrValue(self.ptr(),
bindings::Gecko_SnapshotAtomAttrValue(self,
atom!("id").as_ptr())
};
@ -163,7 +162,7 @@ impl ElementSnapshot for GeckoElementSnapshot {
}
fn has_class(&self, name: &Atom) -> bool {
snapshot_helpers::has_class(self.ptr(),
snapshot_helpers::has_class(self.as_ptr(),
name,
bindings::Gecko_SnapshotClassOrClassList)
}
@ -171,7 +170,7 @@ impl ElementSnapshot for GeckoElementSnapshot {
fn each_class<F>(&self, callback: F)
where F: FnMut(&Atom)
{
snapshot_helpers::each_class(self.ptr(),
snapshot_helpers::each_class(self.as_ptr(),
callback,
bindings::Gecko_SnapshotClassOrClassList)
}

View file

@ -47,9 +47,11 @@ use gecko_bindings::bindings::Gecko_UpdateAnimations;
use gecko_bindings::structs;
use gecko_bindings::structs::{RawGeckoElement, RawGeckoNode};
use gecko_bindings::structs::{nsIAtom, nsIContent, nsStyleContext};
use gecko_bindings::structs::ELEMENT_HANDLED_SNAPSHOT;
use gecko_bindings::structs::ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO;
use gecko_bindings::structs::ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO;
use gecko_bindings::structs::ELEMENT_HAS_SNAPSHOT;
use gecko_bindings::structs::EffectCompositor_CascadeLevel as CascadeLevel;
use gecko_bindings::structs::NODE_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO;
use gecko_bindings::structs::NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO;
use gecko_bindings::structs::NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE;
use gecko_bindings::structs::NODE_IS_NATIVE_ANONYMOUS;
use gecko_bindings::sugar::ownership::HasArcFFI;
@ -60,7 +62,7 @@ use properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock};
use properties::animated_properties::{AnimationValue, AnimationValueMap, TransitionProperty};
use properties::style_structs::Font;
use rule_tree::CascadeLevel as ServoCascadeLevel;
use selector_parser::{ElementExt, Snapshot};
use selector_parser::ElementExt;
use selectors::Element;
use selectors::matching::{ElementSelectorFlags, StyleRelations};
use selectors::parser::{AttrSelector, NamespaceConstraint};
@ -361,6 +363,10 @@ impl<'le> GeckoElement<'le> {
/// Clear the element data for a given element.
pub fn clear_data(&self) {
let ptr = self.0.mServoData.get();
unsafe {
self.unset_flags(ELEMENT_HAS_SNAPSHOT as u32 |
ELEMENT_HANDLED_SNAPSHOT as u32);
}
if !ptr.is_null() {
debug!("Dropping ElementData for {:?}", self);
let data = unsafe { Box::from_raw(self.0.mServoData.get()) };
@ -389,11 +395,6 @@ impl<'le> GeckoElement<'le> {
},
}
}
/// Creates a blank snapshot for this element.
pub fn create_snapshot(&self) -> Snapshot {
Snapshot::new(*self)
}
}
/// Converts flags from the layout used by rust-selectors to the layout used
@ -589,14 +590,27 @@ impl<'le> TElement for GeckoElement<'le> {
}
}
fn has_snapshot(&self) -> bool {
self.flags() & (ELEMENT_HAS_SNAPSHOT as u32) != 0
}
fn handled_snapshot(&self) -> bool {
self.flags() & (ELEMENT_HANDLED_SNAPSHOT as u32) != 0
}
unsafe fn set_handled_snapshot(&self) {
debug_assert!(self.get_data().is_some());
self.set_flags(ELEMENT_HANDLED_SNAPSHOT as u32)
}
fn has_dirty_descendants(&self) -> bool {
self.flags() & (NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO as u32) != 0
self.flags() & (ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO as u32) != 0
}
unsafe fn set_dirty_descendants(&self) {
debug_assert!(self.get_data().is_some());
debug!("Setting dirty descendants: {:?}", self);
self.set_flags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO as u32)
self.set_flags(ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO as u32)
}
unsafe fn note_descendants<B: DescendantsBit<Self>>(&self) {
@ -617,19 +631,19 @@ impl<'le> TElement for GeckoElement<'le> {
}
unsafe fn unset_dirty_descendants(&self) {
self.unset_flags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO as u32)
self.unset_flags(ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO as u32)
}
fn has_animation_only_dirty_descendants(&self) -> bool {
self.flags() & (NODE_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO as u32) != 0
self.flags() & (ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO as u32) != 0
}
unsafe fn set_animation_only_dirty_descendants(&self) {
self.set_flags(NODE_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO as u32)
self.set_flags(ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO as u32)
}
unsafe fn unset_animation_only_dirty_descendants(&self) {
self.unset_flags(NODE_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO as u32)
self.unset_flags(ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO as u32)
}
fn is_native_anonymous(&self) -> bool {

View file

@ -85,6 +85,7 @@ impl<T: RefCounted> RefPtr<T> {
pub fn forget(self) -> structs::RefPtr<T> {
let ret = structs::RefPtr {
mRawPtr: self.ptr,
_phantom_0: PhantomData,
};
mem::forget(self);
ret

View file

@ -196,7 +196,7 @@ fn element_matches_candidate<E: TElement>(element: &E,
}
let data = candidate_element.borrow_data().unwrap();
debug_assert!(data.has_current_styles());
debug_assert!(element.has_current_styles(&data));
let current_styles = data.styles();
debug!("Sharing style between {:?} and {:?}", element, candidate_element);
@ -432,7 +432,8 @@ trait PrivateMatchMethods: TElement {
// construct a frame for some small piece of newly-added
// content in order to do something specific with that frame,
// but not wanting to flush all of layout).
debug_assert!(cfg!(feature = "gecko") || d.has_current_styles());
debug_assert!(cfg!(feature = "gecko") ||
parent_el.unwrap().has_current_styles(d));
d.styles().primary.values()
});

View file

@ -13,7 +13,7 @@ use element_state::*;
use gecko_bindings::structs::nsRestyleHint;
#[cfg(feature = "servo")]
use heapsize::HeapSizeOf;
use selector_parser::{AttrValue, NonTSPseudoClass, Snapshot, SelectorImpl};
use selector_parser::{AttrValue, NonTSPseudoClass, SelectorImpl, Snapshot, SnapshotMap};
use selectors::{Element, MatchAttr};
use selectors::matching::{ElementSelectorFlags, StyleRelations};
use selectors::matching::matches_selector;
@ -22,6 +22,7 @@ use selectors::parser::{SelectorInner, SelectorMethods};
use selectors::visitor::SelectorVisitor;
use smallvec::SmallVec;
use std::borrow::Borrow;
use std::cell::Cell;
use std::clone::Clone;
use stylist::SelectorMap;
@ -126,6 +127,7 @@ impl RestyleHint {
impl From<nsRestyleHint> for RestyleHint {
fn from(raw: nsRestyleHint) -> Self {
let raw_bits: u32 = raw.0;
// FIXME(bholley): Finish aligning the binary representations here and
// then .expect() the result of the checked version.
if Self::from_bits(raw_bits).is_none() {
@ -193,20 +195,38 @@ struct ElementWrapper<'a, E>
where E: TElement,
{
element: E,
snapshot: Option<&'a Snapshot>,
cached_snapshot: Cell<Option<&'a Snapshot>>,
snapshot_map: &'a SnapshotMap,
}
impl<'a, E> ElementWrapper<'a, E>
where E: TElement,
{
/// Trivially constructs an `ElementWrapper` without a snapshot.
pub fn new(el: E) -> ElementWrapper<'a, E> {
ElementWrapper { element: el, snapshot: None }
/// Trivially constructs an `ElementWrapper`.
fn new(el: E, snapshot_map: &'a SnapshotMap) -> Self {
ElementWrapper {
element: el,
cached_snapshot: Cell::new(None),
snapshot_map: snapshot_map,
}
}
/// Trivially constructs an `ElementWrapper` with a snapshot.
pub fn new_with_snapshot(el: E, snapshot: &'a Snapshot) -> ElementWrapper<'a, E> {
ElementWrapper { element: el, snapshot: Some(snapshot) }
/// Gets the snapshot associated with this element, if any.
fn snapshot(&self) -> Option<&'a Snapshot> {
if !self.element.has_snapshot() {
return None;
}
if let Some(s) = self.cached_snapshot.get() {
return Some(s);
}
let snapshot = self.snapshot_map.get(&self.element);
debug_assert!(snapshot.is_some(), "has_snapshot lied!");
self.cached_snapshot.set(snapshot);
snapshot
}
}
@ -216,7 +236,7 @@ impl<'a, E> MatchAttr for ElementWrapper<'a, E>
type Impl = SelectorImpl;
fn match_attr_has(&self, attr: &AttrSelector<SelectorImpl>) -> bool {
match self.snapshot {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs()
=> snapshot.match_attr_has(attr),
_ => self.element.match_attr_has(attr)
@ -226,7 +246,7 @@ impl<'a, E> MatchAttr for ElementWrapper<'a, E>
fn match_attr_equals(&self,
attr: &AttrSelector<SelectorImpl>,
value: &AttrValue) -> bool {
match self.snapshot {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs()
=> snapshot.match_attr_equals(attr, value),
_ => self.element.match_attr_equals(attr, value)
@ -236,7 +256,7 @@ impl<'a, E> MatchAttr for ElementWrapper<'a, E>
fn match_attr_equals_ignore_ascii_case(&self,
attr: &AttrSelector<SelectorImpl>,
value: &AttrValue) -> bool {
match self.snapshot {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs()
=> snapshot.match_attr_equals_ignore_ascii_case(attr, value),
_ => self.element.match_attr_equals_ignore_ascii_case(attr, value)
@ -246,7 +266,7 @@ impl<'a, E> MatchAttr for ElementWrapper<'a, E>
fn match_attr_includes(&self,
attr: &AttrSelector<SelectorImpl>,
value: &AttrValue) -> bool {
match self.snapshot {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs()
=> snapshot.match_attr_includes(attr, value),
_ => self.element.match_attr_includes(attr, value)
@ -256,7 +276,7 @@ impl<'a, E> MatchAttr for ElementWrapper<'a, E>
fn match_attr_dash(&self,
attr: &AttrSelector<SelectorImpl>,
value: &AttrValue) -> bool {
match self.snapshot {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs()
=> snapshot.match_attr_dash(attr, value),
_ => self.element.match_attr_dash(attr, value)
@ -266,7 +286,7 @@ impl<'a, E> MatchAttr for ElementWrapper<'a, E>
fn match_attr_prefix(&self,
attr: &AttrSelector<SelectorImpl>,
value: &AttrValue) -> bool {
match self.snapshot {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs()
=> snapshot.match_attr_prefix(attr, value),
_ => self.element.match_attr_prefix(attr, value)
@ -276,7 +296,7 @@ impl<'a, E> MatchAttr for ElementWrapper<'a, E>
fn match_attr_substring(&self,
attr: &AttrSelector<SelectorImpl>,
value: &AttrValue) -> bool {
match self.snapshot {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs()
=> snapshot.match_attr_substring(attr, value),
_ => self.element.match_attr_substring(attr, value)
@ -286,7 +306,7 @@ impl<'a, E> MatchAttr for ElementWrapper<'a, E>
fn match_attr_suffix(&self,
attr: &AttrSelector<SelectorImpl>,
value: &AttrValue) -> bool {
match self.snapshot {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs()
=> snapshot.match_attr_suffix(attr, value),
_ => self.element.match_attr_suffix(attr, value)
@ -322,7 +342,7 @@ impl<'a, E> Element for ElementWrapper<'a, E>
relations,
&mut |_, _| {})
}
match self.snapshot.and_then(|s| s.state()) {
match self.snapshot().and_then(|s| s.state()) {
Some(snapshot_state) => snapshot_state.contains(flag),
None => {
self.element.match_non_ts_pseudo_class(pseudo_class,
@ -333,23 +353,28 @@ impl<'a, E> Element for ElementWrapper<'a, E>
}
fn parent_element(&self) -> Option<Self> {
self.element.parent_element().map(ElementWrapper::new)
self.element.parent_element()
.map(|e| ElementWrapper::new(e, self.snapshot_map))
}
fn first_child_element(&self) -> Option<Self> {
self.element.first_child_element().map(ElementWrapper::new)
self.element.first_child_element()
.map(|e| ElementWrapper::new(e, self.snapshot_map))
}
fn last_child_element(&self) -> Option<Self> {
self.element.last_child_element().map(ElementWrapper::new)
self.element.last_child_element()
.map(|e| ElementWrapper::new(e, self.snapshot_map))
}
fn prev_sibling_element(&self) -> Option<Self> {
self.element.prev_sibling_element().map(ElementWrapper::new)
self.element.prev_sibling_element()
.map(|e| ElementWrapper::new(e, self.snapshot_map))
}
fn next_sibling_element(&self) -> Option<Self> {
self.element.next_sibling_element().map(ElementWrapper::new)
self.element.next_sibling_element()
.map(|e| ElementWrapper::new(e, self.snapshot_map))
}
fn is_html_element_in_html_document(&self) -> bool {
@ -365,7 +390,7 @@ impl<'a, E> Element for ElementWrapper<'a, E>
}
fn get_id(&self) -> Option<Atom> {
match self.snapshot {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs()
=> snapshot.id_attr(),
_ => self.element.get_id()
@ -373,7 +398,7 @@ impl<'a, E> Element for ElementWrapper<'a, E>
}
fn has_class(&self, name: &Atom) -> bool {
match self.snapshot {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs()
=> snapshot.has_class(name),
_ => self.element.has_class(name)
@ -390,7 +415,7 @@ impl<'a, E> Element for ElementWrapper<'a, E>
fn each_class<F>(&self, callback: F)
where F: FnMut(&Atom) {
match self.snapshot {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs()
=> snapshot.each_class(callback),
_ => self.element.each_class(callback)
@ -606,13 +631,21 @@ impl DependencySet {
/// explained in the rest of the documentation.
pub fn compute_hint<E>(&self,
el: &E,
snapshot: &Snapshot)
snapshots: &SnapshotMap)
-> RestyleHint
where E: TElement + Clone,
{
debug_assert!(el.has_snapshot(), "Shouldn't be here!");
let snapshot_el = ElementWrapper::new(el.clone(), snapshots);
let snapshot =
snapshot_el.snapshot().expect("has_snapshot lied so badly");
let current_state = el.get_state();
let state_changes = snapshot.state()
.map_or_else(ElementState::empty, |old_state| current_state ^ old_state);
let state_changes =
snapshot.state()
.map_or_else(ElementState::empty,
|old_state| current_state ^ old_state);
let attrs_changed = snapshot.has_attrs();
if state_changes.is_empty() && !attrs_changed {
@ -620,7 +653,6 @@ impl DependencySet {
}
let mut hint = RestyleHint::empty();
let snapshot_el = ElementWrapper::new_with_snapshot(el.clone(), snapshot);
// Compute whether the snapshot has any different id or class attributes
// from the element. If it does, we need to pass those to the lookup, so
@ -637,12 +669,14 @@ impl DependencySet {
}
self.0.lookup_with_additional(*el, additional_id, &additional_classes, &mut |dep| {
if !dep.sensitivities.sensitive_to(attrs_changed, state_changes) || hint.contains(dep.hint) {
if !dep.sensitivities.sensitive_to(attrs_changed, state_changes) ||
hint.contains(dep.hint) {
return true;
}
// 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.
// 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_selector(&dep.selector, &snapshot_el, None,
&mut StyleRelations::empty(),
@ -658,8 +692,8 @@ impl DependencySet {
!hint.is_all()
});
debug!("Calculated restyle hint: {:?}. (Element={:?}, State={:?}, Snapshot={:?}, {} Deps)",
hint, el, current_state, snapshot, self.len());
debug!("Calculated restyle hint: {:?} for {:?}. (State={:?}, {} Deps)",
hint, el, current_state, self.len());
trace!("Deps: {:?}", self);
hint

View file

@ -9,7 +9,9 @@
use {Atom, Prefix, Namespace, LocalName};
use attr::{AttrIdentifier, AttrValue};
use cssparser::{Parser as CssParser, ToCss, serialize_identifier};
use dom::{OpaqueNode, TElement, TNode};
use element_state::ElementState;
use fnv::FnvHashMap;
use restyle_hints::ElementSnapshot;
use selector_parser::{ElementExt, PseudoElementCascadeType, SelectorParser};
use selectors::{Element, MatchAttrGeneric};
@ -20,6 +22,7 @@ use std::borrow::Cow;
use std::fmt;
use std::fmt::Debug;
use std::mem;
use std::ops::{Deref, DerefMut};
/// A pseudo-element, both public and private.
///
@ -448,6 +451,36 @@ impl SelectorImpl {
}
}
/// A map from elements to snapshots for the Servo style back-end.
#[derive(Debug)]
pub struct SnapshotMap(FnvHashMap<OpaqueNode, ServoElementSnapshot>);
impl SnapshotMap {
/// Create a new empty `SnapshotMap`.
pub fn new() -> Self {
SnapshotMap(FnvHashMap::default())
}
/// Get a snapshot given an element.
pub fn get<T: TElement>(&self, el: &T) -> Option<&ServoElementSnapshot> {
self.0.get(&el.as_node().opaque())
}
}
impl Deref for SnapshotMap {
type Target = FnvHashMap<OpaqueNode, ServoElementSnapshot>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for SnapshotMap {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
/// Servo's version of an element snapshot.
#[derive(Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]

View file

@ -22,7 +22,7 @@ use properties::INHERIT_ALL;
use properties::PropertyDeclarationBlock;
use restyle_hints::{RestyleHint, DependencySet};
use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource};
use selector_parser::{SelectorImpl, PseudoElement, Snapshot};
use selector_parser::{SelectorImpl, PseudoElement, SnapshotMap};
use selectors::Element;
use selectors::bloom::BloomFilter;
use selectors::matching::{AFFECTED_BY_STYLE_ATTRIBUTE, AFFECTED_BY_PRESENTATIONAL_HINTS};
@ -894,16 +894,16 @@ impl Stylist {
results
}
/// Given an element, and a snapshot that represents a previous state of the
/// element, compute the appropriate restyle hint, that is, the kind of
/// Given an element, and a snapshot table that represents a previous state
/// of the tree, compute the appropriate restyle hint, that is, the kind of
/// restyle we need to do.
pub fn compute_restyle_hint<E>(&self,
element: &E,
snapshot: &Snapshot)
snapshots: &SnapshotMap)
-> RestyleHint
where E: TElement,
{
self.dependencies.compute_hint(element, snapshot)
self.dependencies.compute_hint(element, snapshots)
}
/// Computes styles for a given declaration with parent_style.

View file

@ -13,7 +13,6 @@ use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_SELF};
use selector_parser::RestyleDamage;
#[cfg(feature = "servo")] use servo_config::opts;
use std::borrow::BorrowMut;
use stylist::Stylist;
/// A per-traversal-level chunk of data. This is sent down by the traversal, and
/// currently only holds the dom depth for the bloom filter.
@ -200,7 +199,9 @@ pub trait DomTraversal<E: TElement> : Sync {
/// appended children without restyling the parent.
/// If traversal_flag::ANIMATION_ONLY is specified, style only elements for
/// animations.
fn pre_traverse(root: E, stylist: &Stylist, traversal_flags: TraversalFlags)
fn pre_traverse(root: E,
shared_context: &SharedStyleContext,
traversal_flags: TraversalFlags)
-> PreTraverseToken
{
debug_assert!(!(traversal_flags.for_reconstruct() &&
@ -226,8 +227,7 @@ pub trait DomTraversal<E: TElement> : Sync {
// Expanding snapshots here may create a LATER_SIBLINGS restyle hint, which
// we propagate to the next sibling element.
if let Some(mut data) = root.mutate_data() {
if let Some(r) = data.get_restyle_mut() {
let later_siblings = r.compute_final_hint(root, stylist);
let later_siblings = data.compute_final_hint(root, shared_context);
if later_siblings {
if let Some(next) = root.next_sibling_element() {
if let Some(mut next_data) = next.mutate_data() {
@ -237,7 +237,6 @@ pub trait DomTraversal<E: TElement> : Sync {
}
}
}
}
PreTraverseToken {
traverse: Self::node_needs_traversal(root.as_node(), traversal_flags),
@ -375,6 +374,7 @@ pub trait DomTraversal<E: TElement> : Sync {
return true;
}
trace!("{:?} doesn't need traversal", el);
false
}
@ -390,7 +390,7 @@ pub trait DomTraversal<E: TElement> : Sync {
log: LogBehavior) -> bool
{
// See the comment on `cascade_node` for why we allow this on Gecko.
debug_assert!(cfg!(feature = "gecko") || parent_data.has_current_styles());
debug_assert!(cfg!(feature = "gecko") || parent.has_current_styles(parent_data));
// If the parent computed display:none, we don't style the subtree.
if parent_data.styles().is_display_none() {
@ -597,11 +597,13 @@ pub fn recalc_style_at<E, D>(traversal: &D,
{
context.thread_local.begin_element(element, &data);
context.thread_local.statistics.elements_traversed += 1;
debug_assert!(!element.has_snapshot() || element.handled_snapshot(),
"Should've handled snapshots here already");
debug_assert!(data.get_restyle().map_or(true, |r| {
r.snapshot.is_none() && !r.has_sibling_invalidations()
!r.has_sibling_invalidations()
}), "Should've computed the final hint and handled later_siblings already");
let compute_self = !data.has_current_styles();
let compute_self = !element.has_current_styles(data);
let mut inherited_style_changed = false;
debug!("recalc_style_at: {:?} (compute_self={:?}, dirty_descendants={:?}, data={:?})",
@ -613,9 +615,9 @@ pub fn recalc_style_at<E, D>(traversal: &D,
// If we're restyling this element to display:none, throw away all style
// data in the subtree, notify the caller to early-return.
let display_none = data.styles().is_display_none();
if display_none {
debug!("New element style is display:none - clearing data from descendants.");
if data.styles().is_display_none() {
debug!("{:?} style is display:none - clearing data from descendants.",
element);
clear_descendant_data(element, &|e| unsafe { D::clear_element_data(&e) });
}
@ -636,15 +638,15 @@ pub fn recalc_style_at<E, D>(traversal: &D,
r.hint.propagate(&context.shared.traversal_flags)
},
};
debug_assert!(data.has_current_styles() ||
context.shared.traversal_flags.for_animation_only(),
"Should have computed style or haven't yet valid computed \
style in case of animation-only restyle");
trace!("propagated_hint={:?}, inherited_style_changed={:?}, \
is_display_none={:?}, implementing_pseudo={:?}",
propagated_hint, inherited_style_changed,
data.styles().is_display_none(),
element.implemented_pseudo_element());
debug_assert!(element.has_current_styles(data) ||
context.shared.traversal_flags.for_animation_only(),
"Should have computed style or haven't yet valid computed \
style in case of animation-only restyle");
let has_dirty_descendants_for_this_restyle =
if context.shared.traversal_flags.for_animation_only() {
@ -779,6 +781,18 @@ fn preprocess_children<E, D>(traversal: &D,
continue;
}
// Handle element snapshots and sibling restyle hints.
//
// NB: This will be a no-op if there's no restyle data and no snapshot.
let later_siblings =
child_data.compute_final_hint(child, traversal.shared_context());
trace!(" > {:?} -> {:?} + {:?}, later_siblings: {:?}",
child,
child_data.get_restyle().map(|r| &r.hint),
propagated_hint,
later_siblings);
// If the child doesn't have pre-existing RestyleData and we don't have
// any reason to create one, avoid the useless allocation and move on to
// the next child.
@ -786,6 +800,7 @@ fn preprocess_children<E, D>(traversal: &D,
damage_handled.is_empty() && !child_data.has_restyle() {
continue;
}
let mut restyle_data = child_data.ensure_restyle();
// Propagate the parent and sibling restyle hint.
@ -793,11 +808,6 @@ fn preprocess_children<E, D>(traversal: &D,
restyle_data.hint.insert(&propagated_hint);
}
// Handle element snapshots and sibling restyle hints.
let stylist = &traversal.shared_context().stylist;
let later_siblings = restyle_data.compute_final_hint(child, stylist);
trace!(" > {:?} -> {:?}, later_siblings: {:?}",
child, restyle_data.hint, later_siblings);
if later_siblings {
propagated_hint.insert(&(RESTYLE_SELF | RESTYLE_DESCENDANTS).into());
}
@ -805,9 +815,11 @@ fn preprocess_children<E, D>(traversal: &D,
// Store the damage already handled by ancestors.
restyle_data.set_damage_handled(damage_handled);
// If properties that we inherited from the parent changed, we need to recascade.
// If properties that we inherited from the parent changed, we need to
// recascade.
//
// FIXME(bholley): Need to handle explicitly-inherited reset properties somewhere.
// FIXME(bholley): Need to handle explicitly-inherited reset properties
// somewhere.
if parent_inherited_style_changed {
restyle_data.recascade = true;
}

View file

@ -61,6 +61,7 @@ use style::gecko_bindings::structs::{SheetParsingMode, nsIAtom, nsCSSPropertyID}
use style::gecko_bindings::structs::{nsRestyleHint, nsChangeHint, nsCSSFontFaceRule};
use style::gecko_bindings::structs::Loader;
use style::gecko_bindings::structs::RawGeckoPresContextOwned;
use style::gecko_bindings::structs::ServoElementSnapshotTable;
use style::gecko_bindings::structs::URLExtraData;
use style::gecko_bindings::structs::nsCSSValueSharedList;
use style::gecko_bindings::structs::nsresult;
@ -149,7 +150,9 @@ unsafe fn dummy_url_data() -> &'static RefPtr<URLExtraData> {
fn create_shared_context<'a>(global_style_data: &GlobalStyleData,
guard: &'a SharedRwLockReadGuard,
per_doc_data: &PerDocumentStyleDataImpl,
traversal_flags: TraversalFlags) -> SharedStyleContext<'a> {
traversal_flags: TraversalFlags,
snapshot_map: &'a ServoElementSnapshotTable)
-> SharedStyleContext<'a> {
let local_context_data =
ThreadLocalStyleContextCreationInfo::new(per_doc_data.new_animations_sender.clone());
@ -166,12 +169,14 @@ fn create_shared_context<'a>(global_style_data: &GlobalStyleData,
// FIXME Find the real QuirksMode information for this document
quirks_mode: QuirksMode::NoQuirks,
traversal_flags: traversal_flags,
snapshot_map: snapshot_map,
}
}
fn traverse_subtree(element: GeckoElement,
raw_data: RawServoStyleSetBorrowed,
traversal_flags: TraversalFlags) {
traversal_flags: TraversalFlags,
snapshots: &ServoElementSnapshotTable) {
// When new content is inserted in a display:none subtree, we will call into
// servo to try to style it. Detect that here and bail out.
if let Some(parent) = element.parent_element() {
@ -184,7 +189,18 @@ fn traverse_subtree(element: GeckoElement,
let per_doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow();
debug_assert!(!per_doc_data.stylesheets.has_changed());
let token = RecalcStyleOnly::pre_traverse(element, &per_doc_data.stylist, traversal_flags);
let global_style_data = &*GLOBAL_STYLE_DATA;
let guard = global_style_data.shared_lock.read();
let shared_style_context = create_shared_context(&global_style_data,
&guard,
&per_doc_data,
traversal_flags,
snapshots);
let token = RecalcStyleOnly::pre_traverse(element,
&shared_style_context,
traversal_flags);
if !token.should_traverse() {
return;
}
@ -192,13 +208,6 @@ fn traverse_subtree(element: GeckoElement,
debug!("Traversing subtree:");
debug!("{:?}", ShowSubtreeData(element.as_node()));
let global_style_data = &*GLOBAL_STYLE_DATA;
let guard = global_style_data.shared_lock.read();
let shared_style_context = create_shared_context(&global_style_data,
&guard,
&per_doc_data,
traversal_flags);
let traversal_driver = if global_style_data.style_thread_pool.is_none() {
TraversalDriver::Sequential
} else {
@ -214,17 +223,20 @@ fn traverse_subtree(element: GeckoElement,
}
}
/// Traverses the subtree rooted at `root` for restyling. Returns whether a
/// Gecko post-traversal (to perform lazy frame construction, or consume any
/// RestyleData, or drop any ElementData) is required.
/// Traverses the subtree rooted at `root` for restyling.
///
/// Returns whether a Gecko post-traversal (to perform lazy frame construction,
/// or consume any RestyleData, or drop any ElementData) is required.
#[no_mangle]
pub extern "C" fn Servo_TraverseSubtree(root: RawGeckoElementBorrowed,
raw_data: RawServoStyleSetBorrowed,
snapshots: *const ServoElementSnapshotTable,
root_behavior: structs::TraversalRootBehavior,
restyle_behavior: structs::TraversalRestyleBehavior)
-> bool {
use self::structs::TraversalRestyleBehavior as Restyle;
use self::structs::TraversalRootBehavior as Root;
debug_assert!(!snapshots.is_null());
let element = GeckoElement(root);
debug!("Servo_TraverseSubtree: {:?}", element);
@ -238,12 +250,18 @@ pub extern "C" fn Servo_TraverseSubtree(root: RawGeckoElementBorrowed,
if element.has_animation_only_dirty_descendants() ||
element.has_animation_restyle_hints() {
traverse_subtree(element, raw_data, traversal_flags | ANIMATION_ONLY);
traverse_subtree(element,
raw_data,
traversal_flags | ANIMATION_ONLY,
unsafe { &*snapshots });
}
traverse_subtree(element, raw_data, traversal_flags);
traverse_subtree(element,
raw_data,
traversal_flags,
unsafe { &*snapshots });
element.has_dirty_descendants() || element.mutate_data().unwrap().has_restyle()
element.has_dirty_descendants() || element.borrow_data().unwrap().has_restyle()
}
#[no_mangle]
@ -436,18 +454,21 @@ pub extern "C" fn Servo_AnimationValue_Uncompute(value: RawServoAnimationValueBo
#[no_mangle]
pub extern "C" fn Servo_StyleSet_GetBaseComputedValuesForElement(raw_data: RawServoStyleSetBorrowed,
element: RawGeckoElementBorrowed,
snapshots: *const ServoElementSnapshotTable,
pseudo_tag: *mut nsIAtom)
-> ServoComputedValuesStrong
{
use style::matching::MatchMethods;
debug_assert!(!snapshots.is_null());
let doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow();
let global_style_data = &*GLOBAL_STYLE_DATA;
let guard = global_style_data.shared_lock.read();
let shared_context = &create_shared_context(&global_style_data,
let shared_context = create_shared_context(&global_style_data,
&guard,
&doc_data,
TraversalFlags::empty());
TraversalFlags::empty(),
unsafe { &*snapshots });
let element = GeckoElement(element);
let element_data = element.borrow_data().unwrap();
let styles = element_data.styles();
@ -469,7 +490,10 @@ pub extern "C" fn Servo_StyleSet_GetBaseComputedValuesForElement(raw_data: RawSe
};
let provider = get_metrics_provider_for_product();
element.get_base_style(shared_context, &provider, &styles.primary, pseudo_style)
element.get_base_style(&shared_context,
&provider,
&styles.primary,
pseudo_style)
.into_strong()
}
@ -1867,10 +1891,12 @@ unsafe fn maybe_restyle<'a>(data: &'a mut AtomicRefMut<ElementData>,
}
// Propagate the bit up the chain.
if let Some(p) = element.parent_element() {
if animation_only {
element.parent_element().map(|p| p.note_descendants::<AnimationOnlyDirtyDescendants>());
p.note_descendants::<AnimationOnlyDirtyDescendants>();
} else {
element.parent_element().map(|p| p.note_descendants::<DirtyDescendants>());
p.note_descendants::<DirtyDescendants>();
}
};
bindings::Gecko_SetOwnerDocumentNeedsStyleFlush(element.0);
@ -1879,25 +1905,6 @@ unsafe fn maybe_restyle<'a>(data: &'a mut AtomicRefMut<ElementData>,
Some(data.ensure_restyle())
}
#[no_mangle]
pub extern "C" fn Servo_Element_GetSnapshot(element: RawGeckoElementBorrowed) -> *mut structs::ServoElementSnapshot
{
let element = GeckoElement(element);
let snapshot = match element.mutate_data() {
None => ptr::null_mut(),
Some(mut data) => {
if let Some(restyle_data) = unsafe { maybe_restyle(&mut data, element, false) } {
restyle_data.snapshot.ensure(|| element.create_snapshot()).borrow_mut_raw()
} else {
ptr::null_mut()
}
},
};
debug!("Servo_Element_GetSnapshot: {:?}: {:?}", element, snapshot);
snapshot
}
#[no_mangle]
pub extern "C" fn Servo_Element_GetStyleRuleList(element: RawGeckoElementBorrowed,
rules: RawGeckoServoStyleRuleListBorrowedMut) {
@ -1982,12 +1989,12 @@ pub extern "C" fn Servo_ResolveStyle(element: RawGeckoElementBorrowed,
{
let element = GeckoElement(element);
debug!("Servo_ResolveStyle: {:?}", element);
let data = unsafe { element.ensure_data() }.borrow_mut();
let data = unsafe { element.ensure_data() }.borrow();
let valid_styles = if allow_stale {
data.has_styles()
} else {
data.has_current_styles()
element.has_current_styles(&*data)
};
if !valid_styles {
@ -2003,9 +2010,11 @@ pub extern "C" fn Servo_ResolveStyle(element: RawGeckoElementBorrowed,
#[no_mangle]
pub extern "C" fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed,
pseudo_tag: *mut nsIAtom,
snapshots: *const ServoElementSnapshotTable,
raw_data: RawServoStyleSetBorrowed)
-> ServoComputedValuesStrong
{
debug_assert!(!snapshots.is_null());
let global_style_data = &*GLOBAL_STYLE_DATA;
let guard = global_style_data.shared_lock.read();
let element = GeckoElement(element);
@ -2031,7 +2040,8 @@ pub extern "C" fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed,
let shared = create_shared_context(&global_style_data,
&guard,
&mut doc_data.borrow_mut(),
TraversalFlags::empty());
TraversalFlags::empty(),
unsafe { &*snapshots });
let mut tlc = ThreadLocalStyleContext::new(&shared);
let mut context = StyleContext {
shared: &shared,