mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
auto merge of #2591 : brunoabinader/servo/document-queryselector-v3, r=jdm
This is a subtask for #2254 & #2576. Spec: http://dom.spec.whatwg.org/#dom-parentnode-queryselector
This commit is contained in:
commit
b52fbe0a5f
9 changed files with 226 additions and 7 deletions
|
@ -12,7 +12,8 @@ use dom::bindings::js::{JS, JSRef, Temporary, OptionalSettable, TemporaryPushabl
|
|||
use dom::bindings::js::OptionalRootable;
|
||||
use dom::bindings::trace::Untraceable;
|
||||
use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
|
||||
use dom::bindings::error::{ErrorResult, Fallible, NotSupported, InvalidCharacter, HierarchyRequest, NamespaceError};
|
||||
use dom::bindings::error::{ErrorResult, Fallible, NotSupported, InvalidCharacter};
|
||||
use dom::bindings::error::{HierarchyRequest, NamespaceError};
|
||||
use dom::bindings::utils::{xml_name_type, InvalidXMLName, Name, QName};
|
||||
use dom::comment::Comment;
|
||||
use dom::customevent::CustomEvent;
|
||||
|
@ -328,6 +329,7 @@ pub trait DocumentMethods {
|
|||
fn Applets(&self) -> Temporary<HTMLCollection>;
|
||||
fn Location(&self) -> Temporary<Location>;
|
||||
fn Children(&self) -> Temporary<HTMLCollection>;
|
||||
fn QuerySelector(&self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>>;
|
||||
fn GetOnload(&self) -> Option<EventHandlerNonNull>;
|
||||
fn SetOnload(&mut self, listener: Option<EventHandlerNonNull>);
|
||||
}
|
||||
|
@ -805,11 +807,18 @@ impl<'a> DocumentMethods for JSRef<'a, Document> {
|
|||
window.Location()
|
||||
}
|
||||
|
||||
// http://dom.spec.whatwg.org/#dom-parentnode-children
|
||||
fn Children(&self) -> Temporary<HTMLCollection> {
|
||||
let window = self.window.root();
|
||||
HTMLCollection::children(&*window, NodeCast::from_ref(self))
|
||||
}
|
||||
|
||||
// http://dom.spec.whatwg.org/#dom-parentnode-queryselector
|
||||
fn QuerySelector(&self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>> {
|
||||
let root: &JSRef<Node> = NodeCast::from_ref(self);
|
||||
root.query_selector(selectors)
|
||||
}
|
||||
|
||||
fn GetOnload(&self) -> Option<EventHandlerNonNull> {
|
||||
let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
|
||||
eventtarget.get_event_handler_common("load")
|
||||
|
|
|
@ -7,10 +7,12 @@ use dom::bindings::codegen::Bindings::DocumentFragmentBinding;
|
|||
use dom::bindings::js::{JSRef, Temporary};
|
||||
use dom::bindings::error::Fallible;
|
||||
use dom::document::Document;
|
||||
use dom::element::Element;
|
||||
use dom::eventtarget::{EventTarget, NodeTargetTypeId};
|
||||
use dom::htmlcollection::HTMLCollection;
|
||||
use dom::node::{DocumentFragmentNodeTypeId, Node, window_from_node};
|
||||
use dom::node::{DocumentFragmentNodeTypeId, Node, NodeHelpers, window_from_node};
|
||||
use dom::window::{Window, WindowMethods};
|
||||
use servo_util::str::DOMString;
|
||||
|
||||
#[deriving(Encodable)]
|
||||
pub struct DocumentFragment {
|
||||
|
@ -46,11 +48,19 @@ impl DocumentFragment {
|
|||
|
||||
pub trait DocumentFragmentMethods {
|
||||
fn Children(&self) -> Temporary<HTMLCollection>;
|
||||
fn QuerySelector(&self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>>;
|
||||
}
|
||||
|
||||
impl<'a> DocumentFragmentMethods for JSRef<'a, DocumentFragment> {
|
||||
// http://dom.spec.whatwg.org/#dom-parentnode-children
|
||||
fn Children(&self) -> Temporary<HTMLCollection> {
|
||||
let window = window_from_node(self).root();
|
||||
HTMLCollection::children(&window.root_ref(), NodeCast::from_ref(self))
|
||||
}
|
||||
|
||||
// http://dom.spec.whatwg.org/#dom-parentnode-queryselector
|
||||
fn QuerySelector(&self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>> {
|
||||
let root: &JSRef<Node> = NodeCast::from_ref(self);
|
||||
root.query_selector(selectors)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -196,6 +196,8 @@ impl LayoutElementHelpers for JS<Element> {
|
|||
|
||||
pub trait ElementHelpers {
|
||||
fn html_element_in_html_document(&self) -> bool;
|
||||
fn get_local_name<'a>(&'a self) -> &'a str;
|
||||
fn get_namespace<'a>(&'a self) -> &'a Namespace;
|
||||
}
|
||||
|
||||
impl<'a> ElementHelpers for JSRef<'a, Element> {
|
||||
|
@ -204,6 +206,14 @@ impl<'a> ElementHelpers for JSRef<'a, Element> {
|
|||
let node: &JSRef<Node> = NodeCast::from_ref(self);
|
||||
is_html && node.owner_doc().root().is_html_document
|
||||
}
|
||||
|
||||
fn get_local_name<'a>(&'a self) -> &'a str {
|
||||
self.deref().local_name.as_slice()
|
||||
}
|
||||
|
||||
fn get_namespace<'a>(&'a self) -> &'a Namespace {
|
||||
&self.deref().namespace
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AttributeHandlers {
|
||||
|
@ -418,6 +428,7 @@ pub trait ElementMethods {
|
|||
fn GetInnerHTML(&self) -> Fallible<DOMString>;
|
||||
fn GetOuterHTML(&self) -> Fallible<DOMString>;
|
||||
fn Children(&self) -> Temporary<HTMLCollection>;
|
||||
fn QuerySelector(&self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>>;
|
||||
fn Remove(&self);
|
||||
}
|
||||
|
||||
|
@ -688,11 +699,18 @@ impl<'a> ElementMethods for JSRef<'a, Element> {
|
|||
Ok(serialize(&mut NodeIterator::new(NodeCast::from_ref(self), true, false)))
|
||||
}
|
||||
|
||||
// http://dom.spec.whatwg.org/#dom-parentnode-children
|
||||
fn Children(&self) -> Temporary<HTMLCollection> {
|
||||
let window = window_from_node(self).root();
|
||||
HTMLCollection::children(&*window, NodeCast::from_ref(self))
|
||||
}
|
||||
|
||||
// http://dom.spec.whatwg.org/#dom-parentnode-queryselector
|
||||
fn QuerySelector(&self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>> {
|
||||
let root: &JSRef<Node> = NodeCast::from_ref(self);
|
||||
root.query_selector(selectors)
|
||||
}
|
||||
|
||||
// http://dom.spec.whatwg.org/#dom-childnode-remove
|
||||
fn Remove(&self) {
|
||||
let node: &JSRef<Node> = NodeCast::from_ref(self);
|
||||
|
@ -799,3 +817,33 @@ impl<'a> VirtualMethods for JSRef<'a, Element> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> style::TElement for JSRef<'a, Element> {
|
||||
fn get_attr(&self, namespace: &Namespace, attr: &str) -> Option<&'static str> {
|
||||
self.get_attribute(namespace.clone(), attr).root().map(|attr| {
|
||||
unsafe { mem::transmute(attr.deref().value_ref()) }
|
||||
})
|
||||
}
|
||||
fn get_link(&self) -> Option<&'static str> {
|
||||
// FIXME: This is HTML only.
|
||||
let node: &JSRef<Node> = NodeCast::from_ref(self);
|
||||
match node.type_id() {
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/multipage/selectors.html#
|
||||
// selector-link
|
||||
ElementNodeTypeId(HTMLAnchorElementTypeId) |
|
||||
ElementNodeTypeId(HTMLAreaElementTypeId) |
|
||||
ElementNodeTypeId(HTMLLinkElementTypeId) => self.get_attr(&namespace::Null, "href"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
fn get_local_name<'a>(&'a self) -> &'a str {
|
||||
(self as &ElementHelpers).get_local_name()
|
||||
}
|
||||
fn get_namespace<'a>(&'a self) -> &'a Namespace {
|
||||
(self as &ElementHelpers).get_namespace()
|
||||
}
|
||||
fn get_hover_state(&self) -> bool {
|
||||
let node: &JSRef<Node> = NodeCast::from_ref(self);
|
||||
node.get_hover_state()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
|
||||
//! The core DOM types. Defines the basic DOM hierarchy as well as all the HTML elements.
|
||||
|
||||
use dom::attr::Attr;
|
||||
use cssparser::tokenize;
|
||||
use dom::attr::{Attr, AttrMethods};
|
||||
use dom::bindings::codegen::InheritTypes::{CommentCast, DocumentCast, DocumentTypeCast};
|
||||
use dom::bindings::codegen::InheritTypes::{ElementCast, TextCast, NodeCast, ElementDerived};
|
||||
use dom::bindings::codegen::InheritTypes::{CharacterDataCast, NodeBase, NodeDerived};
|
||||
|
@ -14,14 +15,15 @@ use dom::bindings::js::{JS, JSRef, RootedReference, Temporary, Root, OptionalUnr
|
|||
use dom::bindings::js::{OptionalSettable, TemporaryPushable, OptionalRootedRootable};
|
||||
use dom::bindings::js::{ResultRootable, OptionalRootable};
|
||||
use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
|
||||
use dom::bindings::error::{ErrorResult, Fallible, NotFound, HierarchyRequest};
|
||||
use dom::bindings::error::{ErrorResult, Fallible, NotFound, HierarchyRequest, Syntax};
|
||||
use dom::bindings::utils;
|
||||
use dom::characterdata::{CharacterData, CharacterDataMethods};
|
||||
use dom::comment::Comment;
|
||||
use dom::document::{Document, DocumentMethods, DocumentHelpers, HTMLDocument, NonHTMLDocument};
|
||||
use dom::documentfragment::DocumentFragment;
|
||||
use dom::documenttype::DocumentType;
|
||||
use dom::element::{Element, ElementMethods, ElementTypeId, HTMLAnchorElementTypeId};
|
||||
use dom::element::{AttributeHandlers, Element, ElementMethods, ElementTypeId};
|
||||
use dom::element::{HTMLAnchorElementTypeId, ElementHelpers};
|
||||
use dom::eventtarget::{EventTarget, NodeTargetTypeId};
|
||||
use dom::nodelist::{NodeList};
|
||||
use dom::processinginstruction::{ProcessingInstruction, ProcessingInstructionMethods};
|
||||
|
@ -34,6 +36,7 @@ use layout_interface::{ContentBoxQuery, ContentBoxResponse, ContentBoxesQuery, C
|
|||
LayoutChan, ReapLayoutDataMsg, TrustedNodeAddress, UntrustedNodeAddress};
|
||||
use servo_util::geometry::Au;
|
||||
use servo_util::str::{DOMString, null_str_as_empty};
|
||||
use style::{parse_selector_list, matches_compound_selector, NamespaceMap};
|
||||
|
||||
use js::jsapi::{JSContext, JSObject, JSRuntime};
|
||||
use js::jsfriendapi;
|
||||
|
@ -42,6 +45,7 @@ use libc::uintptr_t;
|
|||
use std::cell::{Cell, RefCell, Ref, RefMut};
|
||||
use std::iter::{Map, Filter};
|
||||
use std::mem;
|
||||
use style;
|
||||
use style::ComputedValues;
|
||||
use sync::Arc;
|
||||
|
||||
|
@ -384,6 +388,8 @@ pub trait NodeHelpers {
|
|||
fn get_bounding_content_box(&self) -> Rect<Au>;
|
||||
fn get_content_boxes(&self) -> Vec<Rect<Au>>;
|
||||
|
||||
fn query_selector(&self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>>;
|
||||
|
||||
fn remove_self(&self);
|
||||
}
|
||||
|
||||
|
@ -544,6 +550,31 @@ impl<'a> NodeHelpers for JSRef<'a, Node> {
|
|||
rects
|
||||
}
|
||||
|
||||
// http://dom.spec.whatwg.org/#dom-parentnode-queryselector
|
||||
fn query_selector(&self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>> {
|
||||
// Step 1.
|
||||
let namespace = NamespaceMap::new();
|
||||
match parse_selector_list(tokenize(selectors.as_slice()).map(|(token, _)| token).collect(), &namespace) {
|
||||
// Step 2.
|
||||
None => return Err(Syntax),
|
||||
// Step 3.
|
||||
Some(ref selectors) => {
|
||||
for selector in selectors.iter() {
|
||||
assert!(selector.pseudo_element.is_none());
|
||||
let root = self.ancestors().last().unwrap_or(self.clone());
|
||||
for node in root.traverse_preorder().filter(|node| node.is_element()) {
|
||||
let mut _shareable: bool = false;
|
||||
if matches_compound_selector(selector.compound_selectors.deref(), &node, &mut _shareable) {
|
||||
let elem: &JSRef<Element> = ElementCast::to_ref(&node).unwrap();
|
||||
return Ok(Some(Temporary::from_rooted(elem)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn ancestors(&self) -> AncestorIterator {
|
||||
AncestorIterator {
|
||||
current: self.parent_node.get().map(|node| (*node.root()).clone()),
|
||||
|
@ -1900,3 +1931,46 @@ impl<'a> VirtualMethods for JSRef<'a, Node> {
|
|||
Some(eventtarget as &VirtualMethods:)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> style::TNode<JSRef<'a, Element>> for JSRef<'a, Node> {
|
||||
fn parent_node(&self) -> Option<JSRef<'a, Node>> {
|
||||
(self as &NodeHelpers).parent_node().map(|node| *node.root())
|
||||
}
|
||||
fn prev_sibling(&self) -> Option<JSRef<'a, Node>> {
|
||||
(self as &NodeHelpers).prev_sibling().map(|node| *node.root())
|
||||
}
|
||||
fn next_sibling(&self) -> Option<JSRef<'a, Node>> {
|
||||
(self as &NodeHelpers).next_sibling().map(|node| *node.root())
|
||||
}
|
||||
fn is_document(&self) -> bool {
|
||||
(self as &NodeHelpers).is_document()
|
||||
}
|
||||
fn is_element(&self) -> bool {
|
||||
(self as &NodeHelpers).is_element()
|
||||
}
|
||||
fn as_element(&self) -> JSRef<'a, Element> {
|
||||
let elem: Option<&JSRef<'a, Element>> = ElementCast::to_ref(self);
|
||||
assert!(elem.is_some());
|
||||
*elem.unwrap()
|
||||
}
|
||||
fn match_attr(&self, attr: &style::AttrSelector, test: |&str| -> bool) -> bool {
|
||||
let name = {
|
||||
let elem: Option<&JSRef<'a, Element>> = ElementCast::to_ref(self);
|
||||
assert!(elem.is_some());
|
||||
let elem: &ElementHelpers = elem.unwrap() as &ElementHelpers;
|
||||
if elem.html_element_in_html_document() {
|
||||
attr.lower_name.as_slice()
|
||||
} else {
|
||||
attr.name.as_slice()
|
||||
}
|
||||
};
|
||||
match attr.namespace {
|
||||
style::SpecificNamespace(ref ns) => {
|
||||
self.as_element().get_attribute(ns.clone(), name).root()
|
||||
.map_or(false, |attr| test(attr.deref().Value().as_slice()))
|
||||
},
|
||||
// FIXME: https://github.com/mozilla/servo/issues/1558
|
||||
style::AnyNamespace => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,4 +22,10 @@ interface ParentNode {
|
|||
// Not implemented yet
|
||||
// void prepend((Node or DOMString)... nodes);
|
||||
// void append((Node or DOMString)... nodes);
|
||||
|
||||
//Element? query(DOMString relativeSelectors);
|
||||
//[NewObject] Elements queryAll(DOMString relativeSelectors);
|
||||
[Throws]
|
||||
Element? querySelector(DOMString selectors);
|
||||
//[NewObject] NodeList querySelectorAll(DOMString selectors);
|
||||
};
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
extern crate log;
|
||||
|
||||
extern crate debug;
|
||||
extern crate cssparser;
|
||||
extern crate collections;
|
||||
extern crate geom;
|
||||
extern crate hubbub;
|
||||
|
|
|
@ -521,7 +521,7 @@ impl Ord for MatchedProperty {
|
|||
/// `shareable` to false unless you are willing to update the style sharing logic. Otherwise things
|
||||
/// will almost certainly break as nodes will start mistakenly sharing styles. (See the code in
|
||||
/// `main/css/matching.rs`.)
|
||||
fn matches_compound_selector<E:TElement,
|
||||
pub fn matches_compound_selector<E:TElement,
|
||||
N:TNode<E>>(
|
||||
selector: &CompoundSelector,
|
||||
element: &N,
|
||||
|
|
|
@ -33,7 +33,7 @@ extern crate servo_util = "util";
|
|||
// Public API
|
||||
pub use stylesheets::{Stylesheet, CSSRule, StyleRule};
|
||||
pub use selector_matching::{Stylist, StylesheetOrigin, UserAgentOrigin, AuthorOrigin, UserOrigin};
|
||||
pub use selector_matching::{MatchedProperty};
|
||||
pub use selector_matching::{MatchedProperty, matches_compound_selector};
|
||||
pub use properties::{cascade, cascade_anonymous};
|
||||
pub use properties::{PropertyDeclaration, ComputedValues, computed_values, style_structs};
|
||||
pub use properties::{PropertyDeclarationBlock, parse_style_attribute}; // Style attributes
|
||||
|
@ -43,6 +43,7 @@ pub use errors::with_errors_silenced;
|
|||
pub use node::{TElement, TNode};
|
||||
pub use selectors::{PseudoElement, Before, After, AttrSelector, SpecificNamespace, AnyNamespace};
|
||||
pub use selectors::{NamespaceConstraint, Selector, CompoundSelector, SimpleSelector, Combinator};
|
||||
pub use selectors::{parse_selector_list};
|
||||
pub use namespaces::NamespaceMap;
|
||||
pub use media_queries::{MediaRule, MediaQueryList, MediaQuery, Device, MediaType, MediaQueryType};
|
||||
|
||||
|
|
70
src/test/content/test_parentNode_querySelector.html
Normal file
70
src/test/content/test_parentNode_querySelector.html
Normal file
|
@ -0,0 +1,70 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="harness.js"></script>
|
||||
<script>
|
||||
{ // document.querySelector
|
||||
let div = document.getElementById("foo");
|
||||
is(document.querySelector("#foo"), div);
|
||||
|
||||
div = document.getElementById("foo\\bar");
|
||||
is(document.querySelector("#foo\\\\bar"), div);
|
||||
|
||||
div = document.getElementById("foo:bar");
|
||||
is(document.querySelector("#foo\\:bar"), div);
|
||||
|
||||
div = document.getElementById("bar");
|
||||
is(document.querySelector("div.myClass"), div);
|
||||
is(document.querySelector("div:nth-of-type(4)"), div);
|
||||
}
|
||||
{ // element.querySelector
|
||||
let body = document.body;
|
||||
let div = document.getElementById("foo");
|
||||
is(body.querySelector("#foo"), div);
|
||||
|
||||
div = document.getElementById("foo\\bar");
|
||||
is(body.querySelector("#foo\\\\bar"), div);
|
||||
|
||||
div = document.getElementById("foo:bar");
|
||||
is(body.querySelector("#foo\\:bar"), div);
|
||||
|
||||
div = document.getElementById("bar");
|
||||
is(body.querySelector("div.myClass"), div);
|
||||
is(body.querySelector("div:nth-of-type(4)"), div);
|
||||
}
|
||||
|
||||
{ // docfrag.querySelector
|
||||
let docfrag = document.createDocumentFragment();
|
||||
|
||||
let div = document.createElement("div");
|
||||
div.id = "foo";
|
||||
div.className = "myClass";
|
||||
|
||||
let child = document.createElement("div");
|
||||
div.appendChild(child);
|
||||
docfrag.appendChild(div);
|
||||
|
||||
let p = document.createElement("p");
|
||||
p.id = "bar";
|
||||
p.className = "myClass";
|
||||
docfrag.appendChild(p);
|
||||
|
||||
is(docfrag.querySelector("#foo"), div);
|
||||
is(docfrag.querySelector("div.myClass"), div);
|
||||
|
||||
is(docfrag.querySelector("#bar"), p);
|
||||
is(docfrag.querySelector("p.myClass"), p);
|
||||
|
||||
is(docfrag.querySelector(".myClass"), div);
|
||||
is(docfrag.querySelector("div > div"), child);
|
||||
}
|
||||
finish();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="foo"></div>
|
||||
<div id="foo\bar"></div>
|
||||
<div id="foo:bar"></div>
|
||||
<div id="bar" class="myClass"></p>
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue