Skip to content

Commit e48eac6

Browse files
committed
Doc named getter improvements
1 parent 4b750ca commit e48eac6

File tree

10 files changed

+405
-111
lines changed

10 files changed

+405
-111
lines changed

components/script/dom/document.rs

Lines changed: 208 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ use servo_media::{ClientContextId, ServoMedia};
146146
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
147147
use std::borrow::Cow;
148148
use std::cell::Cell;
149+
use std::cmp::Ordering;
149150
use std::collections::hash_map::Entry::{Occupied, Vacant};
150151
use std::collections::{HashMap, HashSet, VecDeque};
151152
use std::default::Default;
@@ -243,6 +244,7 @@ pub struct Document {
243244
quirks_mode: Cell<QuirksMode>,
244245
/// Caches for the getElement methods
245246
id_map: DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>,
247+
name_map: DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>,
246248
tag_map: DomRefCell<HashMap<LocalName, Dom<HTMLCollection>>>,
247249
tagns_map: DomRefCell<HashMap<QualName, Dom<HTMLCollection>>>,
248250
classes_map: DomRefCell<HashMap<Vec<Atom>, Dom<HTMLCollection>>>,
@@ -450,6 +452,12 @@ impl CollectionFilter for AnchorsFilter {
450452
}
451453
}
452454

455+
enum ElementLookupResult {
456+
None,
457+
One(DomRoot<Element>),
458+
Many,
459+
}
460+
453461
#[allow(non_snake_case)]
454462
impl Document {
455463
#[inline]
@@ -709,14 +717,14 @@ impl Document {
709717
}
710718

711719
/// Remove any existing association between the provided id and any elements in this document.
712-
pub fn unregister_named_element(&self, to_unregister: &Element, id: Atom) {
720+
pub fn unregister_element_id(&self, to_unregister: &Element, id: Atom) {
713721
self.document_or_shadow_root
714722
.unregister_named_element(&self.id_map, to_unregister, &id);
715723
self.reset_form_owner_for_listeners(&id);
716724
}
717725

718726
/// Associate an element present in this document with the provided id.
719-
pub fn register_named_element(&self, element: &Element, id: Atom) {
727+
pub fn register_element_id(&self, element: &Element, id: Atom) {
720728
let root = self.GetDocumentElement().expect(
721729
"The element is in the document, so there must be a document \
722730
element.",
@@ -730,6 +738,26 @@ impl Document {
730738
self.reset_form_owner_for_listeners(&id);
731739
}
732740

741+
/// Remove any existing association between the provided name and any elements in this document.
742+
pub fn unregister_element_name(&self, to_unregister: &Element, name: Atom) {
743+
self.document_or_shadow_root
744+
.unregister_named_element(&self.name_map, to_unregister, &name);
745+
}
746+
747+
/// Associate an element present in this document with the provided name.
748+
pub fn register_element_name(&self, element: &Element, name: Atom) {
749+
let root = self.GetDocumentElement().expect(
750+
"The element is in the document, so there must be a document \
751+
element.",
752+
);
753+
self.document_or_shadow_root.register_named_element(
754+
&self.name_map,
755+
element,
756+
&name,
757+
DomRoot::from_ref(root.upcast::<Node>()),
758+
);
759+
}
760+
733761
pub fn register_form_id_listener<T: ?Sized + FormControl>(&self, id: DOMString, listener: &T) {
734762
let mut map = self.form_id_listener_map.borrow_mut();
735763
let listener = listener.to_element();
@@ -823,18 +851,13 @@ impl Document {
823851
}
824852

825853
fn get_anchor_by_name(&self, name: &str) -> Option<DomRoot<Element>> {
826-
// TODO faster name lookups (see #25548)
827-
let check_anchor = |node: &HTMLAnchorElement| {
828-
let elem = node.upcast::<Element>();
829-
elem.get_attribute(&ns!(), &local_name!("name"))
830-
.map_or(false, |attr| &**attr.value() == name)
831-
};
832-
let doc_node = self.upcast::<Node>();
833-
doc_node
834-
.traverse_preorder(ShadowIncluding::No)
835-
.filter_map(DomRoot::downcast)
836-
.find(|node| check_anchor(&node))
837-
.map(DomRoot::upcast)
854+
let name = Atom::from(name);
855+
self.name_map.borrow().get(&name).and_then(|elements| {
856+
elements
857+
.iter()
858+
.find(|e| e.is::<HTMLAnchorElement>())
859+
.map(|e| DomRoot::from_ref(&**e))
860+
})
838861
}
839862

840863
// https://html.spec.whatwg.org/multipage/#current-document-readiness
@@ -2524,6 +2547,75 @@ impl Document {
25242547
.unwrap();
25252548
receiver.recv().unwrap();
25262549
}
2550+
2551+
// https://html.spec.whatwg.org/multipage/#dom-tree-accessors:supported-property-names
2552+
// (This takes the filter as a method so the window named getter can use it too)
2553+
pub fn supported_property_names_impl(
2554+
&self,
2555+
nameditem_filter: fn(&Node, &Atom) -> bool,
2556+
) -> Vec<DOMString> {
2557+
// The tricky part here is making sure we return the names in
2558+
// tree order, without just resorting to a full tree walkthrough.
2559+
2560+
let mut first_elements_with_name: HashMap<&Atom, &Dom<Element>> = HashMap::new();
2561+
2562+
// Get the first-in-tree-order element for each name in the name_map
2563+
let name_map = self.name_map.borrow();
2564+
name_map.iter().for_each(|(name, value)| {
2565+
if let Some(first) = value
2566+
.iter()
2567+
.find(|n| nameditem_filter((***n).upcast::<Node>(), &name))
2568+
{
2569+
first_elements_with_name.insert(name, first);
2570+
}
2571+
});
2572+
2573+
// Get the first-in-tree-order element for each name in the id_map;
2574+
// if we already had one from the name_map, figure out which of
2575+
// the two is first.
2576+
let id_map = self.id_map.borrow();
2577+
id_map.iter().for_each(|(name, value)| {
2578+
if let Some(first) = value
2579+
.iter()
2580+
.find(|n| nameditem_filter((***n).upcast::<Node>(), &name))
2581+
{
2582+
match first_elements_with_name.get(&name) {
2583+
None => {
2584+
first_elements_with_name.insert(name, first);
2585+
},
2586+
Some(el) => {
2587+
if *el != first && first.upcast::<Node>().is_before(el.upcast::<Node>()) {
2588+
first_elements_with_name.insert(name, first);
2589+
}
2590+
},
2591+
}
2592+
}
2593+
});
2594+
2595+
// first_elements_with_name now has our supported property names
2596+
// as keys, and the elements to order on as values.
2597+
let mut sortable_vec: Vec<(&Atom, &Dom<Element>)> = first_elements_with_name
2598+
.iter()
2599+
.map(|(k, v)| (*k, *v))
2600+
.collect();
2601+
sortable_vec.sort_unstable_by(|a, b| {
2602+
if a.1 == b.1 {
2603+
// This can happen if an img has an id different from its name,
2604+
// spec does not say which string to put first.
2605+
a.0.cmp(&b.0)
2606+
} else if a.1.upcast::<Node>().is_before(b.1.upcast::<Node>()) {
2607+
Ordering::Less
2608+
} else {
2609+
Ordering::Greater
2610+
}
2611+
});
2612+
2613+
// And now that they're sorted, we can return the keys
2614+
sortable_vec
2615+
.iter()
2616+
.map(|(k, _v)| DOMString::from(&***k))
2617+
.collect()
2618+
}
25272619
}
25282620

25292621
fn is_character_value_key(key: &Key) -> bool {
@@ -2735,6 +2827,7 @@ impl Document {
27352827
// https://dom.spec.whatwg.org/#concept-document-quirks
27362828
quirks_mode: Cell::new(QuirksMode::NoQuirks),
27372829
id_map: DomRefCell::new(HashMap::new()),
2830+
name_map: DomRefCell::new(HashMap::new()),
27382831
// https://dom.spec.whatwg.org/#concept-document-encoding
27392832
encoding: Cell::new(encoding),
27402833
is_html_document: is_html_document == IsHTMLDocument::HTMLDocument,
@@ -3397,6 +3490,81 @@ impl Document {
33973490
StylesheetSetRef::Document(&mut *self.stylesheets.borrow_mut()),
33983491
)
33993492
}
3493+
3494+
// https://html.spec.whatwg.org/multipage/#dom-tree-accessors:determine-the-value-of-a-named-property
3495+
// Support method for steps 1-3:
3496+
// Count if there are 0, 1, or >1 elements that match the name.
3497+
// (This takes the filter as a method so the window named getter can use it too)
3498+
fn look_up_named_elements(
3499+
&self,
3500+
name: &Atom,
3501+
nameditem_filter: fn(&Node, &Atom) -> bool,
3502+
) -> ElementLookupResult {
3503+
// We might match because of either id==name or name==name, so there
3504+
// are two sets of nodes to look through, but we don't need a
3505+
// full tree traversal.
3506+
let id_map = self.id_map.borrow();
3507+
let name_map = self.name_map.borrow();
3508+
let id_vec = id_map.get(&name);
3509+
let name_vec = name_map.get(&name);
3510+
3511+
// If nothing can possibly have the name, exit fast
3512+
if id_vec.is_none() && name_vec.is_none() {
3513+
return ElementLookupResult::None;
3514+
}
3515+
3516+
let one_from_id_map = if let Some(id_vec) = id_vec {
3517+
let mut elements = id_vec
3518+
.iter()
3519+
.filter(|n| nameditem_filter((***n).upcast::<Node>(), &name))
3520+
.peekable();
3521+
if let Some(first) = elements.next() {
3522+
if elements.peek().is_none() {
3523+
Some(first)
3524+
} else {
3525+
return ElementLookupResult::Many;
3526+
}
3527+
} else {
3528+
None
3529+
}
3530+
} else {
3531+
None
3532+
};
3533+
3534+
let one_from_name_map = if let Some(name_vec) = name_vec {
3535+
let mut elements = name_vec
3536+
.iter()
3537+
.filter(|n| nameditem_filter((***n).upcast::<Node>(), &name))
3538+
.peekable();
3539+
if let Some(first) = elements.next() {
3540+
if elements.peek().is_none() {
3541+
Some(first)
3542+
} else {
3543+
return ElementLookupResult::Many;
3544+
}
3545+
} else {
3546+
None
3547+
}
3548+
} else {
3549+
None
3550+
};
3551+
3552+
// We now have two elements, or one element, or the same
3553+
// element twice, or no elements.
3554+
match (one_from_id_map, one_from_name_map) {
3555+
(Some(one), None) | (None, Some(one)) => {
3556+
ElementLookupResult::One(DomRoot::from_ref(&one))
3557+
},
3558+
(Some(one), Some(other)) => {
3559+
if one == other {
3560+
ElementLookupResult::One(DomRoot::from_ref(&one))
3561+
} else {
3562+
ElementLookupResult::Many
3563+
}
3564+
},
3565+
(None, None) => ElementLookupResult::None,
3566+
}
3567+
}
34003568
}
34013569

34023570
impl Element {
@@ -4297,69 +4465,39 @@ impl DocumentMethods for Document {
42974465
}
42984466
impl CollectionFilter for NamedElementFilter {
42994467
fn filter(&self, elem: &Element, _root: &Node) -> bool {
4300-
filter_by_name(&self.name, elem.upcast())
4301-
}
4302-
}
4303-
// https://html.spec.whatwg.org/multipage/#dom-document-nameditem-filter
4304-
fn filter_by_name(name: &Atom, node: &Node) -> bool {
4305-
// TODO faster name lookups (see #25548)
4306-
let html_elem_type = match node.type_id() {
4307-
NodeTypeId::Element(ElementTypeId::HTMLElement(type_)) => type_,
4308-
_ => return false,
4309-
};
4310-
let elem = match node.downcast::<Element>() {
4311-
Some(elem) => elem,
4312-
None => return false,
4313-
};
4314-
match html_elem_type {
4315-
HTMLElementTypeId::HTMLFormElement => {
4316-
match elem.get_attribute(&ns!(), &local_name!("name")) {
4317-
Some(ref attr) => attr.value().as_atom() == name,
4318-
None => false,
4319-
}
4320-
},
4321-
HTMLElementTypeId::HTMLImageElement => {
4322-
match elem.get_attribute(&ns!(), &local_name!("name")) {
4323-
Some(ref attr) => {
4324-
if attr.value().as_atom() == name {
4325-
true
4326-
} else {
4327-
match elem.get_attribute(&ns!(), &local_name!("id")) {
4328-
Some(ref attr) => attr.value().as_atom() == name,
4329-
None => false,
4330-
}
4331-
}
4332-
},
4333-
None => false,
4334-
}
4335-
},
4336-
// TODO: Handle <embed>, <iframe> and <object>.
4337-
_ => false,
4468+
elem.upcast::<Node>().is_document_named_item(&self.name)
43384469
}
43394470
}
4471+
43404472
let name = Atom::from(name);
4341-
let root = self.upcast::<Node>();
4342-
unsafe {
4343-
// Step 1.
4344-
let mut elements = root
4345-
.traverse_preorder(ShadowIncluding::No)
4346-
.filter(|node| filter_by_name(&name, &node))
4347-
.peekable();
4348-
if let Some(first) = elements.next() {
4349-
if elements.peek().is_none() {
4350-
// TODO: Step 2.
4351-
// Step 3.
4473+
4474+
match self.look_up_named_elements(&name, Node::is_document_named_item) {
4475+
ElementLookupResult::None => {
4476+
return None;
4477+
},
4478+
ElementLookupResult::One(element) => {
4479+
if let Some(nested_proxy) = element
4480+
.downcast::<HTMLIFrameElement>()
4481+
.and_then(|iframe| iframe.GetContentWindow())
4482+
{
4483+
unsafe {
4484+
return Some(NonNull::new_unchecked(
4485+
nested_proxy.reflector().get_jsobject().get(),
4486+
));
4487+
}
4488+
}
4489+
unsafe {
43524490
return Some(NonNull::new_unchecked(
4353-
first.reflector().get_jsobject().get(),
4491+
element.reflector().get_jsobject().get(),
43544492
));
43554493
}
4356-
} else {
4357-
return None;
4358-
}
4359-
}
4494+
},
4495+
ElementLookupResult::Many => {},
4496+
};
4497+
43604498
// Step 4.
43614499
let filter = NamedElementFilter { name: name };
4362-
let collection = HTMLCollection::create(self.window(), root, Box::new(filter));
4500+
let collection = HTMLCollection::create(self.window(), self.upcast(), Box::new(filter));
43634501
unsafe {
43644502
Some(NonNull::new_unchecked(
43654503
collection.reflector().get_jsobject().get(),
@@ -4369,8 +4507,7 @@ impl DocumentMethods for Document {
43694507

43704508
// https://html.spec.whatwg.org/multipage/#dom-tree-accessors:supported-property-names
43714509
fn SupportedPropertyNames(&self) -> Vec<DOMString> {
4372-
// FIXME: unimplemented (https://github.com/servo/servo/issues/7273)
4373-
vec![]
4510+
self.supported_property_names_impl(Node::is_document_named_item)
43744511
}
43754512

43764513
// https://html.spec.whatwg.org/multipage/#dom-document-clear

components/script/dom/documentorshadowroot.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ impl DocumentOrShadowRoot {
266266
}
267267
}
268268

269-
/// Remove any existing association between the provided id and any elements in this document.
269+
/// Remove any existing association between the provided id/name and any elements in this document.
270270
pub fn unregister_named_element(
271271
&self,
272272
id_map: &DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>,
@@ -294,7 +294,7 @@ impl DocumentOrShadowRoot {
294294
}
295295
}
296296

297-
/// Associate an element present in this document with the provided id.
297+
/// Associate an element present in this document with the provided id/name.
298298
pub fn register_named_element(
299299
&self,
300300
id_map: &DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>,

0 commit comments

Comments
 (0)