Auto merge of #14214 - bholley:dirtiness_overhaul, r=emilio

Overhaul dirtiness handling in Servo to prepare for the new incremental restyle architecture

<!-- 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/14214)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2016-11-17 12:25:52 -06:00 committed by GitHub
commit e4a27c4d16
12 changed files with 192 additions and 183 deletions

View file

@ -41,6 +41,7 @@ use dom::bindings::refcounted::{Trusted, TrustedPromise};
use dom::bindings::reflector::{Reflectable, Reflector};
use dom::bindings::str::{DOMString, USVString};
use dom::bindings::utils::WindowProxyHandler;
use dom::document::PendingRestyle;
use encoding::types::EncodingRef;
use euclid::{Matrix2D, Matrix4D, Point2D};
use euclid::length::Length as EuclidLength;
@ -348,6 +349,7 @@ no_jsmanaged_fields!(Mime);
no_jsmanaged_fields!(AttrIdentifier);
no_jsmanaged_fields!(AttrValue);
no_jsmanaged_fields!(Snapshot);
no_jsmanaged_fields!(PendingRestyle);
no_jsmanaged_fields!(HttpsState);
no_jsmanaged_fields!(Request);
no_jsmanaged_fields!(RequestInit);

View file

@ -122,7 +122,8 @@ use std::sync::Arc;
use std::time::{Duration, Instant};
use style::attr::AttrValue;
use style::context::ReflowGoal;
use style::selector_impl::Snapshot;
use style::restyle_hints::RestyleHint;
use style::selector_impl::{RestyleDamage, Snapshot};
use style::str::{split_html_space_chars, str_join};
use style::stylesheets::Stylesheet;
use time;
@ -155,6 +156,29 @@ pub struct StylesheetInDocument {
pub stylesheet: Arc<Stylesheet>,
}
#[derive(Debug, HeapSizeOf)]
pub struct PendingRestyle {
/// If this element had a state or attribute change since the last restyle, track
/// the original condition of the element.
pub snapshot: Option<Snapshot>,
/// Any explicit restyles hints that have been accumulated for this element.
pub hint: RestyleHint,
/// Any explicit restyles damage that have been accumulated for this element.
pub damage: RestyleDamage,
}
impl PendingRestyle {
pub fn new() -> Self {
PendingRestyle {
snapshot: None,
hint: RestyleHint::empty(),
damage: RestyleDamage::empty(),
}
}
}
// https://dom.spec.whatwg.org/#document
#[dom_struct]
pub struct Document {
@ -232,9 +256,9 @@ pub struct Document {
/// This field is set to the document itself for inert documents.
/// https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document
appropriate_template_contents_owner_document: MutNullableHeap<JS<Document>>,
/// For each element that has had a state or attribute change since the last restyle,
/// track the original condition of the element.
modified_elements: DOMRefCell<HashMap<JS<Element>, Snapshot>>,
/// Information on elements needing restyle to ship over to the layout thread when the
/// time comes.
pending_restyles: DOMRefCell<HashMap<JS<Element>, PendingRestyle>>,
/// This flag will be true if layout suppressed a reflow attempt that was
/// needed in order for the page to be painted.
needs_paint: Cell<bool>,
@ -408,7 +432,7 @@ impl Document {
Some(root) => {
root.upcast::<Node>().is_dirty() ||
root.upcast::<Node>().has_dirty_descendants() ||
!self.modified_elements.borrow().is_empty() ||
!self.pending_restyles.borrow().is_empty() ||
self.needs_paint()
}
None => false,
@ -451,7 +475,7 @@ impl Document {
}
pub fn content_and_heritage_changed(&self, node: &Node, damage: NodeDamage) {
node.force_dirty_ancestors(damage);
node.dirty(damage);
}
/// Reflows and disarms the timer if the reflow timer has expired.
@ -1706,7 +1730,7 @@ pub enum DocumentSource {
#[allow(unsafe_code)]
pub trait LayoutDocumentHelpers {
unsafe fn is_html_document_for_layout(&self) -> bool;
unsafe fn drain_modified_elements(&self) -> Vec<(LayoutJS<Element>, Snapshot)>;
unsafe fn drain_pending_restyles(&self) -> Vec<(LayoutJS<Element>, PendingRestyle)>;
unsafe fn needs_paint_from_layout(&self);
unsafe fn will_paint(&self);
}
@ -1720,8 +1744,8 @@ impl LayoutDocumentHelpers for LayoutJS<Document> {
#[inline]
#[allow(unrooted_must_root)]
unsafe fn drain_modified_elements(&self) -> Vec<(LayoutJS<Element>, Snapshot)> {
let mut elements = (*self.unsafe_get()).modified_elements.borrow_mut_for_layout();
unsafe fn drain_pending_restyles(&self) -> Vec<(LayoutJS<Element>, PendingRestyle)> {
let mut elements = (*self.unsafe_get()).pending_restyles.borrow_mut_for_layout();
let result = elements.drain().map(|(k, v)| (k.to_layout(), v)).collect();
result
}
@ -1829,7 +1853,7 @@ impl Document {
reflow_timeout: Cell::new(None),
base_element: Default::default(),
appropriate_template_contents_owner_document: Default::default(),
modified_elements: DOMRefCell::new(HashMap::new()),
pending_restyles: DOMRefCell::new(HashMap::new()),
needs_paint: Cell::new(false),
active_touch_points: DOMRefCell::new(Vec::new()),
dom_loading: Cell::new(Default::default()),
@ -1967,23 +1991,28 @@ impl Document {
self.id_map.borrow().get(&id).map(|ref elements| Root::from_ref(&*(*elements)[0]))
}
pub fn ensure_pending_restyle(&self, el: &Element) -> RefMut<PendingRestyle> {
let map = self.pending_restyles.borrow_mut();
RefMut::map(map, |m| m.entry(JS::from_ref(el)).or_insert_with(PendingRestyle::new))
}
pub fn ensure_snapshot(&self, el: &Element) -> RefMut<Snapshot> {
let mut entry = self.ensure_pending_restyle(el);
if entry.snapshot.is_none() {
entry.snapshot = Some(Snapshot::new(el.html_element_in_html_document()));
}
RefMut::map(entry, |e| e.snapshot.as_mut().unwrap())
}
pub fn element_state_will_change(&self, el: &Element) {
let mut map = self.modified_elements.borrow_mut();
let snapshot = map.entry(JS::from_ref(el))
.or_insert_with(|| {
Snapshot::new(el.html_element_in_html_document())
});
let mut snapshot = self.ensure_snapshot(el);
if snapshot.state.is_none() {
snapshot.state = Some(el.state());
}
}
pub fn element_attr_will_change(&self, el: &Element) {
let mut map = self.modified_elements.borrow_mut();
let mut snapshot = map.entry(JS::from_ref(el))
.or_insert_with(|| {
Snapshot::new(el.html_element_in_html_document())
});
let mut snapshot = self.ensure_snapshot(el);
if snapshot.attrs.is_none() {
let attrs = el.attrs()
.iter()

View file

@ -84,13 +84,15 @@ use std::fmt;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
use style::dom::TRestyleDamage;
use style::element_state::*;
use style::matching::{common_style_affecting_attributes, rare_style_affecting_attributes};
use style::parser::ParserContextExtraData;
use style::properties::{DeclaredValue, Importance};
use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, parse_style_attribute};
use style::properties::longhands::{background_image, border_spacing, font_family, font_size, overflow_x};
use style::selector_impl::{NonTSPseudoClass, ServoSelectorImpl};
use style::restyle_hints::RESTYLE_SELF;
use style::selector_impl::{NonTSPseudoClass, RestyleDamage, ServoSelectorImpl};
use style::selector_matching::ApplicableDeclarationBlock;
use style::sink::Push;
use style::values::CSSFloat;
@ -201,6 +203,19 @@ impl Element {
ElementBinding::Wrap)
}
pub fn restyle(&self, damage: NodeDamage) {
let doc = self.node.owner_doc();
let mut restyle = doc.ensure_pending_restyle(self);
// FIXME(bholley): I think we should probably only do this for
// NodeStyleDamaged, but I'm preserving existing behavior.
restyle.hint |= RESTYLE_SELF;
if damage == NodeDamage::OtherNodeDamage {
restyle.damage = RestyleDamage::rebuild_and_reflow();
}
}
// https://drafts.csswg.org/cssom-view/#css-layout-box
// Elements that have a computed value of the display property
// that is table-column or table-column-group

View file

@ -149,8 +149,6 @@ bitflags! {
pub flags NodeFlags: u8 {
#[doc = "Specifies whether this node is in a document."]
const IS_IN_DOC = 0x01,
#[doc = "Specifies whether this node _must_ be reflowed regardless of style differences."]
const HAS_CHANGED = 0x02,
#[doc = "Specifies whether this node needs style recalc on next reflow."]
const IS_DIRTY = 0x04,
#[doc = "Specifies whether this node has descendants (inclusive of itself) which \
@ -175,7 +173,7 @@ bitflags! {
impl NodeFlags {
pub fn new() -> NodeFlags {
HAS_CHANGED | IS_DIRTY | HAS_DIRTY_DESCENDANTS
IS_DIRTY
}
}
@ -251,6 +249,8 @@ impl Node {
let parent_in_doc = self.is_in_doc();
for node in new_child.traverse_preorder() {
node.set_flag(IS_IN_DOC, parent_in_doc);
// Out-of-document elements never have the descendants flag set.
debug_assert!(!node.get_flag(HAS_DIRTY_DESCENDANTS));
vtable_for(&&*node).bind_to_tree(parent_in_doc);
}
let document = new_child.owner_doc();
@ -289,7 +289,8 @@ impl Node {
self.children_count.set(self.children_count.get() - 1);
for node in child.traverse_preorder() {
node.set_flag(IS_IN_DOC, false);
// Out-of-document elements never have the descendants flag set.
node.set_flag(IS_IN_DOC | HAS_DIRTY_DESCENDANTS, false);
vtable_for(&&*node).unbind_from_tree(&context);
node.style_and_layout_data.get().map(|d| node.dispose(d));
}
@ -428,14 +429,6 @@ impl Node {
self.flags.set(flags);
}
pub fn has_changed(&self) -> bool {
self.get_flag(HAS_CHANGED)
}
pub fn set_has_changed(&self, state: bool) {
self.set_flag(HAS_CHANGED, state)
}
pub fn is_dirty(&self) -> bool {
self.get_flag(IS_DIRTY)
}
@ -452,10 +445,6 @@ impl Node {
self.set_flag(HAS_DIRTY_DESCENDANTS, state)
}
pub fn force_dirty_ancestors(&self, damage: NodeDamage) {
self.dirty_impl(damage, true)
}
pub fn rev_version(&self) {
// The new version counter is 1 plus the max of the node's current version counter,
// its descendants version, and the document's version. Normally, this will just be
@ -470,30 +459,18 @@ impl Node {
}
pub fn dirty(&self, damage: NodeDamage) {
self.dirty_impl(damage, false)
}
pub fn dirty_impl(&self, damage: NodeDamage, force_ancestors: bool) {
// 0. Set version counter
self.rev_version();
// 1. Dirty self.
match damage {
NodeDamage::NodeStyleDamaged => {}
NodeDamage::OtherNodeDamage => self.set_has_changed(true),
if !self.is_in_doc() {
return;
}
if self.is_dirty() && !force_ancestors {
return
}
self.set_flag(IS_DIRTY, true);
// 4. Dirty ancestors.
for ancestor in self.ancestors() {
if !force_ancestors && ancestor.has_dirty_descendants() { break }
ancestor.set_has_dirty_descendants(true);
}
match self.type_id() {
NodeTypeId::CharacterData(CharacterDataTypeId::Text) =>
self.parent_node.get().unwrap().downcast::<Element>().unwrap().restyle(damage),
NodeTypeId::Element(_) =>
self.downcast::<Element>().unwrap().restyle(damage),
_ => {},
};
}
/// The maximum version number of this node's descendants, including itself

View file

@ -34,9 +34,9 @@ use dom::bindings::inheritance::{CharacterDataTypeId, ElementTypeId};
use dom::bindings::inheritance::{HTMLElementTypeId, NodeTypeId};
use dom::bindings::js::LayoutJS;
use dom::characterdata::LayoutCharacterDataHelpers;
use dom::document::{Document, LayoutDocumentHelpers};
use dom::document::{Document, LayoutDocumentHelpers, PendingRestyle};
use dom::element::{Element, LayoutElementHelpers, RawLayoutElementHelpers};
use dom::node::{CAN_BE_FRAGMENTED, DIRTY_ON_VIEWPORT_SIZE_CHANGE, HAS_CHANGED, HAS_DIRTY_DESCENDANTS, IS_DIRTY};
use dom::node::{CAN_BE_FRAGMENTED, DIRTY_ON_VIEWPORT_SIZE_CHANGE, HAS_DIRTY_DESCENDANTS, IS_DIRTY};
use dom::node::{LayoutNodeHelpers, Node};
use dom::text::Text;
use gfx_traits::ByteIndex;
@ -46,7 +46,7 @@ use parking_lot::RwLock;
use range::Range;
use script_layout_interface::{HTMLCanvasData, LayoutNodeType, SVGSVGData, TrustedNodeAddress};
use script_layout_interface::{OpaqueStyleAndLayoutData, PartialPersistentLayoutData};
use script_layout_interface::wrapper_traits::{DangerousThreadSafeLayoutNode, GetLayoutData, LayoutElement, LayoutNode};
use script_layout_interface::wrapper_traits::{DangerousThreadSafeLayoutNode, GetLayoutData, LayoutNode};
use script_layout_interface::wrapper_traits::{PseudoElementType, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
use selectors::matching::ElementFlags;
use selectors::parser::{AttrSelector, NamespaceConstraint};
@ -61,11 +61,11 @@ use style::attr::AttrValue;
use style::computed_values::display;
use style::context::SharedStyleContext;
use style::data::ElementData;
use style::dom::{LayoutIterator, NodeInfo, OpaqueNode, PresentationalHintsSynthetizer, TDocument, TElement, TNode};
use style::dom::{TRestyleDamage, UnsafeNode};
use style::dom::{LayoutIterator, NodeInfo, OpaqueNode, PresentationalHintsSynthetizer, TElement, TNode};
use style::dom::UnsafeNode;
use style::element_state::*;
use style::properties::{ComputedValues, PropertyDeclarationBlock};
use style::selector_impl::{NonTSPseudoClass, PseudoElement, RestyleDamage, ServoSelectorImpl, Snapshot};
use style::selector_impl::{NonTSPseudoClass, PseudoElement, RestyleDamage, ServoSelectorImpl};
use style::selector_matching::ApplicableDeclarationBlock;
use style::sink::Push;
use style::str::is_whitespace;
@ -112,6 +112,10 @@ impl<'ln> ServoLayoutNode<'ln> {
self.node.type_id_for_layout()
}
}
pub fn as_document(&self) -> Option<ServoLayoutDocument<'ln>> {
self.node.downcast().map(ServoLayoutDocument::from_layout_js)
}
}
impl<'ln> NodeInfo for ServoLayoutNode<'ln> {
@ -128,7 +132,6 @@ impl<'ln> NodeInfo for ServoLayoutNode<'ln> {
impl<'ln> TNode for ServoLayoutNode<'ln> {
type ConcreteElement = ServoLayoutElement<'ln>;
type ConcreteDocument = ServoLayoutDocument<'ln>;
type ConcreteChildrenIterator = ServoChildrenIterator<'ln>;
fn to_unsafe(&self) -> UnsafeNode {
@ -177,10 +180,6 @@ impl<'ln> TNode for ServoLayoutNode<'ln> {
as_element(self.node)
}
fn as_document(&self) -> Option<ServoLayoutDocument<'ln>> {
self.node.downcast().map(ServoLayoutDocument::from_layout_js)
}
fn needs_dirty_on_viewport_size_changed(&self) -> bool {
unsafe { self.node.get_flag(DIRTY_ON_VIEWPORT_SIZE_CHANGE) }
}
@ -260,12 +259,7 @@ impl<'ln> LayoutNode for ServoLayoutNode<'ln> {
self.get_jsmanaged().take_style_and_layout_data()
}
fn has_changed(&self) -> bool {
unsafe { self.node.get_flag(HAS_CHANGED) }
}
unsafe fn clear_dirty_bits(&self) {
self.node.set_flag(HAS_CHANGED, false);
self.node.set_flag(IS_DIRTY, false);
self.node.set_flag(HAS_DIRTY_DESCENDANTS, false);
}
@ -336,8 +330,8 @@ impl<'ln> ServoLayoutNode<'ln> {
}
fn debug_str(self) -> String {
format!("{:?}: changed={} dirty={} dirty_descendants={}",
self.script_type_id(), self.has_changed(),
format!("{:?}: dirty={} dirty_descendants={}",
self.script_type_id(),
self.as_element().map_or(false, |el| el.deprecated_dirty_bit_is_set()),
self.as_element().map_or(false, |el| el.has_dirty_descendants()))
}
@ -369,34 +363,29 @@ pub struct ServoLayoutDocument<'ld> {
chain: PhantomData<&'ld ()>,
}
impl<'ld> TDocument for ServoLayoutDocument<'ld> {
type ConcreteNode = ServoLayoutNode<'ld>;
type ConcreteElement = ServoLayoutElement<'ld>;
impl<'ld> ServoLayoutDocument<'ld> {
fn as_node(&self) -> ServoLayoutNode<'ld> {
ServoLayoutNode::from_layout_js(self.document.upcast())
}
fn root_node(&self) -> Option<ServoLayoutNode<'ld>> {
pub fn root_node(&self) -> Option<ServoLayoutNode<'ld>> {
self.as_node().children().find(ServoLayoutNode::is_element)
}
fn drain_modified_elements(&self) -> Vec<(ServoLayoutElement<'ld>, Snapshot)> {
let elements = unsafe { self.document.drain_modified_elements() };
pub fn drain_pending_restyles(&self) -> Vec<(ServoLayoutElement<'ld>, PendingRestyle)> {
let elements = unsafe { self.document.drain_pending_restyles() };
elements.into_iter().map(|(el, snapshot)| (ServoLayoutElement::from_layout_js(el), snapshot)).collect()
}
fn needs_paint_from_layout(&self) {
pub fn needs_paint_from_layout(&self) {
unsafe { self.document.needs_paint_from_layout(); }
}
fn will_paint(&self) {
pub fn will_paint(&self) {
unsafe { self.document.will_paint(); }
}
}
impl<'ld> ServoLayoutDocument<'ld> {
fn from_layout_js(doc: LayoutJS<Document>) -> ServoLayoutDocument<'ld> {
pub fn from_layout_js(doc: LayoutJS<Document>) -> ServoLayoutDocument<'ld> {
ServoLayoutDocument {
document: doc,
chain: PhantomData,
@ -433,7 +422,6 @@ impl<'le> PresentationalHintsSynthetizer for ServoLayoutElement<'le> {
impl<'le> TElement for ServoLayoutElement<'le> {
type ConcreteNode = ServoLayoutNode<'le>;
type ConcreteDocument = ServoLayoutDocument<'le>;
fn as_node(&self) -> ServoLayoutNode<'le> {
ServoLayoutNode::from_layout_js(self.element.upcast())
@ -460,7 +448,7 @@ impl<'le> TElement for ServoLayoutElement<'le> {
}
fn set_restyle_damage(self, damage: RestyleDamage) {
self.get_partial_layout_data().unwrap().borrow_mut().restyle_damage = damage;
self.get_partial_layout_data().unwrap().borrow_mut().restyle_damage |= damage;
}
#[inline]
@ -536,6 +524,35 @@ impl<'le> ServoLayoutElement<'le> {
self.get_style_and_layout_data().map(|d| &**d.ptr)
}
}
pub unsafe fn note_dirty_descendant(&self) {
use ::selectors::Element;
let mut current = Some(*self);
while let Some(el) = current {
// FIXME(bholley): Ideally we'd have the invariant that any element
// with has_dirty_descendants also has the bit set on all its ancestors.
// However, there are currently some corner-cases where we get that wrong.
// I have in-flight patches to fix all this stuff up, so we just always
// propagate this bit for now.
el.set_dirty_descendants();
current = el.parent_element();
}
debug_assert!(self.dirty_descendants_bit_is_propagated());
}
fn dirty_descendants_bit_is_propagated(&self) -> bool {
use ::selectors::Element;
let mut current = Some(*self);
while let Some(el) = current {
if !el.has_dirty_descendants() { return false; }
current = el.parent_element();
}
true
}
}
fn as_element<'le>(node: LayoutJS<Node>) -> Option<ServoLayoutElement<'le>> {
@ -857,9 +874,7 @@ impl<'ln> ThreadSafeLayoutNode for ServoThreadSafeLayoutNode<'ln> {
}
fn restyle_damage(self) -> RestyleDamage {
if self.node.has_changed() {
RestyleDamage::rebuild_and_reflow()
} else if self.is_text_node() {
if self.is_text_node() {
let parent = self.node.parent_node().unwrap().as_element().unwrap();
let parent_data = parent.get_partial_layout_data().unwrap().borrow();
parent_data.restyle_damage
@ -1069,8 +1084,6 @@ impl<'le> ThreadSafeLayoutElement for ServoThreadSafeLayoutElement<'le> {
}
}
impl<'le> LayoutElement for ServoLayoutElement<'le> {}
/// This implementation of `::selectors::Element` is used for implementing lazy
/// pseudo-elements.
///