Skip to content

Commit

Permalink
Bug 1464428: Optimize QuerySelector in shadow trees. r=xidorn
Browse files Browse the repository at this point in the history
Pretty much the same setup we have for document.

We have the awkwardness of having to check containing shadow manually for
ShadowRoot because it's not available in TNode (and making it available added a
bit more complexity that wasn't worth it IMO).

MozReview-Commit-ID: CqOh0sLHf6o
  • Loading branch information
emilio committed May 28, 2018
1 parent a9dc9a7 commit 02691bc
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 46 deletions.
6 changes: 3 additions & 3 deletions layout/style/ServoBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2892,12 +2892,12 @@ Gecko_ContentList_AppendAll(
}

const nsTArray<Element*>*
Gecko_GetElementsWithId(const nsIDocument* aDocument, nsAtom* aId)
Gecko_GetElementsWithId(const DocumentOrShadowRoot* aDocOrShadowRoot, nsAtom* aId)
{
MOZ_ASSERT(aDocument);
MOZ_ASSERT(aDocOrShadowRoot);
MOZ_ASSERT(aId);

return aDocument->GetAllElementsForId(nsDependentAtomString(aId));
return aDocOrShadowRoot->GetAllElementsForId(nsDependentAtomString(aId));
}

bool
Expand Down
2 changes: 1 addition & 1 deletion layout/style/ServoBindings.h
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,7 @@ void Gecko_ContentList_AppendAll(nsSimpleContentList* aContentList,
size_t aLength);

const nsTArray<mozilla::dom::Element*>* Gecko_GetElementsWithId(
const nsIDocument* aDocument,
const mozilla::dom::DocumentOrShadowRoot* aDocOrShadowRoot,
nsAtom* aId);

// Check the value of the given bool preference. The pref name needs to
Expand Down
1 change: 1 addition & 0 deletions layout/style/ServoBindings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ structs-types = [
"mozilla::css::URLValue",
"mozilla::css::URLValueData",
"mozilla::dom::CallerType",
"mozilla::dom::DocumentOrShadowRoot",
"mozilla::AnonymousCounterStyle",
"mozilla::AtomArray",
"mozilla::FontStretch",
Expand Down
24 changes: 21 additions & 3 deletions servo/components/style/dom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,17 @@ pub trait TDocument: Sized + Copy + Clone {
fn quirks_mode(&self) -> QuirksMode;

/// Get a list of elements with a given ID in this document, sorted by
/// document position.
/// tree position.
///
/// Can return an error to signal that this list is not available, or also
/// return an empty slice.
fn elements_with_id(
fn elements_with_id<'a>(
&self,
_id: &Atom,
) -> Result<&[<Self::ConcreteNode as TNode>::ConcreteElement], ()> {
) -> Result<&'a [<Self::ConcreteNode as TNode>::ConcreteElement], ()>
where
Self: 'a,
{
Err(())
}
}
Expand Down Expand Up @@ -342,6 +345,21 @@ pub trait TShadowRoot: Sized + Copy + Clone + PartialEq {
fn style_data<'a>(&self) -> &'a CascadeData
where
Self: 'a;

/// Get a list of elements with a given ID in this shadow root, sorted by
/// tree position.
///
/// Can return an error to signal that this list is not available, or also
/// return an empty slice.
fn elements_with_id<'a>(
&self,
_id: &Atom,
) -> Result<&'a [<Self::ConcreteNode as TNode>::ConcreteElement], ()>
where
Self: 'a,
{
Err(())
}
}

/// The element trait, the main abstraction the style crate acts over.
Expand Down
62 changes: 41 additions & 21 deletions servo/components/style/dom_apis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,14 +221,31 @@ where
}
}

