mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
Bug 1294572 - Avoid traversing children of XBL-bound elements during initial styling. r=heycam
MozReview-Commit-ID: JHABvLnMYco
This commit is contained in:
parent
7c7dd0f965
commit
652e891d0d
6 changed files with 73 additions and 14 deletions
|
@ -244,16 +244,6 @@ pub trait TElement : PartialEq + Debug + Sized + Copy + Clone + ElementExt + Pre
|
||||||
/// traversal. Returns the number of children left to process.
|
/// traversal. Returns the number of children left to process.
|
||||||
fn did_process_child(&self) -> isize;
|
fn did_process_child(&self) -> isize;
|
||||||
|
|
||||||
/// Returns true if this element's style is display:none. Panics if
|
|
||||||
/// the element has no style.
|
|
||||||
fn is_display_none(&self) -> bool {
|
|
||||||
let data = self.borrow_data().unwrap();
|
|
||||||
// See the comment on `cascade_node` about getting the up-to-date parent
|
|
||||||
// style for why we allow this on Gecko.
|
|
||||||
debug_assert!(cfg!(feature = "gecko") || data.has_current_styles());
|
|
||||||
data.styles().is_display_none()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets a reference to the ElementData container.
|
/// Gets a reference to the ElementData container.
|
||||||
fn get_data(&self) -> Option<&AtomicRefCell<ElementData>>;
|
fn get_data(&self) -> Option<&AtomicRefCell<ElementData>>;
|
||||||
|
|
||||||
|
|
|
@ -353,6 +353,7 @@ impl<'le> TElement for GeckoElement<'le> {
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn set_dirty_descendants(&self) {
|
unsafe fn set_dirty_descendants(&self) {
|
||||||
|
debug!("Setting dirty descendants: {:?}", self);
|
||||||
self.set_flags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO as u32)
|
self.set_flags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO as u32)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -170,6 +170,11 @@ impl ComputedValues {
|
||||||
self.custom_properties.as_ref().map(|x| x.clone())
|
self.custom_properties.as_ref().map(|x| x.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn has_moz_binding(&self) -> bool {
|
||||||
|
!self.get_box().gecko.mBinding.mRawPtr.is_null()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn root_font_size(&self) -> Au { self.root_font_size }
|
pub fn root_font_size(&self) -> Au { self.root_font_size }
|
||||||
pub fn set_root_font_size(&mut self, s: Au) { self.root_font_size = s; }
|
pub fn set_root_font_size(&mut self, s: Au) { self.root_font_size = s; }
|
||||||
pub fn set_writing_mode(&mut self, mode: WritingMode) { self.writing_mode = mode; }
|
pub fn set_writing_mode(&mut self, mode: WritingMode) { self.writing_mode = mode; }
|
||||||
|
|
|
@ -1199,6 +1199,8 @@ impl ComputedValues {
|
||||||
self.custom_properties.as_ref().map(|x| x.clone())
|
self.custom_properties.as_ref().map(|x| x.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_moz_binding(&self) -> bool { false }
|
||||||
|
|
||||||
pub fn root_font_size(&self) -> Au { self.root_font_size }
|
pub fn root_font_size(&self) -> Au { self.root_font_size }
|
||||||
pub fn set_root_font_size(&mut self, size: Au) { self.root_font_size = size }
|
pub fn set_root_font_size(&mut self, size: Au) { self.root_font_size = size }
|
||||||
pub fn set_writing_mode(&mut self, mode: WritingMode) { self.writing_mode = mode; }
|
pub fn set_writing_mode(&mut self, mode: WritingMode) { self.writing_mode = mode; }
|
||||||
|
|
|
@ -117,6 +117,16 @@ impl PreTraverseToken {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enum to prevent duplicate logging.
|
||||||
|
pub enum LogBehavior {
|
||||||
|
MayLog,
|
||||||
|
DontLog,
|
||||||
|
}
|
||||||
|
use self::LogBehavior::*;
|
||||||
|
impl LogBehavior {
|
||||||
|
fn allow(&self) -> bool { match *self { MayLog => true, DontLog => false, } }
|
||||||
|
}
|
||||||
|
|
||||||
pub trait DomTraversalContext<N: TNode> {
|
pub trait DomTraversalContext<N: TNode> {
|
||||||
type SharedContext: Sync + 'static + Borrow<SharedStyleContext>;
|
type SharedContext: Sync + 'static + Borrow<SharedStyleContext>;
|
||||||
|
|
||||||
|
@ -227,11 +237,60 @@ pub trait DomTraversalContext<N: TNode> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if traversal of this element's children is allowed. We use
|
||||||
|
/// this to cull traversal of various subtrees.
|
||||||
|
///
|
||||||
|
/// This may be called multiple times when processing an element, so we pass
|
||||||
|
/// a parameter to keep the logs tidy.
|
||||||
|
fn should_traverse_children(parent: N::ConcreteElement, parent_data: &ElementData,
|
||||||
|
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());
|
||||||
|
|
||||||
|
// If the parent computed display:none, we don't style the subtree.
|
||||||
|
if parent_data.styles().is_display_none() {
|
||||||
|
if log.allow() { debug!("Parent {:?} is display:none, culling traversal", parent); }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gecko-only XBL handling.
|
||||||
|
//
|
||||||
|
// If we're computing initial styles and the parent has a Gecko XBL
|
||||||
|
// binding, that binding may inject anonymous children and remap the
|
||||||
|
// explicit children to an insertion point (or hide them entirely). It
|
||||||
|
// may also specify a scoped stylesheet, which changes the rules that
|
||||||
|
// apply within the subtree. These two effects can invalidate the result
|
||||||
|
// of property inheritance and selector matching (respectively) within the
|
||||||
|
// subtree.
|
||||||
|
//
|
||||||
|
// To avoid wasting work, we defer initial styling of XBL subtrees
|
||||||
|
// until frame construction, which does an explicit traversal of the
|
||||||
|
// unstyled children after shuffling the subtree. That explicit
|
||||||
|
// traversal may in turn find other bound elements, which get handled
|
||||||
|
// in the same way.
|
||||||
|
//
|
||||||
|
// We explicitly avoid handling restyles here (explicitly removing or
|
||||||
|
// changing bindings), since that adds complexity and is rarer. If it
|
||||||
|
// happens, we may just end up doing wasted work, since Gecko
|
||||||
|
// recursively drops Servo ElementData when the XBL insertion parent of
|
||||||
|
// an Element is changed.
|
||||||
|
if cfg!(feature = "gecko") && parent_data.is_styled_initial() &&
|
||||||
|
parent_data.styles().primary.values.has_moz_binding() {
|
||||||
|
if log.allow() { debug!("Parent {:?} has XBL binding, deferring traversal", parent); }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper for the traversal implementations to select the children that
|
/// Helper for the traversal implementations to select the children that
|
||||||
/// should be enqueued for processing.
|
/// should be enqueued for processing.
|
||||||
fn traverse_children<F: FnMut(N)>(parent: N::ConcreteElement, mut f: F)
|
fn traverse_children<F: FnMut(N)>(parent: N::ConcreteElement, mut f: F)
|
||||||
{
|
{
|
||||||
if parent.is_display_none() {
|
// Check if we're allowed to traverse past this element.
|
||||||
|
if !Self::should_traverse_children(parent, &parent.borrow_data().unwrap(), MayLog) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,7 +346,9 @@ pub fn style_element_in_display_none_subtree<'a, E, C, F>(element: E,
|
||||||
{
|
{
|
||||||
// Check the base case.
|
// Check the base case.
|
||||||
if element.get_data().is_some() {
|
if element.get_data().is_some() {
|
||||||
debug_assert!(element.is_display_none());
|
// 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;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -351,7 +412,7 @@ pub fn recalc_style_at<'a, E, C, D>(context: &'a C,
|
||||||
trace!("propagated_hint={:?}, inherited_style_changed={:?}", propagated_hint, inherited_style_changed);
|
trace!("propagated_hint={:?}, inherited_style_changed={:?}", propagated_hint, inherited_style_changed);
|
||||||
|
|
||||||
// Preprocess children, propagating restyle hints and handling sibling relationships.
|
// Preprocess children, propagating restyle hints and handling sibling relationships.
|
||||||
if !data.styles().is_display_none() &&
|
if D::should_traverse_children(element, &data, DontLog) &&
|
||||||
(element.has_dirty_descendants() || !propagated_hint.is_empty() || inherited_style_changed) {
|
(element.has_dirty_descendants() || !propagated_hint.is_empty() || inherited_style_changed) {
|
||||||
preprocess_children::<_, _, D>(context, element, propagated_hint, inherited_style_changed);
|
preprocess_children::<_, _, D>(context, element, propagated_hint, inherited_style_changed);
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,7 +131,7 @@ fn traverse_subtree(element: GeckoElement, raw_data: RawServoStyleSetBorrowed,
|
||||||
// When new content is inserted in a display:none subtree, we will call into
|
// 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.
|
// servo to try to style it. Detect that here and bail out.
|
||||||
if let Some(parent) = element.parent_element() {
|
if let Some(parent) = element.parent_element() {
|
||||||
if parent.get_data().is_none() || parent.is_display_none() {
|
if parent.borrow_data().map_or(true, |d| d.styles().is_display_none()) {
|
||||||
debug!("{:?} has unstyled parent - ignoring call to traverse_subtree", parent);
|
debug!("{:?} has unstyled parent - ignoring call to traverse_subtree", parent);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue