mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
This was the underlying cause of the restyle_hints_state.html failure. It was uncovered only when fixing the HAS_EDGE_CHILD_SELECTOR flags because this was the series of events happening. * <script> starts executing. * All the restyle hint processing takes place. * <script> ends executing, gets appended to the body (I think this is also a bug). * <body> receives children_changed notification, with an Append mutation. * <body> had the HAS_EDGE_CHILD_SELECTOR flag, so due to its bogus value, it restyled the whole tree after <body>, fixing any mishandling of restyle hints.
312 lines
11 KiB
Rust
312 lines
11 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* 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/. */
|
|
|
|
//! Types and traits used to access the DOM from style calculation.
|
|
|
|
#![allow(unsafe_code)]
|
|
|
|
use context::SharedStyleContext;
|
|
use data::PrivateStyleData;
|
|
use element_state::ElementState;
|
|
use properties::{ComputedValues, PropertyDeclaration, PropertyDeclarationBlock};
|
|
use refcell::{Ref, RefMut};
|
|
use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint};
|
|
use selector_impl::{ElementExt, PseudoElement};
|
|
use selectors::matching::DeclarationBlock;
|
|
use sink::Push;
|
|
use std::fmt::Debug;
|
|
use std::ops::BitOr;
|
|
use std::sync::Arc;
|
|
use string_cache::{Atom, Namespace};
|
|
|
|
/// Opaque type stored in type-unsafe work queues for parallel layout.
|
|
/// Must be transmutable to and from TNode.
|
|
pub type UnsafeNode = (usize, usize);
|
|
|
|
/// An opaque handle to a node, which, unlike UnsafeNode, cannot be transformed
|
|
/// back into a non-opaque representation. The only safe operation that can be
|
|
/// performed on this node is to compare it to another opaque handle or to another
|
|
/// OpaqueNode.
|
|
///
|
|
/// Layout and Graphics use this to safely represent nodes for comparison purposes.
|
|
/// Because the script task's GC does not trace layout, node data cannot be safely stored in layout
|
|
/// data structures. Also, layout code tends to be faster when the DOM is not being accessed, for
|
|
/// locality reasons. Using `OpaqueNode` enforces this invariant.
|
|
#[derive(Clone, PartialEq, Copy, Debug, Hash, Eq)]
|
|
#[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
|
|
pub struct OpaqueNode(pub usize);
|
|
|
|
impl OpaqueNode {
|
|
/// Returns the address of this node, for debugging purposes.
|
|
#[inline]
|
|
pub fn id(&self) -> usize {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
pub trait TRestyleDamage : Debug + PartialEq + BitOr<Output=Self> + Copy {
|
|
/// The source for our current computed values in the cascade. This is a
|
|
/// ComputedValues in Servo and a StyleContext in Gecko.
|
|
///
|
|
/// This is needed because Gecko has a few optimisations for the calculation
|
|
/// of the difference depending on which values have been used during
|
|
/// layout.
|
|
///
|
|
/// This should be obtained via TNode::existing_style_for_restyle_damage
|
|
type PreExistingComputedValues;
|
|
|
|
fn compute(old: &Self::PreExistingComputedValues,
|
|
new: &Arc<ComputedValues>) -> Self;
|
|
|
|
fn empty() -> Self;
|
|
|
|
fn rebuild_and_reflow() -> Self;
|
|
}
|
|
|
|
pub trait TNode : Sized + Copy + Clone {
|
|
type ConcreteElement: TElement<ConcreteNode = Self, ConcreteDocument = Self::ConcreteDocument>;
|
|
type ConcreteDocument: TDocument<ConcreteNode = Self, ConcreteElement = Self::ConcreteElement>;
|
|
type ConcreteRestyleDamage: TRestyleDamage;
|
|
|
|
fn to_unsafe(&self) -> UnsafeNode;
|
|
unsafe fn from_unsafe(n: &UnsafeNode) -> Self;
|
|
|
|
/// Returns whether this is a text node. It turns out that this is all the style system cares
|
|
/// about, and thus obviates the need to compute the full type id, which would be expensive in
|
|
/// Gecko.
|
|
fn is_text_node(&self) -> bool;
|
|
|
|
fn is_element(&self) -> bool;
|
|
|
|
fn dump(self);
|
|
|
|
fn dump_style(self);
|
|
|
|
fn traverse_preorder(self) -> TreeIterator<Self> {
|
|
TreeIterator::new(self)
|
|
}
|
|
|
|
/// Returns an iterator over this node's children.
|
|
fn children(self) -> ChildrenIterator<Self> {
|
|
ChildrenIterator {
|
|
current: self.first_child(),
|
|
}
|
|
}
|
|
|
|
fn rev_children(self) -> ReverseChildrenIterator<Self> {
|
|
ReverseChildrenIterator {
|
|
current: self.last_child(),
|
|
}
|
|
}
|
|
|
|
/// Converts self into an `OpaqueNode`.
|
|
fn opaque(&self) -> OpaqueNode;
|
|
|
|
/// While doing a reflow, the node at the root has no parent, as far as we're
|
|
/// concerned. This method returns `None` at the reflow root.
|
|
fn layout_parent_node(self, reflow_root: OpaqueNode) -> Option<Self>;
|
|
|
|
fn debug_id(self) -> usize;
|
|
|
|
fn as_element(&self) -> Option<Self::ConcreteElement>;
|
|
|
|
fn as_document(&self) -> Option<Self::ConcreteDocument>;
|
|
|
|
fn children_count(&self) -> u32;
|
|
|
|
fn has_changed(&self) -> bool;
|
|
|
|
unsafe fn set_changed(&self, value: bool);
|
|
|
|
fn is_dirty(&self) -> bool;
|
|
|
|
unsafe fn set_dirty(&self, value: bool);
|
|
|
|
fn has_dirty_descendants(&self) -> bool;
|
|
|
|
unsafe fn set_dirty_descendants(&self, value: bool);
|
|
|
|
fn needs_dirty_on_viewport_size_changed(&self) -> bool;
|
|
|
|
unsafe fn set_dirty_on_viewport_size_changed(&self);
|
|
|
|
fn can_be_fragmented(&self) -> bool;
|
|
|
|
unsafe fn set_can_be_fragmented(&self, value: bool);
|
|
|
|
/// Borrows the PrivateStyleData without checks.
|
|
#[inline(always)]
|
|
unsafe fn borrow_data_unchecked(&self) -> Option<*const PrivateStyleData>;
|
|
|
|
/// Borrows the PrivateStyleData immutably. Fails on a conflicting borrow.
|
|
#[inline(always)]
|
|
fn borrow_data(&self) -> Option<Ref<PrivateStyleData>>;
|
|
|
|
/// Borrows the PrivateStyleData mutably. Fails on a conflicting borrow.
|
|
#[inline(always)]
|
|
fn mutate_data(&self) -> Option<RefMut<PrivateStyleData>>;
|
|
|
|
/// Get the description of how to account for recent style changes.
|
|
fn restyle_damage(self) -> Self::ConcreteRestyleDamage;
|
|
|
|
/// Set the restyle damage field.
|
|
fn set_restyle_damage(self, damage: Self::ConcreteRestyleDamage);
|
|
|
|
fn parent_node(&self) -> Option<Self>;
|
|
|
|
fn first_child(&self) -> Option<Self>;
|
|
|
|
fn last_child(&self) -> Option<Self>;
|
|
|
|
fn prev_sibling(&self) -> Option<Self>;
|
|
|
|
fn next_sibling(&self) -> Option<Self>;
|
|
|
|
|
|
/// Returns the style results for the given node. If CSS selector matching
|
|
/// has not yet been performed, fails.
|
|
fn style(&self, _context: &SharedStyleContext) -> Ref<Arc<ComputedValues>> {
|
|
Ref::map(self.borrow_data().unwrap(), |data| data.style.as_ref().unwrap())
|
|
}
|
|
|
|
/// Removes the style from this node.
|
|
fn unstyle(self) {
|
|
self.mutate_data().unwrap().style = None;
|
|
}
|
|
|
|
/// XXX: It's a bit unfortunate we need to pass the current computed values
|
|
/// as an argument here, but otherwise Servo would crash due to double
|
|
/// borrows to return it.
|
|
fn existing_style_for_restyle_damage<'a>(&'a self,
|
|
current_computed_values: Option<&'a Arc<ComputedValues>>,
|
|
pseudo: Option<&PseudoElement>)
|
|
-> Option<&'a <Self::ConcreteRestyleDamage as TRestyleDamage>::PreExistingComputedValues>;
|
|
}
|
|
|
|
pub trait TDocument : Sized + Copy + Clone {
|
|
type ConcreteNode: TNode<ConcreteElement = Self::ConcreteElement, ConcreteDocument = Self>;
|
|
type ConcreteElement: TElement<ConcreteNode = Self::ConcreteNode, ConcreteDocument = Self>;
|
|
|
|
fn as_node(&self) -> Self::ConcreteNode;
|
|
|
|
fn root_node(&self) -> Option<Self::ConcreteNode>;
|
|
|
|
fn drain_modified_elements(&self) -> Vec<(Self::ConcreteElement,
|
|
<Self::ConcreteElement as ElementExt>::Snapshot)>;
|
|
}
|
|
|
|
pub trait PresentationalHintsSynthetizer {
|
|
fn synthesize_presentational_hints_for_legacy_attributes<V>(&self, hints: &mut V)
|
|
where V: Push<DeclarationBlock<Vec<PropertyDeclaration>>>;
|
|
}
|
|
|
|
pub trait TElement : PartialEq + Sized + Copy + Clone + ElementExt + PresentationalHintsSynthetizer {
|
|
type ConcreteNode: TNode<ConcreteElement = Self, ConcreteDocument = Self::ConcreteDocument>;
|
|
type ConcreteDocument: TDocument<ConcreteNode = Self::ConcreteNode, ConcreteElement = Self>;
|
|
|
|
fn as_node(&self) -> Self::ConcreteNode;
|
|
|
|
fn style_attribute(&self) -> &Option<PropertyDeclarationBlock>;
|
|
|
|
fn get_state(&self) -> ElementState;
|
|
|
|
fn has_attr(&self, namespace: &Namespace, attr: &Atom) -> bool;
|
|
fn attr_equals(&self, namespace: &Namespace, attr: &Atom, value: &Atom) -> bool;
|
|
|
|
/// Properly marks nodes as dirty in response to restyle hints.
|
|
fn note_restyle_hint(&self, hint: RestyleHint) {
|
|
// Bail early if there's no restyling to do.
|
|
if hint.is_empty() {
|
|
return;
|
|
}
|
|
|
|
// If the restyle hint is non-empty, we need to restyle either this element
|
|
// or one of its siblings. Mark our ancestor chain as having dirty descendants.
|
|
let node = self.as_node();
|
|
let mut curr = node;
|
|
while let Some(parent) = curr.parent_node() {
|
|
if parent.has_dirty_descendants() { break }
|
|
unsafe { parent.set_dirty_descendants(true); }
|
|
curr = parent;
|
|
}
|
|
|
|
// Process hints.
|
|
if hint.contains(RESTYLE_SELF) {
|
|
unsafe { node.set_dirty(true); }
|
|
// XXX(emilio): For now, dirty implies dirty descendants if found.
|
|
} else if hint.contains(RESTYLE_DESCENDANTS) {
|
|
unsafe { node.set_dirty_descendants(true); }
|
|
let mut current = node.first_child();
|
|
while let Some(node) = current {
|
|
unsafe { node.set_dirty(true); }
|
|
current = node.next_sibling();
|
|
}
|
|
}
|
|
|
|
if hint.contains(RESTYLE_LATER_SIBLINGS) {
|
|
let mut next = ::selectors::Element::next_sibling_element(self);
|
|
while let Some(sib) = next {
|
|
let sib_node = sib.as_node();
|
|
unsafe { sib_node.set_dirty(true) };
|
|
next = ::selectors::Element::next_sibling_element(&sib);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct TreeIterator<ConcreteNode> where ConcreteNode: TNode {
|
|
stack: Vec<ConcreteNode>,
|
|
}
|
|
|
|
impl<ConcreteNode> TreeIterator<ConcreteNode> where ConcreteNode: TNode {
|
|
fn new(root: ConcreteNode) -> TreeIterator<ConcreteNode> {
|
|
let mut stack = vec![];
|
|
stack.push(root);
|
|
TreeIterator {
|
|
stack: stack,
|
|
}
|
|
}
|
|
|
|
pub fn next_skipping_children(&mut self) -> Option<ConcreteNode> {
|
|
self.stack.pop()
|
|
}
|
|
}
|
|
|
|
impl<ConcreteNode> Iterator for TreeIterator<ConcreteNode>
|
|
where ConcreteNode: TNode {
|
|
type Item = ConcreteNode;
|
|
fn next(&mut self) -> Option<ConcreteNode> {
|
|
let ret = self.stack.pop();
|
|
ret.map(|node| self.stack.extend(node.rev_children()));
|
|
ret
|
|
}
|
|
}
|
|
|
|
pub struct ChildrenIterator<ConcreteNode> where ConcreteNode: TNode {
|
|
current: Option<ConcreteNode>,
|
|
}
|
|
|
|
impl<ConcreteNode> Iterator for ChildrenIterator<ConcreteNode>
|
|
where ConcreteNode: TNode {
|
|
type Item = ConcreteNode;
|
|
fn next(&mut self) -> Option<ConcreteNode> {
|
|
let node = self.current;
|
|
self.current = node.and_then(|node| node.next_sibling());
|
|
node
|
|
}
|
|
}
|
|
|
|
pub struct ReverseChildrenIterator<ConcreteNode> where ConcreteNode: TNode {
|
|
current: Option<ConcreteNode>,
|
|
}
|
|
|
|
impl<ConcreteNode> Iterator for ReverseChildrenIterator<ConcreteNode>
|
|
where ConcreteNode: TNode {
|
|
type Item = ConcreteNode;
|
|
fn next(&mut self) -> Option<ConcreteNode> {
|
|
let node = self.current;
|
|
self.current = node.and_then(|node| node.prev_sibling());
|
|
node
|
|
}
|
|
}
|