diff --git a/src/components/script/dom/document.rs b/src/components/script/dom/document.rs index a00a12c26de..9b4d7aaf55d 100644 --- a/src/components/script/dom/document.rs +++ b/src/components/script/dom/document.rs @@ -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; fn Location(&self) -> Temporary; fn Children(&self) -> Temporary; + fn QuerySelector(&self, selectors: DOMString) -> Fallible>>; fn GetOnload(&self) -> Option; fn SetOnload(&mut self, listener: Option); } @@ -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 { 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>> { + let root: &JSRef = NodeCast::from_ref(self); + root.query_selector(selectors) + } + fn GetOnload(&self) -> Option { let eventtarget: &JSRef = EventTargetCast::from_ref(self); eventtarget.get_event_handler_common("load") diff --git a/src/components/script/dom/documentfragment.rs b/src/components/script/dom/documentfragment.rs index 886b1b894e1..bcf9c28e7c1 100644 --- a/src/components/script/dom/documentfragment.rs +++ b/src/components/script/dom/documentfragment.rs @@ -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; + fn QuerySelector(&self, selectors: DOMString) -> Fallible>>; } impl<'a> DocumentFragmentMethods for JSRef<'a, DocumentFragment> { + // http://dom.spec.whatwg.org/#dom-parentnode-children fn Children(&self) -> Temporary { 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>> { + let root: &JSRef = NodeCast::from_ref(self); + root.query_selector(selectors) + } } diff --git a/src/components/script/dom/element.rs b/src/components/script/dom/element.rs index 5fce6ba6903..3189360ccf5 100644 --- a/src/components/script/dom/element.rs +++ b/src/components/script/dom/element.rs @@ -196,6 +196,8 @@ impl LayoutElementHelpers for JS { 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 = 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; fn GetOuterHTML(&self) -> Fallible; fn Children(&self) -> Temporary; + fn QuerySelector(&self, selectors: DOMString) -> Fallible>>; 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 { 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>> { + let root: &JSRef = NodeCast::from_ref(self); + root.query_selector(selectors) + } + // http://dom.spec.whatwg.org/#dom-childnode-remove fn Remove(&self) { let node: &JSRef = 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 = 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 = NodeCast::from_ref(self); + node.get_hover_state() + } +} diff --git a/src/components/script/dom/node.rs b/src/components/script/dom/node.rs index 45c85ea793e..261518752cb 100644 --- a/src/components/script/dom/node.rs +++ b/src/components/script/dom/node.rs @@ -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; fn get_content_boxes(&self) -> Vec>; + fn query_selector(&self, selectors: DOMString) -> Fallible>>; + 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>> { + // 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 = 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> for JSRef<'a, Node> { + fn parent_node(&self) -> Option> { + (self as &NodeHelpers).parent_node().map(|node| *node.root()) + } + fn prev_sibling(&self) -> Option> { + (self as &NodeHelpers).prev_sibling().map(|node| *node.root()) + } + fn next_sibling(&self) -> Option> { + (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, + } + } +} diff --git a/src/components/script/dom/webidls/ParentNode.webidl b/src/components/script/dom/webidls/ParentNode.webidl index 54b05fbbad9..70ab892fea8 100644 --- a/src/components/script/dom/webidls/ParentNode.webidl +++ b/src/components/script/dom/webidls/ParentNode.webidl @@ -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); }; diff --git a/src/components/script/script.rs b/src/components/script/script.rs index f6dd0ba7025..860187dd5d2 100644 --- a/src/components/script/script.rs +++ b/src/components/script/script.rs @@ -17,6 +17,7 @@ extern crate log; extern crate debug; +extern crate cssparser; extern crate collections; extern crate geom; extern crate hubbub; diff --git a/src/components/style/selector_matching.rs b/src/components/style/selector_matching.rs index 66a874ae798..061a8fc76b5 100644 --- a/src/components/style/selector_matching.rs +++ b/src/components/style/selector_matching.rs @@ -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>( selector: &CompoundSelector, element: &N, diff --git a/src/components/style/style.rs b/src/components/style/style.rs index e667fa743b3..0368b71c1f6 100644 --- a/src/components/style/style.rs +++ b/src/components/style/style.rs @@ -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}; diff --git a/src/test/content/test_parentNode_querySelector.html b/src/test/content/test_parentNode_querySelector.html new file mode 100644 index 00000000000..556a45ba81a --- /dev/null +++ b/src/test/content/test_parentNode_querySelector.html @@ -0,0 +1,70 @@ + + + + + + + +
+
+
+

+ +