mirror of
https://github.com/servo/servo.git
synced 2025-07-23 15:23:42 +01:00
Keep a list of slot descendants on each shadow root (#35802)
This makes it much faster to traverse over the slot descendants of a shadow root, which is a fairly costly part of "assign slottables to a tree". This reduces the time it takes for the results to load on wpt.fyi from over 3 minutes to about 5 seconds. Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
This commit is contained in:
parent
0419a7818d
commit
fd0e2125c6
3 changed files with 89 additions and 13 deletions
|
@ -29,7 +29,7 @@ use crate::dom::element::{AttributeMutation, Element};
|
|||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::htmlelement::HTMLElement;
|
||||
use crate::dom::mutationobserver::MutationObserver;
|
||||
use crate::dom::node::{Node, NodeDamage, ShadowIncluding};
|
||||
use crate::dom::node::{Node, NodeDamage, NodeTraits, ShadowIncluding};
|
||||
use crate::dom::virtualmethods::VirtualMethods;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
|
@ -407,17 +407,7 @@ impl Slottable {
|
|||
|
||||
// Step 6. Return the first slot in tree order in shadow’s descendants whose
|
||||
// name is slottable’s name, if any; otherwise null.
|
||||
for node in shadow_root
|
||||
.upcast::<Node>()
|
||||
.traverse_preorder(ShadowIncluding::No)
|
||||
{
|
||||
if let Some(slot) = node.downcast::<HTMLSlotElement>() {
|
||||
if slot.Name() == self.name() {
|
||||
return Some(DomRoot::from_ref(slot));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
shadow_root.slot_for_name(&self.name())
|
||||
}
|
||||
|
||||
/// <https://dom.spec.whatwg.org/#assign-a-slot>
|
||||
|
@ -475,11 +465,50 @@ impl VirtualMethods for HTMLSlotElement {
|
|||
self.super_type().unwrap().attribute_mutated(attr, mutation);
|
||||
|
||||
if attr.local_name() == &local_name!("name") && attr.namespace() == &ns!() {
|
||||
if let Some(shadow_root) = self.containing_shadow_root() {
|
||||
// Shadow roots keep a list of slot descendants, so we need to tell it
|
||||
// about our name change
|
||||
let old_value = match mutation {
|
||||
AttributeMutation::Set(old) => old
|
||||
.map(|value| value.to_string().into())
|
||||
.unwrap_or_default(),
|
||||
AttributeMutation::Removed => attr.value().to_string().into(),
|
||||
};
|
||||
|
||||
shadow_root.unregister_slot(old_value, self);
|
||||
shadow_root.register_slot(self);
|
||||
}
|
||||
|
||||
// Changing the name might cause slot assignments to change
|
||||
self.upcast::<Node>()
|
||||
.GetRootNode(&GetRootNodeOptions::empty())
|
||||
.assign_slottables_for_a_tree()
|
||||
}
|
||||
}
|
||||
|
||||
fn bind_to_tree(&self, context: &super::node::BindContext) {
|
||||
if let Some(s) = self.super_type() {
|
||||
s.bind_to_tree(context);
|
||||
}
|
||||
|
||||
if !context.tree_is_in_a_shadow_tree {
|
||||
return;
|
||||
}
|
||||
|
||||
self.containing_shadow_root()
|
||||
.expect("not in a shadow tree")
|
||||
.register_slot(self);
|
||||
}
|
||||
|
||||
fn unbind_from_tree(&self, context: &super::node::UnbindContext) {
|
||||
if let Some(s) = self.super_type() {
|
||||
s.unbind_from_tree(context);
|
||||
}
|
||||
|
||||
if let Some(shadow_root) = self.containing_shadow_root() {
|
||||
shadow_root.unregister_slot(self.Name(), self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl js::gc::Rootable for Slottable {}
|
||||
|
|
|
@ -1362,7 +1362,11 @@ impl Node {
|
|||
// NOTE: This method traverses all descendants of the node and is potentially very
|
||||
// expensive. If the node is not a shadow root then assigning slottables to it won't
|
||||
// have any effect, so we take a fast path out.
|
||||
if !self.is::<ShadowRoot>() {
|
||||
let Some(shadow_root) = self.downcast::<ShadowRoot>() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !shadow_root.has_slot_descendants() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use servo_arc::Arc;
|
||||
use servo_atoms::Atom;
|
||||
|
@ -13,6 +16,7 @@ use style::stylist::{CascadeData, Stylist};
|
|||
|
||||
use crate::conversions::Convert;
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::HTMLSlotElementBinding::HTMLSlotElement_Binding::HTMLSlotElementMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{
|
||||
ShadowRootMode, SlotAssignmentMode,
|
||||
|
@ -27,8 +31,10 @@ use crate::dom::document::Document;
|
|||
use crate::dom::documentfragment::DocumentFragment;
|
||||
use crate::dom::documentorshadowroot::{DocumentOrShadowRoot, StyleSheetInDocument};
|
||||
use crate::dom::element::Element;
|
||||
use crate::dom::htmlslotelement::HTMLSlotElement;
|
||||
use crate::dom::node::{
|
||||
BindContext, Node, NodeDamage, NodeFlags, NodeTraits, ShadowIncluding, UnbindContext,
|
||||
VecPreOrderInsertionHelper,
|
||||
};
|
||||
use crate::dom::stylesheetlist::{StyleSheetList, StyleSheetListOwner};
|
||||
use crate::dom::types::EventTarget;
|
||||
|
@ -65,6 +71,8 @@ pub(crate) struct ShadowRoot {
|
|||
|
||||
/// <https://dom.spec.whatwg.org/#dom-shadowroot-clonable>
|
||||
clonable: bool,
|
||||
|
||||
slots: DomRefCell<HashMap<DOMString, Vec<Dom<HTMLSlotElement>>>>,
|
||||
}
|
||||
|
||||
impl ShadowRoot {
|
||||
|
@ -95,6 +103,7 @@ impl ShadowRoot {
|
|||
mode,
|
||||
slot_assignment_mode,
|
||||
clonable,
|
||||
slots: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -209,6 +218,40 @@ impl ShadowRoot {
|
|||
root,
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn register_slot(&self, slot: &HTMLSlotElement) {
|
||||
debug!("Registering slot with name={:?}", slot.Name().str());
|
||||
|
||||
let mut slots = self.slots.borrow_mut();
|
||||
|
||||
let slots_with_the_same_name = slots.entry(slot.Name()).or_default();
|
||||
|
||||
// Insert the slot before the first element that comes after it in tree order
|
||||
slots_with_the_same_name.insert_pre_order(slot, self.upcast::<Node>());
|
||||
}
|
||||
|
||||
pub(crate) fn unregister_slot(&self, name: DOMString, slot: &HTMLSlotElement) {
|
||||
debug!("Unregistering slot with name={:?}", name.str());
|
||||
|
||||
let mut slots = self.slots.borrow_mut();
|
||||
let Entry::Occupied(mut entry) = slots.entry(name) else {
|
||||
panic!("slot is not registered");
|
||||
};
|
||||
entry.get_mut().retain(|s| slot != &**s);
|
||||
}
|
||||
|
||||
/// Find the first slot with the given name among this root's descendants in tree order
|
||||
pub(crate) fn slot_for_name(&self, name: &DOMString) -> Option<DomRoot<HTMLSlotElement>> {
|
||||
self.slots
|
||||
.borrow()
|
||||
.get(name)
|
||||
.and_then(|slots| slots.first())
|
||||
.map(|slot| slot.as_rooted())
|
||||
}
|
||||
|
||||
pub(crate) fn has_slot_descendants(&self) -> bool {
|
||||
!self.slots.borrow().is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl ShadowRootMethods<crate::DomTypeHolder> for ShadowRoot {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue