mirror of
https://github.com/servo/servo.git
synced 2025-07-24 15:50:21 +01:00
Doc named getter improvements
This commit is contained in:
parent
4b750ca0d0
commit
e48eac6879
10 changed files with 405 additions and 111 deletions
|
@ -146,6 +146,7 @@ use servo_media::{ClientContextId, ServoMedia};
|
||||||
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
|
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
use std::cmp::Ordering;
|
||||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||||
use std::collections::{HashMap, HashSet, VecDeque};
|
use std::collections::{HashMap, HashSet, VecDeque};
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
|
@ -243,6 +244,7 @@ pub struct Document {
|
||||||
quirks_mode: Cell<QuirksMode>,
|
quirks_mode: Cell<QuirksMode>,
|
||||||
/// Caches for the getElement methods
|
/// Caches for the getElement methods
|
||||||
id_map: DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>,
|
id_map: DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>,
|
||||||
|
name_map: DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>,
|
||||||
tag_map: DomRefCell<HashMap<LocalName, Dom<HTMLCollection>>>,
|
tag_map: DomRefCell<HashMap<LocalName, Dom<HTMLCollection>>>,
|
||||||
tagns_map: DomRefCell<HashMap<QualName, Dom<HTMLCollection>>>,
|
tagns_map: DomRefCell<HashMap<QualName, Dom<HTMLCollection>>>,
|
||||||
classes_map: DomRefCell<HashMap<Vec<Atom>, Dom<HTMLCollection>>>,
|
classes_map: DomRefCell<HashMap<Vec<Atom>, Dom<HTMLCollection>>>,
|
||||||
|
@ -450,6 +452,12 @@ impl CollectionFilter for AnchorsFilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ElementLookupResult {
|
||||||
|
None,
|
||||||
|
One(DomRoot<Element>),
|
||||||
|
Many,
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
impl Document {
|
impl Document {
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -709,14 +717,14 @@ impl Document {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove any existing association between the provided id and any elements in this 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
|
self.document_or_shadow_root
|
||||||
.unregister_named_element(&self.id_map, to_unregister, &id);
|
.unregister_named_element(&self.id_map, to_unregister, &id);
|
||||||
self.reset_form_owner_for_listeners(&id);
|
self.reset_form_owner_for_listeners(&id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Associate an element present in this document with the provided 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(
|
let root = self.GetDocumentElement().expect(
|
||||||
"The element is in the document, so there must be a document \
|
"The element is in the document, so there must be a document \
|
||||||
element.",
|
element.",
|
||||||
|
@ -730,6 +738,26 @@ impl Document {
|
||||||
self.reset_form_owner_for_listeners(&id);
|
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::<Node>()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn register_form_id_listener<T: ?Sized + FormControl>(&self, id: DOMString, listener: &T) {
|
pub fn register_form_id_listener<T: ?Sized + FormControl>(&self, id: DOMString, listener: &T) {
|
||||||
let mut map = self.form_id_listener_map.borrow_mut();
|
let mut map = self.form_id_listener_map.borrow_mut();
|
||||||
let listener = listener.to_element();
|
let listener = listener.to_element();
|
||||||
|
@ -823,18 +851,13 @@ impl Document {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_anchor_by_name(&self, name: &str) -> Option<DomRoot<Element>> {
|
fn get_anchor_by_name(&self, name: &str) -> Option<DomRoot<Element>> {
|
||||||
// TODO faster name lookups (see #25548)
|
let name = Atom::from(name);
|
||||||
let check_anchor = |node: &HTMLAnchorElement| {
|
self.name_map.borrow().get(&name).and_then(|elements| {
|
||||||
let elem = node.upcast::<Element>();
|
elements
|
||||||
elem.get_attribute(&ns!(), &local_name!("name"))
|
.iter()
|
||||||
.map_or(false, |attr| &**attr.value() == name)
|
.find(|e| e.is::<HTMLAnchorElement>())
|
||||||
};
|
.map(|e| DomRoot::from_ref(&**e))
|
||||||
let doc_node = self.upcast::<Node>();
|
})
|
||||||
doc_node
|
|
||||||
.traverse_preorder(ShadowIncluding::No)
|
|
||||||
.filter_map(DomRoot::downcast)
|
|
||||||
.find(|node| check_anchor(&node))
|
|
||||||
.map(DomRoot::upcast)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#current-document-readiness
|
// https://html.spec.whatwg.org/multipage/#current-document-readiness
|
||||||
|
@ -2524,6 +2547,75 @@ impl Document {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
receiver.recv().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<DOMString> {
|
||||||
|
// 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<Element>> = 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::<Node>(), &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::<Node>(), &name))
|
||||||
|
{
|
||||||
|
match first_elements_with_name.get(&name) {
|
||||||
|
None => {
|
||||||
|
first_elements_with_name.insert(name, first);
|
||||||
|
},
|
||||||
|
Some(el) => {
|
||||||
|
if *el != first && first.upcast::<Node>().is_before(el.upcast::<Node>()) {
|
||||||
|
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<Element>)> = 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::<Node>().is_before(b.1.upcast::<Node>()) {
|
||||||
|
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 {
|
fn is_character_value_key(key: &Key) -> bool {
|
||||||
|
@ -2735,6 +2827,7 @@ impl Document {
|
||||||
// https://dom.spec.whatwg.org/#concept-document-quirks
|
// https://dom.spec.whatwg.org/#concept-document-quirks
|
||||||
quirks_mode: Cell::new(QuirksMode::NoQuirks),
|
quirks_mode: Cell::new(QuirksMode::NoQuirks),
|
||||||
id_map: DomRefCell::new(HashMap::new()),
|
id_map: DomRefCell::new(HashMap::new()),
|
||||||
|
name_map: DomRefCell::new(HashMap::new()),
|
||||||
// https://dom.spec.whatwg.org/#concept-document-encoding
|
// https://dom.spec.whatwg.org/#concept-document-encoding
|
||||||
encoding: Cell::new(encoding),
|
encoding: Cell::new(encoding),
|
||||||
is_html_document: is_html_document == IsHTMLDocument::HTMLDocument,
|
is_html_document: is_html_document == IsHTMLDocument::HTMLDocument,
|
||||||
|
@ -3397,6 +3490,81 @@ impl Document {
|
||||||
StylesheetSetRef::Document(&mut *self.stylesheets.borrow_mut()),
|
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::<Node>(), &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::<Node>(), &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 {
|
impl Element {
|
||||||
|
@ -4297,69 +4465,39 @@ impl DocumentMethods for Document {
|
||||||
}
|
}
|
||||||
impl CollectionFilter for NamedElementFilter {
|
impl CollectionFilter for NamedElementFilter {
|
||||||
fn filter(&self, elem: &Element, _root: &Node) -> bool {
|
fn filter(&self, elem: &Element, _root: &Node) -> bool {
|
||||||
filter_by_name(&self.name, elem.upcast())
|
elem.upcast::<Node>().is_document_named_item(&self.name)
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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::<Element>() {
|
|
||||||
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 <embed>, <iframe> and <object>.
|
|
||||||
_ => false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = Atom::from(name);
|
let name = Atom::from(name);
|
||||||
let root = self.upcast::<Node>();
|
|
||||||
unsafe {
|
match self.look_up_named_elements(&name, Node::is_document_named_item) {
|
||||||
// Step 1.
|
ElementLookupResult::None => {
|
||||||
let mut elements = root
|
return None;
|
||||||
.traverse_preorder(ShadowIncluding::No)
|
},
|
||||||
.filter(|node| filter_by_name(&name, &node))
|
ElementLookupResult::One(element) => {
|
||||||
.peekable();
|
if let Some(nested_proxy) = element
|
||||||
if let Some(first) = elements.next() {
|
.downcast::<HTMLIFrameElement>()
|
||||||
if elements.peek().is_none() {
|
.and_then(|iframe| iframe.GetContentWindow())
|
||||||
// TODO: Step 2.
|
{
|
||||||
// Step 3.
|
unsafe {
|
||||||
|
return Some(NonNull::new_unchecked(
|
||||||
|
nested_proxy.reflector().get_jsobject().get(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
return Some(NonNull::new_unchecked(
|
return Some(NonNull::new_unchecked(
|
||||||
first.reflector().get_jsobject().get(),
|
element.reflector().get_jsobject().get(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
},
|
||||||
return None;
|
ElementLookupResult::Many => {},
|
||||||
}
|
};
|
||||||
}
|
|
||||||
// Step 4.
|
// Step 4.
|
||||||
let filter = NamedElementFilter { name: name };
|
let filter = NamedElementFilter { name: name };
|
||||||
let collection = HTMLCollection::create(self.window(), root, Box::new(filter));
|
let collection = HTMLCollection::create(self.window(), self.upcast(), Box::new(filter));
|
||||||
unsafe {
|
unsafe {
|
||||||
Some(NonNull::new_unchecked(
|
Some(NonNull::new_unchecked(
|
||||||
collection.reflector().get_jsobject().get(),
|
collection.reflector().get_jsobject().get(),
|
||||||
|
@ -4369,8 +4507,7 @@ impl DocumentMethods for Document {
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-tree-accessors:supported-property-names
|
// https://html.spec.whatwg.org/multipage/#dom-tree-accessors:supported-property-names
|
||||||
fn SupportedPropertyNames(&self) -> Vec<DOMString> {
|
fn SupportedPropertyNames(&self) -> Vec<DOMString> {
|
||||||
// FIXME: unimplemented (https://github.com/servo/servo/issues/7273)
|
self.supported_property_names_impl(Node::is_document_named_item)
|
||||||
vec![]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-document-clear
|
// https://html.spec.whatwg.org/multipage/#dom-document-clear
|
||||||
|
|
|
@ -266,7 +266,7 @@ impl DocumentOrShadowRoot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove any existing association between the provided id and any elements in this document.
|
/// Remove any existing association between the provided id/name and any elements in this document.
|
||||||
pub fn unregister_named_element(
|
pub fn unregister_named_element(
|
||||||
&self,
|
&self,
|
||||||
id_map: &DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>,
|
id_map: &DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>,
|
||||||
|
@ -294,7 +294,7 @@ impl DocumentOrShadowRoot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Associate an element present in this document with the provided id.
|
/// Associate an element present in this document with the provided id/name.
|
||||||
pub fn register_named_element(
|
pub fn register_named_element(
|
||||||
&self,
|
&self,
|
||||||
id_map: &DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>,
|
id_map: &DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>,
|
||||||
|
|
|
@ -1177,6 +1177,10 @@ impl Element {
|
||||||
ns!()
|
ns!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn name_attribute(&self) -> Option<Atom> {
|
||||||
|
self.rare_data().as_ref()?.name_attribute.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn style_attribute(&self) -> &DomRefCell<Option<Arc<Locked<PropertyDeclarationBlock>>>> {
|
pub fn style_attribute(&self) -> &DomRefCell<Option<Arc<Locked<PropertyDeclarationBlock>>>> {
|
||||||
&self.style_attribute
|
&self.style_attribute
|
||||||
}
|
}
|
||||||
|
@ -2803,25 +2807,25 @@ impl VirtualMethods for Element {
|
||||||
if let Some(old_value) = old_value {
|
if let Some(old_value) = old_value {
|
||||||
let old_value = old_value.as_atom().clone();
|
let old_value = old_value.as_atom().clone();
|
||||||
if let Some(ref shadow_root) = containing_shadow_root {
|
if let Some(ref shadow_root) = containing_shadow_root {
|
||||||
shadow_root.unregister_named_element(self, old_value);
|
shadow_root.unregister_element_id(self, old_value);
|
||||||
} else {
|
} else {
|
||||||
doc.unregister_named_element(self, old_value);
|
doc.unregister_element_id(self, old_value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if value != atom!("") {
|
if value != atom!("") {
|
||||||
if let Some(ref shadow_root) = containing_shadow_root {
|
if let Some(ref shadow_root) = containing_shadow_root {
|
||||||
shadow_root.register_named_element(self, value);
|
shadow_root.register_element_id(self, value);
|
||||||
} else {
|
} else {
|
||||||
doc.register_named_element(self, value);
|
doc.register_element_id(self, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
AttributeMutation::Removed => {
|
AttributeMutation::Removed => {
|
||||||
if value != atom!("") {
|
if value != atom!("") {
|
||||||
if let Some(ref shadow_root) = containing_shadow_root {
|
if let Some(ref shadow_root) = containing_shadow_root {
|
||||||
shadow_root.unregister_named_element(self, value);
|
shadow_root.unregister_element_id(self, value);
|
||||||
} else {
|
} else {
|
||||||
doc.unregister_named_element(self, value);
|
doc.unregister_element_id(self, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2839,8 +2843,27 @@ impl VirtualMethods for Element {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// TODO: notify the document about the name change
|
// Keep the document name_map up to date
|
||||||
// once it has a name_map (#25548)
|
// (if we're not in shadow DOM)
|
||||||
|
if node.is_connected() && node.containing_shadow_root().is_none() {
|
||||||
|
let value = attr.value().as_atom().clone();
|
||||||
|
match mutation {
|
||||||
|
AttributeMutation::Set(old_value) => {
|
||||||
|
if let Some(old_value) = old_value {
|
||||||
|
let old_value = old_value.as_atom().clone();
|
||||||
|
doc.unregister_element_name(self, old_value);
|
||||||
|
}
|
||||||
|
if value != atom!("") {
|
||||||
|
doc.register_element_name(self, value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AttributeMutation::Removed => {
|
||||||
|
if value != atom!("") {
|
||||||
|
doc.unregister_element_name(self, value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
// FIXME(emilio): This is pretty dubious, and should be done in
|
// FIXME(emilio): This is pretty dubious, and should be done in
|
||||||
|
@ -2896,11 +2919,17 @@ impl VirtualMethods for Element {
|
||||||
|
|
||||||
if let Some(ref value) = *self.id_attribute.borrow() {
|
if let Some(ref value) = *self.id_attribute.borrow() {
|
||||||
if let Some(shadow_root) = self.upcast::<Node>().containing_shadow_root() {
|
if let Some(shadow_root) = self.upcast::<Node>().containing_shadow_root() {
|
||||||
shadow_root.register_named_element(self, value.clone());
|
shadow_root.register_element_id(self, value.clone());
|
||||||
} else {
|
} else {
|
||||||
doc.register_named_element(self, value.clone());
|
doc.register_element_id(self, value.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(ref value) = self.name_attribute() {
|
||||||
|
if self.upcast::<Node>().containing_shadow_root().is_none() {
|
||||||
|
doc.register_element_name(self, value.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This is used for layout optimization.
|
// This is used for layout optimization.
|
||||||
doc.increment_dom_count();
|
doc.increment_dom_count();
|
||||||
}
|
}
|
||||||
|
@ -2933,7 +2962,10 @@ impl VirtualMethods for Element {
|
||||||
doc.exit_fullscreen();
|
doc.exit_fullscreen();
|
||||||
}
|
}
|
||||||
if let Some(ref value) = *self.id_attribute.borrow() {
|
if let Some(ref value) = *self.id_attribute.borrow() {
|
||||||
doc.unregister_named_element(self, value.clone());
|
doc.unregister_element_id(self, value.clone());
|
||||||
|
}
|
||||||
|
if let Some(ref value) = self.name_attribute() {
|
||||||
|
doc.unregister_element_name(self, value.clone());
|
||||||
}
|
}
|
||||||
// This is used for layout optimization.
|
// This is used for layout optimization.
|
||||||
doc.decrement_dom_count();
|
doc.decrement_dom_count();
|
||||||
|
|
|
@ -83,6 +83,7 @@ use script_traits::UntrustedNodeAddress;
|
||||||
use selectors::matching::{matches_selector_list, MatchingContext, MatchingMode};
|
use selectors::matching::{matches_selector_list, MatchingContext, MatchingMode};
|
||||||
use selectors::parser::SelectorList;
|
use selectors::parser::SelectorList;
|
||||||
use servo_arc::Arc;
|
use servo_arc::Arc;
|
||||||
|
use servo_atoms::Atom;
|
||||||
use servo_url::ServoUrl;
|
use servo_url::ServoUrl;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::borrow::ToOwned;
|
use std::borrow::ToOwned;
|
||||||
|
@ -1170,6 +1171,34 @@ impl Node {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/#dom-document-nameditem-filter
|
||||||
|
pub fn is_document_named_item(&self, name: &Atom) -> bool {
|
||||||
|
let html_elem_type = match self.type_id() {
|
||||||
|
NodeTypeId::Element(ElementTypeId::HTMLElement(type_)) => type_,
|
||||||
|
_ => return false,
|
||||||
|
};
|
||||||
|
let elem = self
|
||||||
|
.downcast::<Element>()
|
||||||
|
.expect("Node with an Element::HTMLElement NodeTypeID must be an Element");
|
||||||
|
match html_elem_type {
|
||||||
|
HTMLElementTypeId::HTMLFormElement | HTMLElementTypeId::HTMLIFrameElement => {
|
||||||
|
elem.get_name().map_or(false, |n| n == *name)
|
||||||
|
},
|
||||||
|
HTMLElementTypeId::HTMLImageElement =>
|
||||||
|
// Images can match by id, but only when their name is non-empty.
|
||||||
|
{
|
||||||
|
elem.get_name().map_or(false, |n| {
|
||||||
|
n == *name || elem.get_id().map_or(false, |i| i == *name)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// TODO: Handle <embed> and <object>; these depend on
|
||||||
|
// whether the element is "exposed", a concept which
|
||||||
|
// doesn't fully make sense until embed/object behaviors
|
||||||
|
// are actually implemented.
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate through `nodes` until we find a `Node` that is not in `not_in`
|
/// Iterate through `nodes` until we find a `Node` that is not in `not_in`
|
||||||
|
|
|
@ -145,7 +145,7 @@ impl ShadowRoot {
|
||||||
|
|
||||||
/// Remove any existing association between the provided id and any elements
|
/// Remove any existing association between the provided id and any elements
|
||||||
/// in this shadow tree.
|
/// in this shadow tree.
|
||||||
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.document_or_shadow_root.unregister_named_element(
|
||||||
self.document_fragment.id_map(),
|
self.document_fragment.id_map(),
|
||||||
to_unregister,
|
to_unregister,
|
||||||
|
@ -154,7 +154,7 @@ impl ShadowRoot {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Associate an element present in this shadow tree with the provided id.
|
/// Associate an element present in this shadow tree 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
|
let root = self
|
||||||
.upcast::<Node>()
|
.upcast::<Node>()
|
||||||
.inclusive_ancestors(ShadowIncluding::No)
|
.inclusive_ancestors(ShadowIncluding::No)
|
||||||
|
|
|
@ -363877,6 +363877,12 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
"html/dom/documents/dom-tree-accessors/nameditem-names.html": [
|
||||||
|
[
|
||||||
|
"html/dom/documents/dom-tree-accessors/nameditem-names.html",
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"html/dom/documents/resource-metadata-management/document-compatmode-01.html": [
|
"html/dom/documents/resource-metadata-management/document-compatmode-01.html": [
|
||||||
[
|
[
|
||||||
"html/dom/documents/resource-metadata-management/document-compatmode-01.html",
|
"html/dom/documents/resource-metadata-management/document-compatmode-01.html",
|
||||||
|
@ -682171,6 +682177,10 @@
|
||||||
"bb024d9e781fc99d4f60d5d50d6a83970c661f6f",
|
"bb024d9e781fc99d4f60d5d50d6a83970c661f6f",
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
|
"html/dom/documents/dom-tree-accessors/nameditem-names.html": [
|
||||||
|
"3f76d85a1bca783df916b159409a94c30488a2dd",
|
||||||
|
"testharness"
|
||||||
|
],
|
||||||
"html/dom/documents/resource-metadata-management/document-compatmode-01.html": [
|
"html/dom/documents/resource-metadata-management/document-compatmode-01.html": [
|
||||||
"218a3fe84388111133e493ef50f1dd44f2c60923",
|
"218a3fe84388111133e493ef50f1dd44f2c60923",
|
||||||
"testharness"
|
"testharness"
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
[nameditem-02.html]
|
|
||||||
type: testharness
|
|
||||||
[If the only named item is an iframe, the contentWindow should be returned.]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[If there are two iframes, a collection should be returned.]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[If there are an iframe and another element (iframe first), a collection should be returned.]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[If there are an iframe and another element (iframe last), a collection should be returned.]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[If an iframe has a name and a different id, it should be returned by its name.]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[An iframe whose name looks like an array index should work.]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[nameditem-06.html]
|
|
||||||
type: testharness
|
|
||||||
[If there are two imgs, nothing should be returned. (id)]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
[nameditem-names.html]
|
||||||
|
type: testharness
|
||||||
|
[An embed name appears in a document's property names if the embed is exposed.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[An object name appears in a document's property names if the object is exposed.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[An object id appears in a document's property names if the object is exposed.]
|
||||||
|
expected: FAIL
|
|
@ -0,0 +1,101 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset=utf-8>
|
||||||
|
<title>Named items: supported property names</title>
|
||||||
|
<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-document-nameditem">
|
||||||
|
<script src="/resources/testharness.js"></script>
|
||||||
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
<div id="log"></div>
|
||||||
|
<embed name="exposed_embed">
|
||||||
|
<embed name="not_exposed_embed">
|
||||||
|
</embed>
|
||||||
|
</embed>
|
||||||
|
<form name="form">
|
||||||
|
</form>
|
||||||
|
<iframe name="iframe">
|
||||||
|
</iframe>
|
||||||
|
<img name="img">
|
||||||
|
<object name="exposed_object_with_name">
|
||||||
|
<object name="not_exposed_object_with_name">
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<object id="exposed_object_with_id">
|
||||||
|
<object id="not_exposed_object_with_id">
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<img name="img_with_id" id="img_id">
|
||||||
|
<img id="img_with_just_id">
|
||||||
|
<template id="template">
|
||||||
|
<img name="img_in_template">
|
||||||
|
</template>
|
||||||
|
<img name="42">
|
||||||
|
<script>
|
||||||
|
var names = Object.getOwnPropertyNames(document);
|
||||||
|
|
||||||
|
test(function() {
|
||||||
|
assert_true(names.includes("exposed_embed"))
|
||||||
|
}, "An embed name appears in a document's property names if the embed is exposed.");
|
||||||
|
|
||||||
|
test(function() {
|
||||||
|
assert_false(names.includes("not_exposed_embed"))
|
||||||
|
}, "An embed name does not appears in a document's property names if the embed is inside another embed.");
|
||||||
|
|
||||||
|
test(function() {
|
||||||
|
assert_true(names.includes("form"))
|
||||||
|
}, "A form name appears in a document's property names.");
|
||||||
|
|
||||||
|
test(function() {
|
||||||
|
assert_true(names.includes("iframe"))
|
||||||
|
}, "An iframe name appears in a document's property names.");
|
||||||
|
|
||||||
|
test(function() {
|
||||||
|
assert_true(names.includes("img"))
|
||||||
|
}, "An img name appears in a document's property names when the img has no id.");
|
||||||
|
|
||||||
|
test(function() {
|
||||||
|
assert_true(names.includes("exposed_object"))
|
||||||
|
}, "An object name appears in a document's property names if the object is exposed.");
|
||||||
|
|
||||||
|
test(function() {
|
||||||
|
assert_true(names.includes("exposed_object_with_id"))
|
||||||
|
}, "An object id appears in a document's property names if the object is exposed.");
|
||||||
|
|
||||||
|
test(function() {
|
||||||
|
assert_false(names.includes("not_exposed_object_with_name"))
|
||||||
|
}, "An object name does not appear in a document's property names if the object is inside another object.");
|
||||||
|
|
||||||
|
test(function() {
|
||||||
|
assert_false(names.includes("not_exposed_object_with_id"))
|
||||||
|
}, "An object id does not appear in a document's property names if the object is inside another object.");
|
||||||
|
|
||||||
|
test(function() {
|
||||||
|
assert_true(names.includes("img_with_id"))
|
||||||
|
}, "An img name appears in a document's property names when the img has an id.");
|
||||||
|
|
||||||
|
test(function() {
|
||||||
|
assert_true(names.includes("img_id"))
|
||||||
|
}, "An img id appears in a document's property names when the img has a name.");
|
||||||
|
|
||||||
|
test(function() {
|
||||||
|
assert_false(names.includes("img_with_just_id"))
|
||||||
|
}, "An img id does not appear in a document's property names when the img has no name.");
|
||||||
|
|
||||||
|
test(function() {
|
||||||
|
assert_true(names.includes("42"))
|
||||||
|
}, "A document's property names can include integer strings.");
|
||||||
|
|
||||||
|
test(function() {
|
||||||
|
assert_false(names.includes("template"))
|
||||||
|
}, "A template name does not appear in a document's property names.");
|
||||||
|
|
||||||
|
test(function() {
|
||||||
|
assert_false(names.includes("img_in_template"))
|
||||||
|
}, "An img name does not appear in a document's property names when the img is in a template's document fragment.");
|
||||||
|
|
||||||
|
test(function() {
|
||||||
|
var form_index = names.indexOf("form");
|
||||||
|
assert_equals(names.indexOf("iframe"), form_index + 1);
|
||||||
|
assert_equals(names.indexOf("img"), form_index + 2);
|
||||||
|
assert_greater_than(names.indexOf("img_id"), names.indexOf("img"));
|
||||||
|
assert_greater_than(names.indexOf("42"), names.indexOf("img_id"));
|
||||||
|
}, "A document's property names appear in tree order.");
|
||||||
|
</script>
|
Loading…
Add table
Add a link
Reference in a new issue