/// Returns whether a given element is descendant of a given `root` node.
/// Returns whether a given element connected to `root` is descendant of `root`.
///
/// NOTE(emilio): if root == element, this returns false.
fn element_is_descendant_of<E>(element: E, root: E::ConcreteNode) -> bool
fn connected_element_is_descendant_of<E>(element: E, root: E::ConcreteNode) -> bool
where
E: TElement,
{
if element.as_node().is_in_document() && root == root.owner_doc().as_node() {
// Optimize for when the root is a document or a shadow root and the element
// is connected to that root.
if root.as_document().is_some() {
debug_assert!(element.as_node().is_in_document(), "Not connected?");
debug_assert_eq!(
root,
root.owner_doc().as_node(),
"Where did this element come from?",
);
return true;
}

if root.as_shadow_root().is_some() {
debug_assert_eq!(
element.containing_shadow().unwrap().as_node(),
root,
"Not connected?"
);
return true;
}

Expand All @@ -244,28 +261,33 @@ where
}

/// Fast path for iterating over every element with a given id in the document
/// that `root` is connected to.
fn fast_connected_elements_with_id<'a, D>(
doc: &'a D,
root: D::ConcreteNode,
/// or shadow root that `root` is connected to.
fn fast_connected_elements_with_id<'a, N>(
root: N,
id: &Atom,
quirks_mode: QuirksMode,
) -> Result<&'a [<D::ConcreteNode as TNode>::ConcreteElement], ()>
) -> Result<&'a [N::ConcreteElement], ()>
where
D: TDocument,
N: TNode + 'a,
{
debug_assert_eq!(root.owner_doc().as_node(), doc.as_node());

let case_sensitivity = quirks_mode.classes_and_ids_case_sensitivity();
if case_sensitivity != CaseSensitivity::CaseSensitive {
return Err(());
}

if !root.is_in_document() {
return Err(());
if root.is_in_document() {
return root.owner_doc().elements_with_id(id);
}

if let Some(shadow) = root.as_shadow_root() {
return shadow.elements_with_id(id);
}

doc.elements_with_id(id)
if let Some(shadow) = root.as_element().and_then(|e| e.containing_shadow()) {
return shadow.elements_with_id(id);
}

Err(())
}

/// Collects elements with a given id under `root`, that pass `filter`.
Expand All @@ -280,8 +302,7 @@ fn collect_elements_with_id<E, Q, F>(
Q: SelectorQuery<E>,
F: FnMut(E) -> bool,
{
let doc = root.owner_doc();
let elements = match fast_connected_elements_with_id(&doc, root, id, quirks_mode) {
let elements = match fast_connected_elements_with_id(root, id, quirks_mode) {
Ok(elements) => elements,
Err(()) => {
let case_sensitivity = quirks_mode.classes_and_ids_case_sensitivity();
Expand All @@ -297,7 +318,7 @@ fn collect_elements_with_id<E, Q, F>(
for element in elements {
// If the element is not an actual descendant of the root, even though
// it's connected, we don't really care about it.
if !element_is_descendant_of(*element, root) {
if !connected_element_is_descendant_of(*element, root) {
continue;
}

Expand Down Expand Up @@ -405,9 +426,8 @@ where
return Ok(());
}

let doc = root.owner_doc();
let elements = fast_connected_elements_with_id(&doc, root, id, quirks_mode)?;

let elements =
fast_connected_elements_with_id(root, id, quirks_mode)?;
if elements.is_empty() {
return Ok(());
}
Expand All @@ -432,7 +452,7 @@ where
//
// Give up on trying to optimize based on this id and
// keep walking our selector.
if !element_is_descendant_of(*element, root) {
if !connected_element_is_descendant_of(*element, root) {
continue 'component_loop;
}

Expand Down
56 changes: 38 additions & 18 deletions servo/components/style/gecko/wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,30 @@ use std::ptr;
use string_cache::{Atom, Namespace, WeakAtom, WeakNamespace};
use stylist::CascadeData;


fn elements_with_id<'a, 'le>(
root: &'a structs::DocumentOrShadowRoot,
id: &Atom,
) -> &'a [GeckoElement<'le>] {
unsafe {
let array = bindings::Gecko_GetElementsWithId(root, id.as_ptr());
if array.is_null() {
return &[];
}

let elements: &[*mut RawGeckoElement] = &**array;

// NOTE(emilio): We rely on the in-memory representation of
// GeckoElement<'ld> and *mut RawGeckoElement being the same.
#[allow(dead_code)]
unsafe fn static_assert() {
mem::transmute::<*mut RawGeckoElement, GeckoElement<'static>>(0xbadc0de as *mut _);
}

mem::transmute(elements)
}
}

/// A simple wrapper over `nsIDocument`.
#[derive(Clone, Copy)]
pub struct GeckoDocument<'ld>(pub &'ld structs::nsIDocument);
Expand All @@ -109,24 +133,12 @@ impl<'ld> TDocument for GeckoDocument<'ld> {
self.0.mCompatMode.into()
}

fn elements_with_id(&self, id: &Atom) -> Result<&[GeckoElement<'ld>], ()> {
unsafe {
let array = bindings::Gecko_GetElementsWithId(self.0, id.as_ptr());
if array.is_null() {
return Ok(&[]);
}

let elements: &[*mut RawGeckoElement] = &**array;

// NOTE(emilio): We rely on the in-memory representation of
// GeckoElement<'ld> and *mut RawGeckoElement being the same.
#[allow(dead_code)]
unsafe fn static_assert() {
mem::transmute::<*mut RawGeckoElement, GeckoElement<'static>>(0xbadc0de as *mut _);
}

Ok(mem::transmute(elements))
}
#[inline]
fn elements_with_id<'a>(&self, id: &Atom) -> Result<&'a [GeckoElement<'ld>], ()>
where
Self: 'a,
{
Ok(elements_with_id(&self.0._base_1, id))
}
}

Expand Down Expand Up @@ -176,6 +188,14 @@ impl<'lr> TShadowRoot for GeckoShadowRoot<'lr> {

&author_styles.data
}

#[inline]
fn elements_with_id<'a>(&self, id: &Atom) -> Result<&'a [GeckoElement<'lr>], ()>
where
Self: 'a,
{
Ok(elements_with_id(&self.0._base_1, id))
}
}

/// A simple wrapper over a non-null Gecko node (`nsINode`) pointer.
Expand Down

0 comments on commit 02691bc

Please sign in to comment.