Register named elements in either the document or shadow tree

This commit is contained in:
Fernando Jiménez Moreno 2019-03-05 10:23:18 +01:00
parent ccf8a43649
commit 740aae06ba
6 changed files with 131 additions and 61 deletions

View file

@ -74,7 +74,6 @@ use crate::dom::keyboardevent::KeyboardEvent;
use crate::dom::location::Location;
use crate::dom::messageevent::MessageEvent;
use crate::dom::mouseevent::MouseEvent;
use crate::dom::node::VecPreOrderInsertionHelper;
use crate::dom::node::{self, document_from_node, window_from_node, CloneChildrenFlag};
use crate::dom::node::{LayoutNodeHelpers, Node, NodeDamage, NodeFlags, ShadowIncluding};
use crate::dom::nodeiterator::NodeIterator;
@ -682,55 +681,23 @@ 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) {
debug!(
"Removing named element from document {:p}: {:p} id={}",
self, to_unregister, id
);
// Limit the scope of the borrow because id_map might be borrowed again by
// GetElementById through the following sequence of calls
// reset_form_owner_for_listeners -> reset_form_owner -> GetElementById
{
let mut id_map = self.id_map.borrow_mut();
let is_empty = match id_map.get_mut(&id) {
None => false,
Some(elements) => {
let position = elements
.iter()
.position(|element| &**element == to_unregister)
.expect("This element should be in registered.");
elements.remove(position);
elements.is_empty()
},
};
if is_empty {
id_map.remove(&id);
}
}
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) {
debug!(
"Adding named element to document {:p}: {:p} id={}",
self, element, id
);
assert!(element.upcast::<Node>().is_connected());
assert!(!id.is_empty());
let root = self.GetDocumentElement().expect(
"The element is in the document, so there must be a document \
element.",
);
// Limit the scope of the borrow because id_map might be borrowed again by
// GetElementById through the following sequence of calls
// reset_form_owner_for_listeners -> reset_form_owner -> GetElementById
{
let mut id_map = self.id_map.borrow_mut();
let elements = id_map.entry(id.clone()).or_insert(Vec::new());
elements.insert_pre_order(element, root.upcast::<Node>());
}
self.document_or_shadow_root.register_named_element(
&self.id_map,
element,
&id,
DomRoot::from_ref(root.upcast::<Node>()),
);
self.reset_form_owner_for_listeners(&id);
}
@ -2619,11 +2586,11 @@ impl Document {
url: DomRefCell::new(url),
// https://dom.spec.whatwg.org/#concept-document-quirks
quirks_mode: Cell::new(QuirksMode::NoQuirks),
id_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,
activity: Cell::new(activity),
id_map: DomRefCell::new(HashMap::new()),
tag_map: DomRefCell::new(HashMap::new()),
tagns_map: DomRefCell::new(HashMap::new()),
classes_map: DomRefCell::new(HashMap::new()),

View file

@ -2,27 +2,31 @@
* 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 crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::DocumentFragmentBinding;
use crate::dom::bindings::codegen::Bindings::DocumentFragmentBinding::DocumentFragmentMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::codegen::UnionTypes::NodeOrString;
use crate::dom::bindings::error::{ErrorResult, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::Element;
use crate::dom::htmlcollection::HTMLCollection;
use crate::dom::node::{window_from_node, Node, ShadowIncluding};
use crate::dom::node::{window_from_node, Node};
use crate::dom::nodelist::NodeList;
use crate::dom::window::Window;
use dom_struct::dom_struct;
use servo_atoms::Atom;
use std::collections::HashMap;
// https://dom.spec.whatwg.org/#documentfragment
#[dom_struct]
pub struct DocumentFragment {
node: Node,
/// Caches for the getElement methods
id_map: DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>,
}
impl DocumentFragment {
@ -30,6 +34,7 @@ impl DocumentFragment {
pub fn new_inherited(document: &Document) -> DocumentFragment {
DocumentFragment {
node: Node::new_inherited(document),
id_map: DomRefCell::new(HashMap::new()),
}
}
@ -46,6 +51,10 @@ impl DocumentFragment {
Ok(DocumentFragment::new(&document))
}
pub fn id_map(&self) -> &DomRefCell<HashMap<Atom, Vec<Dom<Element>>>> {
&self.id_map
}
}
impl DocumentFragmentMethods for DocumentFragment {
@ -57,16 +66,11 @@ impl DocumentFragmentMethods for DocumentFragment {
// https://dom.spec.whatwg.org/#dom-nonelementparentnode-getelementbyid
fn GetElementById(&self, id: DOMString) -> Option<DomRoot<Element>> {
let node = self.upcast::<Node>();
let id = Atom::from(id);
node.traverse_preorder(ShadowIncluding::No)
.filter_map(DomRoot::downcast::<Element>)
.find(
|descendant| match descendant.get_attribute(&ns!(), &local_name!("id")) {
None => false,
Some(attr) => *attr.value().as_atom() == id,
},
)
self.id_map
.borrow()
.get(&id)
.map(|ref elements| DomRoot::from_ref(&*(*elements)[0]))
}
// https://dom.spec.whatwg.org/#dom-parentnode-firstelementchild

View file

@ -2,6 +2,7 @@
* 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 crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::num::Finite;
@ -9,13 +10,15 @@ use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::element::Element;
use crate::dom::htmlelement::HTMLElement;
use crate::dom::htmlmetaelement::HTMLMetaElement;
use crate::dom::node;
use crate::dom::node::{self, Node, VecPreOrderInsertionHelper};
use crate::dom::window::Window;
use euclid::Point2D;
use js::jsapi::JS_GetRuntime;
use script_layout_interface::message::{NodesFromPointQueryType, QueryMsg};
use script_traits::UntrustedNodeAddress;
use servo_arc::Arc;
use servo_atoms::Atom;
use std::collections::HashMap;
use std::fmt;
use style::context::QuirksMode;
use style::invalidation::media_queries::{MediaListKey, ToMediaListKey};
@ -262,4 +265,59 @@ impl DocumentOrShadowRoot {
},
}
}
/// Remove any existing association between the provided id and any elements in this document.
pub fn unregister_named_element(
&self,
id_map: &DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>,
to_unregister: &Element,
id: &Atom,
) {
debug!(
"Removing named element {:p}: {:p} id={}",
self, to_unregister, id
);
// Limit the scope of the borrow because id_map might be borrowed again by
// GetElementById through the following sequence of calls
// reset_form_owner_for_listeners -> reset_form_owner -> GetElementById
{
let mut id_map = id_map.borrow_mut();
let is_empty = match id_map.get_mut(&id) {
None => false,
Some(elements) => {
let position = elements
.iter()
.position(|element| &**element == to_unregister)
.expect("This element should be in registered.");
elements.remove(position);
elements.is_empty()
},
};
if is_empty {
id_map.remove(&id);
}
}
}
/// Associate an element present in this document with the provided id.
pub fn register_named_element(
&self,
id_map: &DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>,
element: &Element,
id: &Atom,
root: DomRoot<Node>,
) {
debug!("Adding named element {:p}: {:p} id={}", self, element, id);
assert!(element.upcast::<Node>().is_connected());
assert!(!id.is_empty());
// Limit the scope of the borrow because id_map might be borrowed again by
// GetElementById through the following sequence of calls
// reset_form_owner_for_listeners -> reset_form_owner -> GetElementById
{
let mut id_map = id_map.borrow_mut();
let elements = id_map.entry(id.clone()).or_insert(Vec::new());
elements.insert_pre_order(element, &root);
}
}
}

View file

@ -2741,21 +2741,34 @@ impl VirtualMethods for Element {
None
}
});
let owner_shadow_root = self.upcast::<Node>().owner_shadow_root();
if node.is_connected() {
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_named_element(self, old_value);
if let Some(ref shadow_root) = owner_shadow_root {
shadow_root.unregister_named_element(self, old_value);
} else {
doc.unregister_named_element(self, old_value);
}
}
if value != atom!("") {
doc.register_named_element(self, value);
if let Some(ref shadow_root) = owner_shadow_root {
shadow_root.register_named_element(self, value);
} else {
doc.register_named_element(self, value);
}
}
},
AttributeMutation::Removed => {
if value != atom!("") {
doc.unregister_named_element(self, value);
if let Some(ref shadow_root) = owner_shadow_root {
shadow_root.unregister_named_element(self, value);
} else {
doc.unregister_named_element(self, value);
}
}
},
}
@ -2800,8 +2813,6 @@ impl VirtualMethods for Element {
return;
}
let doc = document_from_node(self);
if let Some(shadow_root) = self.upcast::<Node>().owner_shadow_root() {
let shadow_root = shadow_root.upcast::<Node>();
shadow_root.set_flag(NodeFlags::IS_CONNECTED, tree_connected);
@ -2811,8 +2822,13 @@ impl VirtualMethods for Element {
}
}
let doc = document_from_node(self);
if let Some(ref value) = *self.id_attribute.borrow() {
doc.register_named_element(self, value.clone());
if let Some(shadow_root) = self.upcast::<Node>().owner_shadow_root() {
shadow_root.register_named_element(self, value.clone());
} else {
doc.register_named_element(self, value.clone());
}
}
// This is used for layout optimization.
doc.increment_dom_count();

View file

@ -20,6 +20,7 @@ use crate::dom::window::Window;
use dom_struct::dom_struct;
use selectors::context::QuirksMode;
use servo_arc::Arc;
use servo_atoms::Atom;
use style::author_styles::AuthorStyles;
use style::dom::TElement;
use style::media_queries::Device;
@ -126,6 +127,27 @@ impl ShadowRoot {
.upcast::<Node>()
.dirty(NodeDamage::NodeStyleDamaged);
}
/// Remove any existing association between the provided id and any elements
/// in this shadow tree.
pub fn unregister_named_element(&self, to_unregister: &Element, id: Atom) {
self.document_or_shadow_root.unregister_named_element(
self.document_fragment.id_map(),
to_unregister,
&id,
);
}
/// Associate an element present in this shadow tree with the provided id.
pub fn register_named_element(&self, element: &Element, id: Atom) {
let root = self.upcast::<Node>().inclusive_ancestors().last().unwrap();
self.document_or_shadow_root.register_named_element(
self.document_fragment.id_map(),
element,
&id,
root,
);
}
}
impl ShadowRootMethods for ShadowRoot {

View file

@ -6,7 +6,7 @@
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<h1 class="header">Not in the shadows</h1>
<h1 id="header" class="header">Not in the shadows</h1>
<div id="host">
</div>
<script>
@ -20,12 +20,14 @@
assert_equals(document.body.getElementsByTagName('h1').length, 1);
assert_equals(document.body.getElementsByClassName('header').length, 1);
assert_equals(document.getElementById('header').textContent, "Not in the shadows");
assert_equals(document.querySelectorAll('h1').length, 1);
assert_equals(document.body.childNodes.length, 6);
// Append child to shadow tree and check that its content is encapsulated.
var shadowChild = document.createElement('h1');
shadowChild.classList.add('header');
shadowChild.setAttribute('id', 'header');
shadowChild.textContent = 'In the shadows';
shadowRoot.appendChild(shadowChild);
assert_equals(document.body.getElementsByTagName('h1').length, 1);
@ -33,6 +35,7 @@
assert_equals(document.querySelectorAll('h1').length, 1);
assert_equals(document.body.childNodes.length, 6);
assert_equals(shadowRoot.querySelectorAll('h1').length, 1);
assert_equals(shadowRoot.getElementById('header').textContent, "In the shadows");
});
</script>
</body>