--- a/servo/components/layout/query.rs
+++ b/servo/components/layout/query.rs
@@ -1,17 +1,17 @@
/* 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/. */
//! Utilities for querying the layout, as needed by the layout thread.
use app_units::Au;
use construct::ConstructionResult;
-use context::{ScopedThreadLocalLayoutContext, SharedLayoutContext};
+use context::SharedLayoutContext;
use euclid::point::Point2D;
use euclid::rect::Rect;
use euclid::size::Size2D;
use flow::{self, Flow};
use fragment::{Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo};
use gfx::display_list::{DisplayItemMetadata, DisplayList, OpaqueNode, ScrollOffsetMap};
use gfx_traits::ScrollRootId;
use ipc_channel::ipc::IpcSender;
@@ -24,17 +24,17 @@ use script_layout_interface::rpc::{NodeS
use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
use script_traits::LayoutMsg as ConstellationMsg;
use script_traits::UntrustedNodeAddress;
use sequential;
use std::cmp::{min, max};
use std::ops::Deref;
use std::sync::{Arc, Mutex};
use style::computed_values;
-use style::context::StyleContext;
+use style::context::{StyleContext, ThreadLocalStyleContext};
use style::dom::TElement;
use style::logical_geometry::{WritingMode, BlockFlowDirection, InlineBaseDirection};
use style::properties::{style_structs, PropertyId, PropertyDeclarationId, LonghandId};
use style::properties::longhands::{display, position};
use style::selector_parser::PseudoElement;
use style::stylist::Stylist;
use style_traits::ToCss;
use style_traits::cursor::Cursor;
@@ -621,47 +621,56 @@ pub fn process_node_scroll_area_request<
Size2D::new(right, iterator.origin_rect.size.height))
}
}
}
/// Return the resolved value of property for a given (pseudo)element.
/// https://drafts.csswg.org/cssom/#resolved-value
pub fn process_resolved_style_request<'a, N>(shared: &SharedLayoutContext,
- requested_node: N,
+ node: N,
pseudo: &Option<PseudoElement>,
property: &PropertyId,
layout_root: &mut Flow) -> String
where N: LayoutNode,
{
- use style::traversal::{clear_descendant_data, style_element_in_display_none_subtree};
- let element = requested_node.as_element().unwrap();
+ use style::traversal::resolve_style;
+ let element = node.as_element().unwrap();
// We call process_resolved_style_request after performing a whole-document
- // traversal, so the only reason we wouldn't have an up-to-date style here
- // is that the requested node is in a display:none subtree. We currently
- // maintain the invariant that elements in display:none subtrees always have
- // no ElementData, so we need to temporarily bend those invariants here, and
- // then throw them the style data away again before returning to preserve them.
- // We could optimize this later to keep the style data cached somehow, but
- // we'd need a mechanism to prevent detect when it's stale (since we don't
- // traverse display:none subtrees during restyle).
- let display_none_root = if element.get_data().is_none() {
- let mut tlc = ScopedThreadLocalLayoutContext::new(shared);
- let context = StyleContext {
- shared: &shared.style_context,
- thread_local: &mut tlc.style_context,
- };
+ // traversal, so in the common case, the element is styled.
+ if element.get_data().is_some() {
+ return process_resolved_style_request_internal(node, pseudo, property, layout_root);
+ }
- Some(style_element_in_display_none_subtree(&context, element,
- &|e| e.as_node().initialize_data()))
- } else {
- None
+ // However, the element may be in a display:none subtree. The style system
+ // has a mechanism to give us that within a defined scope (after which point
+ // it's cleared to maintained style system invariants).
+ let mut tlc = ThreadLocalStyleContext::new(&shared.style_context);
+ let context = StyleContext {
+ shared: &shared.style_context,
+ thread_local: &mut tlc,
};
+ let mut result = None;
+ let ensure = |el: N::ConcreteElement| el.as_node().initialize_data();
+ let clear = |el: N::ConcreteElement| el.as_node().clear_data();
+ resolve_style(&context, element, &ensure, &clear, |_: &_| {
+ let s = process_resolved_style_request_internal(node, pseudo, property, layout_root);
+ result = Some(s);
+ });
+ result.unwrap()
+}
+/// The primary resolution logic, which assumes that the element is styled.
+fn process_resolved_style_request_internal<'a, N>(requested_node: N,
+ pseudo: &Option<PseudoElement>,
+ property: &PropertyId,
+ layout_root: &mut Flow) -> String
+ where N: LayoutNode,
+{
let layout_el = requested_node.to_threadsafe().as_element().unwrap();
let layout_el = match *pseudo {
Some(PseudoElement::Before) => layout_el.get_before_pseudo(),
Some(PseudoElement::After) => layout_el.get_after_pseudo(),
Some(PseudoElement::DetailsSummary) |
Some(PseudoElement::DetailsContent) |
Some(PseudoElement::Selection) => None,
_ => Some(layout_el)
@@ -686,20 +695,16 @@ pub fn process_resolved_style_request<'a
// so this should be web-compatible.
PropertyId::Shorthand(_) => return String::new(),
PropertyId::Custom(ref name) => {
return style.computed_value_to_string(PropertyDeclarationId::Custom(name))
}
};
- // Clear any temporarily-resolved data to maintain our invariants. See the comment
- // at the top of this function.
- display_none_root.map(|r| clear_descendant_data(r, &|e| e.as_node().clear_data()));
-
let positioned = match style.get_box().position {
position::computed_value::T::relative |
/*position::computed_value::T::sticky |*/
position::computed_value::T::fixed |
position::computed_value::T::absolute => true,
_ => false
};
--- a/servo/components/style/build_gecko.rs
+++ b/servo/components/style/build_gecko.rs
@@ -219,17 +219,16 @@ mod bindings {
"NS_RADIUS_.*",
"BORDER_COLOR_.*",
"BORDER_STYLE_.*"
];
let whitelist = [
"RawGecko.*",
"mozilla::ServoElementSnapshot.*",
"mozilla::ConsumeStyleBehavior",
- "mozilla::LazyComputeBehavior",
"mozilla::css::SheetParsingMode",
"mozilla::TraversalRootBehavior",
"mozilla::DisplayItemClip", // Needed because bindgen generates
// specialization tests for this even
// though it shouldn't.
"mozilla::StyleShapeRadius",
".*ThreadSafe.*Holder",
"AnonymousContent",
@@ -246,17 +245,16 @@ mod bindings {
"FragmentOrURL",
"FrameRequestCallback",
"gfxAlternateValue",
"gfxFontFeature",
"gfxFontVariation",
"GridNamedArea",
"Image",
"ImageURL",
- "LazyComputeBehavior",
"nsAttrName",
"nsAttrValue",
"nsBorderColors",
"nscolor",
"nsChangeHint",
"nsCSSKeyword",
"nsCSSPropertyID",
"nsCSSRect",
@@ -446,17 +444,16 @@ mod bindings {
.whitelisted_function("Gecko_.*");
let structs_types = [
"RawGeckoDocument",
"RawGeckoElement",
"RawGeckoNode",
"ThreadSafeURIHolder",
"ThreadSafePrincipalHolder",
"ConsumeStyleBehavior",
- "LazyComputeBehavior",
"TraversalRootBehavior",
"FontFamilyList",
"FontFamilyType",
"ServoElementSnapshot",
"SheetParsingMode",
"StyleBasicShape",
"StyleBasicShapeType",
"StyleClipPath",
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -255,24 +255,16 @@ impl<'le> GeckoElement<'le> {
// Perform a mutable borrow of the data in debug builds. This
// serves as an assertion that there are no outstanding borrows
// when we destroy the data.
debug_assert!({ let _ = data.borrow_mut(); true });
}
}
- pub fn get_pseudo_style(&self, pseudo: &PseudoElement) -> Option<Arc<ComputedValues>> {
- // FIXME(bholley): Gecko sometimes resolves pseudos after an element has
- // already been marked for restyle. We should consider fixing this, and
- // then assert has_current_styles here.
- self.borrow_data().and_then(|data| data.styles().pseudos
- .get(pseudo).map(|c| c.values.clone()))
- }
-
// Only safe to call with exclusive access to the element.
pub unsafe fn ensure_data(&self) -> &AtomicRefCell<ElementData> {
match self.get_data() {
Some(x) => x,
None => {
debug!("Creating ElementData for {:?}", self);
let ptr = Box::into_raw(Box::new(AtomicRefCell::new(ElementData::new(None))));
self.0.mServoData.set(ptr);
--- a/servo/components/style/gecko_bindings/bindings.rs
+++ b/servo/components/style/gecko_bindings/bindings.rs
@@ -4,17 +4,16 @@ pub use nsstring::{nsACString, nsAString
type nsACString_internal = nsACString;
type nsAString_internal = nsAString;
use gecko_bindings::structs::RawGeckoDocument;
use gecko_bindings::structs::RawGeckoElement;
use gecko_bindings::structs::RawGeckoNode;
use gecko_bindings::structs::ThreadSafeURIHolder;
use gecko_bindings::structs::ThreadSafePrincipalHolder;
use gecko_bindings::structs::ConsumeStyleBehavior;
-use gecko_bindings::structs::LazyComputeBehavior;
use gecko_bindings::structs::TraversalRootBehavior;
use gecko_bindings::structs::FontFamilyList;
use gecko_bindings::structs::FontFamilyType;
use gecko_bindings::structs::ServoElementSnapshot;
use gecko_bindings::structs::SheetParsingMode;
use gecko_bindings::structs::StyleBasicShape;
use gecko_bindings::structs::StyleBasicShapeType;
use gecko_bindings::structs::StyleClipPath;
@@ -1204,28 +1203,33 @@ extern "C" {
change_hint: nsChangeHint);
}
extern "C" {
pub fn Servo_CheckChangeHint(element: RawGeckoElementBorrowed)
-> nsChangeHint;
}
extern "C" {
pub fn Servo_ResolveStyle(element: RawGeckoElementBorrowed,
- set: RawServoStyleSetBorrowed,
- consume: ConsumeStyleBehavior,
- compute: LazyComputeBehavior)
+ consume: ConsumeStyleBehavior)
-> ServoComputedValuesStrong;
}
extern "C" {
pub fn Servo_ResolvePseudoStyle(element: RawGeckoElementBorrowed,
pseudo_tag: *mut nsIAtom, is_probe: bool,
set: RawServoStyleSetBorrowed)
-> ServoComputedValuesStrong;
}
extern "C" {
+ pub fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed,
+ pseudo_tag: *mut nsIAtom,
+ consume: ConsumeStyleBehavior,
+ set: RawServoStyleSetBorrowed)
+ -> ServoComputedValuesStrong;
+}
+extern "C" {
pub fn Servo_TraverseSubtree(root: RawGeckoElementBorrowed,
set: RawServoStyleSetBorrowed,
root_behavior: TraversalRootBehavior);
}
extern "C" {
pub fn Servo_AssertTreeIsClean(root: RawGeckoElementBorrowed);
}
extern "C" {
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -704,23 +704,23 @@ pub trait MatchMethods : TElement {
// stick without the assertions.
debug_assert!(pseudo.is_none() ||
new_style.get_box().clone_display() != display::T::none);
RestyleDamage::rebuild_and_reflow()
}
}
}
- unsafe fn cascade_node(&self,
- context: &StyleContext<Self>,
- mut data: &mut AtomicRefMut<ElementData>,
- parent: Option<Self>,
- primary_rule_node: StrongRuleNode,
- pseudo_rule_nodes: PseudoRuleNodes,
- primary_is_shareable: bool)
+ fn cascade_node(&self,
+ context: &StyleContext<Self>,
+ mut data: &mut AtomicRefMut<ElementData>,
+ parent: Option<Self>,
+ primary_rule_node: StrongRuleNode,
+ pseudo_rule_nodes: PseudoRuleNodes,
+ primary_is_shareable: bool)
{
// Get our parent's style.
let parent_data = parent.as_ref().map(|x| x.borrow_data().unwrap());
let parent_style = parent_data.as_ref().map(|d| {
// Sometimes Gecko eagerly styles things without processing pending
// restyles first. In general we'd like to avoid this, but there can
// be good reasons (for example, needing to construct a frame for
// some small piece of newly-added content in order to do something
@@ -765,20 +765,22 @@ pub trait MatchMethods : TElement {
self.compute_damage_and_cascade_pseudos(old_primary,
old_pseudos,
&new_styles.primary.values,
&mut new_styles.pseudos,
context,
pseudo_rule_nodes,
&mut possibly_expired_animations);
- self.as_node().set_can_be_fragmented(parent.map_or(false, |p| {
- p.as_node().can_be_fragmented() ||
- parent_style.unwrap().is_multicol()
- }));
+ unsafe {
+ self.as_node().set_can_be_fragmented(parent.map_or(false, |p| {
+ p.as_node().can_be_fragmented() ||
+ parent_style.unwrap().is_multicol()
+ }));
+ }
damage
};
data.finish_styling(new_styles, damage);
}
fn compute_damage_and_cascade_pseudos(
--- a/servo/components/style/traversal.rs
+++ b/servo/components/style/traversal.rs
@@ -1,17 +1,17 @@
/* 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/. */
//! Traversing the DOM tree; the bloom filter.
use atomic_refcell::{AtomicRefCell, AtomicRefMut};
use context::{SharedStyleContext, StyleContext};
-use data::{ElementData, StoredRestyleHint};
+use data::{ElementData, ElementStyles, StoredRestyleHint};
use dom::{TElement, TNode};
use matching::{MatchMethods, StyleSharingResult};
use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_SELF};
use selector_parser::RestyleDamage;
use selectors::Element;
use selectors::matching::StyleRelations;
use servo_config::opts;
use std::mem;
@@ -260,50 +260,87 @@ pub fn relations_are_shareable(relations
use selectors::matching::*;
!relations.intersects(AFFECTED_BY_ID_SELECTOR |
AFFECTED_BY_PSEUDO_ELEMENTS | AFFECTED_BY_STATE |
AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR |
AFFECTED_BY_STYLE_ATTRIBUTE |
AFFECTED_BY_PRESENTATIONAL_HINTS)
}
-/// Handles lazy resolution of style in display:none subtrees. See the comment
-/// at the callsite in query.rs.
-pub fn style_element_in_display_none_subtree<E, F>(context: &StyleContext<E>,
- element: E, init_data: &F) -> E
+/// Helper for the function below.
+fn resolve_style_internal<E, F>(context: &StyleContext<E>, element: E, ensure_data: &F)
+ -> Option<E>
where E: TElement,
F: Fn(E),
{
- // Check the base case.
- if element.get_data().is_some() {
- // See the comment on `cascade_node` for why we allow this on Gecko.
- debug_assert!(cfg!(feature = "gecko") || element.borrow_data().unwrap().has_current_styles());
- debug_assert!(element.borrow_data().unwrap().styles().is_display_none());
- return element;
- }
+ ensure_data(element);
+ let mut data = element.mutate_data().unwrap();
+ let mut display_none_root = None;
- // Ensure the parent is styled.
- let parent = element.parent_element().unwrap();
- let display_none_root = style_element_in_display_none_subtree(context, parent, init_data);
-
- // Initialize our data.
- init_data(element);
+ // If the Element isn't styled, we need to compute its style.
+ if data.get_styles().is_none() {
+ // Compute the parent style if necessary.
+ if let Some(parent) = element.parent_element() {
+ display_none_root = resolve_style_internal(context, parent, ensure_data);
+ }
- // Resolve our style.
- let mut data = element.mutate_data().unwrap();
- let match_results = element.match_element(context, None);
- unsafe {
+ // Compute our style.
+ let match_results = element.match_element(context, None);
let shareable = match_results.primary_is_shareable();
- element.cascade_node(context, &mut data, Some(parent),
+ element.cascade_node(context, &mut data, element.parent_element(),
match_results.primary,
match_results.per_pseudo,
shareable);
+
+ // Conservatively mark us as having dirty descendants, since there might
+ // be other unstyled siblings we miss when walking straight up the parent
+ // chain.
+ unsafe { element.set_dirty_descendants() };
+ }
+
+ // If we're display:none and none of our ancestors are, we're the root
+ // of a display:none subtree.
+ if display_none_root.is_none() && data.styles().is_display_none() {
+ display_none_root = Some(element);
}
- display_none_root
+ return display_none_root
+}
+
+/// Manually resolve style by sequentially walking up the parent chain to the
+/// first styled Element, ignoring pending restyles. The resolved style is
+/// made available via a callback, and can be dropped by the time this function
+/// returns in the display:none subtree case.
+pub fn resolve_style<E, F, G, H>(context: &StyleContext<E>, element: E,
+ ensure_data: &F, clear_data: &G, callback: H)
+ where E: TElement,
+ F: Fn(E),
+ G: Fn(E),
+ H: FnOnce(&ElementStyles)
+{
+ // Resolve styles up the tree.
+ let display_none_root = resolve_style_internal(context, element, ensure_data);
+
+ // Make them available for the scope of the callback. The callee may use the
+ // argument, or perform any other processing that requires the styles to exist
+ // on the Element.
+ callback(element.borrow_data().unwrap().styles());
+
+ // Clear any styles in display:none subtrees to leave the tree in a valid state.
+ if let Some(root) = display_none_root {
+ let mut curr = element;
+ loop {
+ unsafe { curr.unset_dirty_descendants(); }
+ if curr == root {
+ break;
+ }
+ clear_data(curr);
+ curr = curr.parent_element().unwrap();
+ }
+ }
}
/// Calculates the style for a single node.
#[inline]
#[allow(unsafe_code)]
pub fn recalc_style_at<E, D>(traversal: &D,
traversal_data: &mut PerLevelTraversalData,
context: &mut StyleContext<E>,
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -53,17 +53,17 @@ use style::properties::{PropertyDeclarat
use style::properties::{apply_declarations, parse_one_declaration};
use style::restyle_hints::RestyleHint;
use style::selector_parser::PseudoElementCascadeType;
use style::sequential;
use style::string_cache::Atom;
use style::stylesheets::{CssRule, CssRules, Origin, Stylesheet, StyleRule};
use style::thread_state;
use style::timer::Timer;
-use style::traversal::{recalc_style_at, DomTraversal, PerLevelTraversalData};
+use style::traversal::{resolve_style, DomTraversal};
use style_traits::ToCss;
use stylesheet_loader::StylesheetLoader;
/*
* For Gecko->Servo function calls, we need to redeclare the same signature that was declared in
* the C header in Gecko. In order to catch accidental mismatches, we run rust-bindgen against
* those signatures as well, giving us a second declaration of all the Servo_* functions in this
* crate. If there's a mismatch, LLVM will assert and abort, which is a rather awful thing to
@@ -854,61 +854,24 @@ pub extern "C" fn Servo_CheckChangeHint(
}
debug!("Servo_GetChangeHint: {:?}, damage={:?}", element, damage);
damage.as_change_hint()
}
#[no_mangle]
pub extern "C" fn Servo_ResolveStyle(element: RawGeckoElementBorrowed,
- raw_data: RawServoStyleSetBorrowed,
- consume: structs::ConsumeStyleBehavior,
- compute: structs::LazyComputeBehavior) -> ServoComputedValuesStrong
+ consume: structs::ConsumeStyleBehavior)
+ -> ServoComputedValuesStrong
{
let element = GeckoElement(element);
- debug!("Servo_ResolveStyle: {:?}, consume={:?}, compute={:?}", element, consume, compute);
+ debug!("Servo_ResolveStyle: {:?}, consume={:?}", element, consume);
let mut data = unsafe { element.ensure_data() }.borrow_mut();
- if compute == structs::LazyComputeBehavior::Allow {
- let should_compute = !data.has_current_styles();
- if should_compute {
- debug!("Performing manual style computation");
- if let Some(parent) = element.parent_element() {
- if parent.borrow_data().map_or(true, |d| !d.has_current_styles()) {
- error!("Attempting manual style computation with unstyled parent");
- return Arc::new(ComputedValues::initial_values().clone()).into_strong();
- }
- }
-
- let per_doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
- let shared_style_context = create_shared_context(&per_doc_data);
- let traversal = RecalcStyleOnly::new(shared_style_context);
-
- let mut traversal_data = PerLevelTraversalData {
- current_dom_depth: None,
- };
-
- let mut tlc = ThreadLocalStyleContext::new(traversal.shared_context());
- let mut context = StyleContext {
- shared: traversal.shared_context(),
- thread_local: &mut tlc,
- };
-
- recalc_style_at(&traversal, &mut traversal_data, &mut context, element, &mut data);
-
- // The element was either unstyled or needed restyle. If it was unstyled, it may have
- // additional unstyled children that subsequent traversals won't find now that the style
- // on this element is up-to-date. Mark dirty descendants in that case.
- if element.first_child_element().is_some() {
- unsafe { element.set_dirty_descendants() };
- }
- }
- }
-
if !data.has_current_styles() {
error!("Resolving style on unstyled element with lazy computation forbidden.");
return Arc::new(ComputedValues::initial_values().clone()).into_strong();
}
let values = data.styles().primary.values.clone();
if consume == structs::ConsumeStyleBehavior::Consume {
@@ -916,16 +879,73 @@ pub extern "C" fn Servo_ResolveStyle(ele
// drop the data here instead.
data.persist();
}
values.into_strong()
}
#[no_mangle]
+pub extern "C" fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed,
+ pseudo_tag: *mut nsIAtom,
+ consume: structs::ConsumeStyleBehavior,
+ raw_data: RawServoStyleSetBorrowed)
+ -> ServoComputedValuesStrong
+{
+ let element = GeckoElement(element);
+ let doc_data = PerDocumentStyleData::from_ffi(raw_data);
+ let finish = |styles: &ElementStyles| -> Arc<ComputedValues> {
+ let maybe_pseudo = if !pseudo_tag.is_null() {
+ get_pseudo_style(element, pseudo_tag, styles, doc_data)
+ } else {
+ None
+ };
+ maybe_pseudo.unwrap_or_else(|| styles.primary.values.clone())
+ };
+
+ // In the common case we already have the style. Check that before setting
+ // up all the computation machinery.
+ let mut result = element.mutate_data()
+ .and_then(|d| d.get_styles().map(&finish));
+ if result.is_some() {
+ if consume == structs::ConsumeStyleBehavior::Consume {
+ let mut d = element.mutate_data().unwrap();
+ if !d.is_persistent() {
+ // XXXheycam is it right to persist an ElementData::Restyle?
+ // Couldn't we lose restyle hints that would cause us to
+ // restyle descendants?
+ d.persist();
+ }
+ }
+ return result.unwrap().into_strong();
+ }
+
+ // We don't have the style ready. Go ahead and compute it as necessary.
+ let shared = create_shared_context(&mut doc_data.borrow_mut());
+ let mut tlc = ThreadLocalStyleContext::new(&shared);
+ let mut context = StyleContext {
+ shared: &shared,
+ thread_local: &mut tlc,
+ };
+ let ensure = |el: GeckoElement| { unsafe { el.ensure_data(); } };
+ let clear = |el: GeckoElement| el.clear_data();
+ resolve_style(&mut context, element, &ensure, &clear,
+ |styles| result = Some(finish(styles)));
+
+ // Consume the style if requested, though it may not exist anymore if the
+ // element is in a display:none subtree.
+ if consume == structs::ConsumeStyleBehavior::Consume {
+ element.mutate_data().map(|mut d| d.persist());
+ }
+
+ result.unwrap().into_strong()
+}
+
+
+#[no_mangle]
pub extern "C" fn Servo_AssertTreeIsClean(root: RawGeckoElementBorrowed) {
if !cfg!(debug_assertions) {
panic!("Calling Servo_AssertTreeIsClean in release build");
}
let root = GeckoElement(root);
fn assert_subtree_is_clean<'le>(el: GeckoElement<'le>) {
debug_assert!(!el.has_dirty_descendants());