mirror of
https://github.com/servo/servo.git
synced 2025-08-06 06:00:15 +01:00
Auto merge of #26484 - servo:layout-2020-style-prep, r=SimonSapin
Implement concept of dirty root
This commit is contained in:
commit
79b6758cb9
16 changed files with 294 additions and 97 deletions
|
@ -45,11 +45,12 @@ impl<T> DomRefCell<T> {
|
|||
&mut *self.value.as_ptr()
|
||||
}
|
||||
|
||||
/// Version of the above that we use during restyle while the script thread
|
||||
/// is blocked.
|
||||
pub fn borrow_mut_for_layout(&self) -> RefMut<T> {
|
||||
/// Mutably borrow a cell for layout. Ideally this would use
|
||||
/// `RefCell::try_borrow_mut_unguarded` but that doesn't exist yet.
|
||||
#[allow(unsafe_code)]
|
||||
pub unsafe fn borrow_mut_for_layout(&self) -> &mut T {
|
||||
debug_assert!(thread_state::get().is_layout());
|
||||
self.value.borrow_mut()
|
||||
&mut *self.value.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -387,6 +387,8 @@ pub struct Document {
|
|||
animation_timeline: DomRefCell<AnimationTimeline>,
|
||||
/// Animations for this Document
|
||||
animations: DomRefCell<Animations>,
|
||||
/// The nearest inclusive ancestors to all the nodes that require a restyle.
|
||||
dirty_root: MutNullableDom<Element>,
|
||||
}
|
||||
|
||||
#[derive(JSTraceable, MallocSizeOf)]
|
||||
|
@ -446,6 +448,112 @@ enum ElementLookupResult {
|
|||
|
||||
#[allow(non_snake_case)]
|
||||
impl Document {
|
||||
pub fn note_node_with_dirty_descendants(&self, node: &Node) {
|
||||
debug_assert!(*node.owner_doc() == *self);
|
||||
if !node.is_connected() {
|
||||
return;
|
||||
}
|
||||
|
||||
let parent = match node.inclusive_ancestors(ShadowIncluding::Yes).nth(1) {
|
||||
Some(parent) => parent,
|
||||
None => {
|
||||
// There is no parent so this is the Document node, so we
|
||||
// behave as if we were called with the document element.
|
||||
let document_element = match self.GetDocumentElement() {
|
||||
Some(element) => element,
|
||||
None => return,
|
||||
};
|
||||
if let Some(dirty_root) = self.dirty_root.get() {
|
||||
// There was an existing dirty root so we mark its
|
||||
// ancestors as dirty until the document element.
|
||||
for ancestor in dirty_root
|
||||
.upcast::<Node>()
|
||||
.inclusive_ancestors(ShadowIncluding::Yes)
|
||||
{
|
||||
if ancestor.is::<Element>() {
|
||||
ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.dirty_root.set(Some(&document_element));
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
if parent.is::<Element>() {
|
||||
if !parent.is_styled() {
|
||||
return;
|
||||
}
|
||||
|
||||
if parent.is_display_none() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let element_parent: DomRoot<Element>;
|
||||
let element = match node.downcast::<Element>() {
|
||||
Some(element) => element,
|
||||
None => {
|
||||
// Current node is not an element, it's probably a text node,
|
||||
// we try to get its element parent.
|
||||
match DomRoot::downcast::<Element>(parent) {
|
||||
Some(parent) => {
|
||||
element_parent = parent;
|
||||
&element_parent
|
||||
},
|
||||
None => {
|
||||
// Parent is not an element so it must be a document,
|
||||
// and this is not an element either, so there is
|
||||
// nothing to do.
|
||||
return;
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let dirty_root = match self.dirty_root.get() {
|
||||
None => {
|
||||
element
|
||||
.upcast::<Node>()
|
||||
.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true);
|
||||
self.dirty_root.set(Some(element));
|
||||
return;
|
||||
},
|
||||
Some(root) => root,
|
||||
};
|
||||
|
||||
for ancestor in element
|
||||
.upcast::<Node>()
|
||||
.inclusive_ancestors(ShadowIncluding::Yes)
|
||||
{
|
||||
if ancestor.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS) {
|
||||
return;
|
||||
}
|
||||
if ancestor.is::<Element>() {
|
||||
ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true);
|
||||
}
|
||||
}
|
||||
|
||||
let new_dirty_root = element
|
||||
.upcast::<Node>()
|
||||
.common_ancestor(dirty_root.upcast(), ShadowIncluding::Yes);
|
||||
|
||||
let mut has_dirty_descendants = true;
|
||||
for ancestor in dirty_root
|
||||
.upcast::<Node>()
|
||||
.inclusive_ancestors(ShadowIncluding::Yes)
|
||||
{
|
||||
ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, has_dirty_descendants);
|
||||
has_dirty_descendants &= *ancestor != *new_dirty_root;
|
||||
}
|
||||
self.dirty_root
|
||||
.set(Some(new_dirty_root.downcast::<Element>().unwrap()));
|
||||
}
|
||||
|
||||
pub fn take_dirty_root(&self) -> Option<DomRoot<Element>> {
|
||||
self.dirty_root.take()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn loader(&self) -> Ref<DocumentLoader> {
|
||||
self.loader.borrow()
|
||||
|
@ -967,8 +1075,14 @@ impl Document {
|
|||
}
|
||||
|
||||
pub fn dirty_all_nodes(&self) {
|
||||
let root = self.upcast::<Node>();
|
||||
for node in root.traverse_preorder(ShadowIncluding::Yes) {
|
||||
let root = match self.GetDocumentElement() {
|
||||
Some(root) => root,
|
||||
None => return,
|
||||
};
|
||||
for node in root
|
||||
.upcast::<Node>()
|
||||
.traverse_preorder(ShadowIncluding::Yes)
|
||||
{
|
||||
node.dirty(NodeDamage::OtherNodeDamage)
|
||||
}
|
||||
}
|
||||
|
@ -2917,6 +3031,7 @@ impl Document {
|
|||
DomRefCell::new(AnimationTimeline::new())
|
||||
},
|
||||
animations: DomRefCell::new(Animations::new()),
|
||||
dirty_root: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -309,6 +309,7 @@ impl Element {
|
|||
restyle.hint.insert(RestyleHint::RESTYLE_SELF);
|
||||
|
||||
if damage == NodeDamage::OtherNodeDamage {
|
||||
doc.note_node_with_dirty_descendants(self.upcast());
|
||||
restyle.damage = RestyleDamage::rebuild_and_reflow();
|
||||
}
|
||||
}
|
||||
|
@ -515,6 +516,7 @@ impl Element {
|
|||
|
||||
pub fn detach_shadow(&self) {
|
||||
if let Some(ref shadow_root) = self.shadow_root() {
|
||||
self.upcast::<Node>().note_dirty_descendants();
|
||||
shadow_root.detach();
|
||||
self.ensure_rare_data().shadow_root = None;
|
||||
} else {
|
||||
|
|
|
@ -150,12 +150,9 @@ pub struct Node {
|
|||
/// are this node.
|
||||
ranges: WeakRangeVec,
|
||||
|
||||
/// Style+Layout information. Only the layout thread may touch this data.
|
||||
///
|
||||
/// Must be sent back to the layout thread to be destroyed when this
|
||||
/// node is finalized.
|
||||
#[ignore_malloc_size_of = "Unsafe cell"]
|
||||
style_and_layout_data: UnsafeCell<Option<Box<StyleAndOpaqueLayoutData>>>,
|
||||
/// Style+Layout information.
|
||||
#[ignore_malloc_size_of = "trait object"]
|
||||
style_and_layout_data: DomRefCell<Option<Box<StyleAndOpaqueLayoutData>>>,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
|
@ -318,6 +315,8 @@ impl Node {
|
|||
/// Fails unless `child` is a child of this node.
|
||||
fn remove_child(&self, child: &Node, cached_index: Option<u32>) {
|
||||
assert!(child.parent_node.get().as_deref() == Some(self));
|
||||
self.note_dirty_descendants();
|
||||
|
||||
let prev_sibling = child.GetPreviousSibling();
|
||||
match prev_sibling {
|
||||
None => {
|
||||
|
@ -630,17 +629,7 @@ impl Node {
|
|||
|
||||
// FIXME(emilio): This and the function below should move to Element.
|
||||
pub fn note_dirty_descendants(&self) {
|
||||
debug_assert!(self.is_connected());
|
||||
|
||||
for ancestor in self.inclusive_ancestors(ShadowIncluding::Yes) {
|
||||
if ancestor.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ancestor.is::<Element>() {
|
||||
ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true);
|
||||
}
|
||||
}
|
||||
self.owner_doc().note_node_with_dirty_descendants(self);
|
||||
}
|
||||
|
||||
pub fn has_dirty_descendants(&self) -> bool {
|
||||
|
@ -708,6 +697,22 @@ impl Node {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn common_ancestor(
|
||||
&self,
|
||||
other: &Node,
|
||||
shadow_including: ShadowIncluding,
|
||||
) -> DomRoot<Node> {
|
||||
for ancestor in self.inclusive_ancestors(shadow_including) {
|
||||
if other
|
||||
.inclusive_ancestors(shadow_including)
|
||||
.any(|node| node == ancestor)
|
||||
{
|
||||
return ancestor;
|
||||
}
|
||||
}
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
pub fn is_inclusive_ancestor_of(&self, parent: &Node) -> bool {
|
||||
self == parent || self.is_ancestor_of(parent)
|
||||
}
|
||||
|
@ -1246,21 +1251,38 @@ impl Node {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
pub fn style(&self) -> Option<Arc<ComputedValues>> {
|
||||
if !window_from_node(self).layout_reflow(QueryMsg::StyleQuery) {
|
||||
return None;
|
||||
}
|
||||
unsafe {
|
||||
(*self.style_and_layout_data.get()).as_ref().map(|data| {
|
||||
pub fn is_styled(&self) -> bool {
|
||||
self.style_and_layout_data.borrow().is_some()
|
||||
}
|
||||
|
||||
pub fn is_display_none(&self) -> bool {
|
||||
self.style_and_layout_data
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map_or(true, |data| {
|
||||
data.style_data
|
||||
.element_data
|
||||
.borrow()
|
||||
.styles
|
||||
.primary()
|
||||
.clone()
|
||||
.get_box()
|
||||
.display
|
||||
.is_none()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn style(&self) -> Option<Arc<ComputedValues>> {
|
||||
if !window_from_node(self).layout_reflow(QueryMsg::StyleQuery) {
|
||||
return None;
|
||||
}
|
||||
self.style_and_layout_data.borrow().as_ref().map(|data| {
|
||||
data.style_data
|
||||
.element_data
|
||||
.borrow()
|
||||
.styles
|
||||
.primary()
|
||||
.clone()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1444,13 +1466,21 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
|
|||
#[inline]
|
||||
#[allow(unsafe_code)]
|
||||
fn get_style_and_opaque_layout_data(self) -> Option<&'dom StyleAndOpaqueLayoutData> {
|
||||
unsafe { (*self.unsafe_get().style_and_layout_data.get()).as_deref() }
|
||||
unsafe {
|
||||
self.unsafe_get()
|
||||
.style_and_layout_data
|
||||
.borrow_for_layout()
|
||||
.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(unsafe_code)]
|
||||
unsafe fn init_style_and_opaque_layout_data(self, val: Box<StyleAndOpaqueLayoutData>) {
|
||||
let data = &mut *self.unsafe_get().style_and_layout_data.get();
|
||||
let data = self
|
||||
.unsafe_get()
|
||||
.style_and_layout_data
|
||||
.borrow_mut_for_layout();
|
||||
debug_assert!(data.is_none());
|
||||
*data = Some(val);
|
||||
}
|
||||
|
@ -1458,7 +1488,9 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
|
|||
#[inline]
|
||||
#[allow(unsafe_code)]
|
||||
unsafe fn take_style_and_opaque_layout_data(self) -> Box<StyleAndOpaqueLayoutData> {
|
||||
(*self.unsafe_get().style_and_layout_data.get())
|
||||
self.unsafe_get()
|
||||
.style_and_layout_data
|
||||
.borrow_mut_for_layout()
|
||||
.take()
|
||||
.unwrap()
|
||||
}
|
||||
|
@ -1649,7 +1681,7 @@ where
|
|||
}
|
||||
|
||||
/// Whether a tree traversal should pass shadow tree boundaries.
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum ShadowIncluding {
|
||||
No,
|
||||
Yes,
|
||||
|
@ -1775,7 +1807,7 @@ impl Node {
|
|||
inclusive_descendants_version: Cell::new(0),
|
||||
ranges: WeakRangeVec::new(),
|
||||
|
||||
style_and_layout_data: UnsafeCell::new(None),
|
||||
style_and_layout_data: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -296,19 +296,8 @@ impl RangeMethods for Range {
|
|||
|
||||
// https://dom.spec.whatwg.org/#dom-range-commonancestorcontainer
|
||||
fn CommonAncestorContainer(&self) -> DomRoot<Node> {
|
||||
let end_container = self.EndContainer();
|
||||
// Step 1.
|
||||
for container in self
|
||||
.StartContainer()
|
||||
.inclusive_ancestors(ShadowIncluding::No)
|
||||
{
|
||||
// Step 2.
|
||||
if container.is_inclusive_ancestor_of(&end_container) {
|
||||
// Step 3.
|
||||
return container;
|
||||
}
|
||||
}
|
||||
unreachable!();
|
||||
self.EndContainer()
|
||||
.common_ancestor(&self.StartContainer(), ShadowIncluding::No)
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-range-setstart
|
||||
|
|
|
@ -281,7 +281,7 @@ impl<'dom> LayoutShadowRootHelpers<'dom> for LayoutDom<'dom, ShadowRoot> {
|
|||
quirks_mode: QuirksMode,
|
||||
guard: &SharedRwLockReadGuard,
|
||||
) {
|
||||
let mut author_styles = (*self.unsafe_get()).author_styles.borrow_mut_for_layout();
|
||||
let author_styles = self.unsafe_get().author_styles.borrow_mut_for_layout();
|
||||
if author_styles.stylesheets.dirty() {
|
||||
author_styles.flush::<E>(device, quirks_mode, guard);
|
||||
}
|
||||
|
|
|
@ -1659,6 +1659,14 @@ impl Window {
|
|||
document.flush_dirty_canvases();
|
||||
}
|
||||
|
||||
let pending_restyles = document.drain_pending_restyles();
|
||||
|
||||
let dirty_root = document
|
||||
.take_dirty_root()
|
||||
.filter(|_| !stylesheets_changed)
|
||||
.or_else(|| document.GetDocumentElement())
|
||||
.map(|root| root.upcast::<Node>().to_trusted_node_address());
|
||||
|
||||
// Send new document and relevant styles to layout.
|
||||
let needs_display = reflow_goal.needs_display();
|
||||
let reflow = ScriptReflow {
|
||||
|
@ -1666,13 +1674,14 @@ impl Window {
|
|||
page_clip_rect: self.page_clip_rect.get(),
|
||||
},
|
||||
document: document.upcast::<Node>().to_trusted_node_address(),
|
||||
dirty_root,
|
||||
stylesheets_changed,
|
||||
window_size: self.window_size.get(),
|
||||
origin: self.origin().immutable().clone(),
|
||||
reflow_goal,
|
||||
script_join_chan: join_chan,
|
||||
dom_count: document.dom_count(),
|
||||
pending_restyles: document.drain_pending_restyles(),
|
||||
pending_restyles,
|
||||
animation_timeline_value: document.current_animation_timeline_value(),
|
||||
animations: document.animations().sets.clone(),
|
||||
};
|
||||
|
@ -1770,12 +1779,17 @@ impl Window {
|
|||
|
||||
// We shouldn't need a reflow immediately after a
|
||||
// reflow, except if we're waiting for a deferred paint.
|
||||
assert!({
|
||||
let condition = self.Document().needs_reflow();
|
||||
condition.is_none() ||
|
||||
(!for_display && condition == Some(ReflowTriggerCondition::PaintPostponed)) ||
|
||||
self.suppress_reflow.get()
|
||||
});
|
||||
let condition = self.Document().needs_reflow();
|
||||
assert!(
|
||||
{
|
||||
condition.is_none() ||
|
||||
(!for_display &&
|
||||
condition == Some(ReflowTriggerCondition::PaintPostponed)) ||
|
||||
self.suppress_reflow.get()
|
||||
},
|
||||
"condition was {:?}",
|
||||
condition
|
||||
);
|
||||
} else {
|
||||
debug!(
|
||||
"Document doesn't need reflow - skipping it (reason {:?})",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue