diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 902463b7d10..36b48c18bf9 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -145,6 +145,7 @@ use servo_media::{ClientContextId, ServoMedia}; use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl}; use std::borrow::Cow; use std::cell::Cell; +use std::cmp::Ordering; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::{HashMap, HashSet, VecDeque}; use std::default::Default; @@ -242,6 +243,7 @@ pub struct Document { quirks_mode: Cell, /// Caches for the getElement methods id_map: DomRefCell>>>, + name_map: DomRefCell>>>, tag_map: DomRefCell>>, tagns_map: DomRefCell>>, classes_map: DomRefCell, Dom>>, @@ -449,6 +451,12 @@ impl CollectionFilter for AnchorsFilter { } } +enum ElementLookupResult { + None, + One(DomRoot), + Many, +} + #[allow(non_snake_case)] impl Document { #[inline] @@ -707,14 +715,14 @@ impl Document { } /// Remove any existing association between the provided id and any elements in this document. - pub fn unregister_named_element(&self, to_unregister: &Element, id: Atom) { + pub fn unregister_element_id(&self, to_unregister: &Element, id: Atom) { self.document_or_shadow_root .unregister_named_element(&self.id_map, to_unregister, &id); self.reset_form_owner_for_listeners(&id); } /// Associate an element present in this document with the provided id. - pub fn register_named_element(&self, element: &Element, id: Atom) { + pub fn register_element_id(&self, element: &Element, id: Atom) { let root = self.GetDocumentElement().expect( "The element is in the document, so there must be a document \ element.", @@ -728,6 +736,26 @@ impl Document { self.reset_form_owner_for_listeners(&id); } + /// Remove any existing association between the provided name and any elements in this document. + pub fn unregister_element_name(&self, to_unregister: &Element, name: Atom) { + self.document_or_shadow_root + .unregister_named_element(&self.name_map, to_unregister, &name); + } + + /// Associate an element present in this document with the provided name. + pub fn register_element_name(&self, element: &Element, name: Atom) { + let root = self.GetDocumentElement().expect( + "The element is in the document, so there must be a document \ + element.", + ); + self.document_or_shadow_root.register_named_element( + &self.name_map, + element, + &name, + DomRoot::from_ref(root.upcast::()), + ); + } + pub fn register_form_id_listener(&self, id: DOMString, listener: &T) { let mut map = self.form_id_listener_map.borrow_mut(); let listener = listener.to_element(); @@ -821,18 +849,13 @@ impl Document { } fn get_anchor_by_name(&self, name: &str) -> Option> { - // TODO faster name lookups (see #25548) - let check_anchor = |node: &HTMLAnchorElement| { - let elem = node.upcast::(); - elem.get_attribute(&ns!(), &local_name!("name")) - .map_or(false, |attr| &**attr.value() == name) - }; - let doc_node = self.upcast::(); - doc_node - .traverse_preorder(ShadowIncluding::No) - .filter_map(DomRoot::downcast) - .find(|node| check_anchor(&node)) - .map(DomRoot::upcast) + let name = Atom::from(name); + self.name_map.borrow().get(&name).and_then(|elements| { + elements + .iter() + .find(|e| e.is::()) + .map(|e| DomRoot::from_ref(&**e)) + }) } // https://html.spec.whatwg.org/multipage/#current-document-readiness @@ -2512,6 +2535,75 @@ impl Document { .unwrap(); receiver.recv().unwrap(); } + + // https://html.spec.whatwg.org/multipage/#dom-tree-accessors:supported-property-names + // (This takes the filter as a method so the window named getter can use it too) + pub fn supported_property_names_impl( + &self, + nameditem_filter: fn(&Node, &Atom) -> bool, + ) -> Vec { + // The tricky part here is making sure we return the names in + // tree order, without just resorting to a full tree walkthrough. + + let mut first_elements_with_name: HashMap<&Atom, &Dom> = HashMap::new(); + + // Get the first-in-tree-order element for each name in the name_map + let name_map = self.name_map.borrow(); + name_map.iter().for_each(|(name, value)| { + if let Some(first) = value + .iter() + .find(|n| nameditem_filter((***n).upcast::(), &name)) + { + first_elements_with_name.insert(name, first); + } + }); + + // Get the first-in-tree-order element for each name in the id_map; + // if we already had one from the name_map, figure out which of + // the two is first. + let id_map = self.id_map.borrow(); + id_map.iter().for_each(|(name, value)| { + if let Some(first) = value + .iter() + .find(|n| nameditem_filter((***n).upcast::(), &name)) + { + match first_elements_with_name.get(&name) { + None => { + first_elements_with_name.insert(name, first); + }, + Some(el) => { + if *el != first && first.upcast::().is_before(el.upcast::()) { + first_elements_with_name.insert(name, first); + } + }, + } + } + }); + + // first_elements_with_name now has our supported property names + // as keys, and the elements to order on as values. + let mut sortable_vec: Vec<(&Atom, &Dom)> = first_elements_with_name + .iter() + .map(|(k, v)| (*k, *v)) + .collect(); + sortable_vec.sort_unstable_by(|a, b| { + if a.1 == b.1 { + // This can happen if an img has an id different from its name, + // spec does not say which string to put first. + a.0.cmp(&b.0) + } else if a.1.upcast::().is_before(b.1.upcast::()) { + Ordering::Less + } else { + Ordering::Greater + } + }); + + // And now that they're sorted, we can return the keys + sortable_vec + .iter() + .map(|(k, _v)| DOMString::from(&***k)) + .collect() + } } fn is_character_value_key(key: &Key) -> bool { @@ -2723,6 +2815,7 @@ impl Document { // https://dom.spec.whatwg.org/#concept-document-quirks quirks_mode: Cell::new(QuirksMode::NoQuirks), id_map: DomRefCell::new(HashMap::new()), + name_map: DomRefCell::new(HashMap::new()), // https://dom.spec.whatwg.org/#concept-document-encoding encoding: Cell::new(encoding), is_html_document: is_html_document == IsHTMLDocument::HTMLDocument, @@ -3385,6 +3478,81 @@ impl Document { StylesheetSetRef::Document(&mut *self.stylesheets.borrow_mut()), ) } + + // https://html.spec.whatwg.org/multipage/#dom-tree-accessors:determine-the-value-of-a-named-property + // Support method for steps 1-3: + // Count if there are 0, 1, or >1 elements that match the name. + // (This takes the filter as a method so the window named getter can use it too) + fn look_up_named_elements( + &self, + name: &Atom, + nameditem_filter: fn(&Node, &Atom) -> bool, + ) -> ElementLookupResult { + // We might match because of either id==name or name==name, so there + // are two sets of nodes to look through, but we don't need a + // full tree traversal. + let id_map = self.id_map.borrow(); + let name_map = self.name_map.borrow(); + let id_vec = id_map.get(&name); + let name_vec = name_map.get(&name); + + // If nothing can possibly have the name, exit fast + if id_vec.is_none() && name_vec.is_none() { + return ElementLookupResult::None; + } + + let one_from_id_map = if let Some(id_vec) = id_vec { + let mut elements = id_vec + .iter() + .filter(|n| nameditem_filter((***n).upcast::(), &name)) + .peekable(); + if let Some(first) = elements.next() { + if elements.peek().is_none() { + Some(first) + } else { + return ElementLookupResult::Many; + } + } else { + None + } + } else { + None + }; + + let one_from_name_map = if let Some(name_vec) = name_vec { + let mut elements = name_vec + .iter() + .filter(|n| nameditem_filter((***n).upcast::(), &name)) + .peekable(); + if let Some(first) = elements.next() { + if elements.peek().is_none() { + Some(first) + } else { + return ElementLookupResult::Many; + } + } else { + None + } + } else { + None + }; + + // We now have two elements, or one element, or the same + // element twice, or no elements. + match (one_from_id_map, one_from_name_map) { + (Some(one), None) | (None, Some(one)) => { + ElementLookupResult::One(DomRoot::from_ref(&one)) + }, + (Some(one), Some(other)) => { + if one == other { + ElementLookupResult::One(DomRoot::from_ref(&one)) + } else { + ElementLookupResult::Many + } + }, + (None, None) => ElementLookupResult::None, + } + } } impl Element { @@ -4285,69 +4453,39 @@ impl DocumentMethods for Document { } impl CollectionFilter for NamedElementFilter { fn filter(&self, elem: &Element, _root: &Node) -> bool { - filter_by_name(&self.name, elem.upcast()) - } - } - // https://html.spec.whatwg.org/multipage/#dom-document-nameditem-filter - fn filter_by_name(name: &Atom, node: &Node) -> bool { - // TODO faster name lookups (see #25548) - let html_elem_type = match node.type_id() { - NodeTypeId::Element(ElementTypeId::HTMLElement(type_)) => type_, - _ => return false, - }; - let elem = match node.downcast::() { - Some(elem) => elem, - None => return false, - }; - match html_elem_type { - HTMLElementTypeId::HTMLFormElement => { - match elem.get_attribute(&ns!(), &local_name!("name")) { - Some(ref attr) => attr.value().as_atom() == name, - None => false, - } - }, - HTMLElementTypeId::HTMLImageElement => { - match elem.get_attribute(&ns!(), &local_name!("name")) { - Some(ref attr) => { - if attr.value().as_atom() == name { - true - } else { - match elem.get_attribute(&ns!(), &local_name!("id")) { - Some(ref attr) => attr.value().as_atom() == name, - None => false, - } - } - }, - None => false, - } - }, - // TODO: Handle , + + + + + + + + + + + + + +