1 use webcore::value::Reference;
2 use webcore::try_from::{TryFrom, TryInto};
3 use webcore::promise::{Promise, TypedPromise};
4 use webapi::error::TypeError;
5 use webapi::dom_exception::{InvalidCharacterError, InvalidPointerId, NoModificationAllowedError, SyntaxError};
6 use webapi::event_target::{IEventTarget, EventTarget};
7 use webapi::node::{INode, Node};
8 use webapi::token_list::TokenList;
9 use webapi::parent_node::IParentNode;
10 use webapi::child_node::IChildNode;
11 use webapi::slotable::ISlotable;
12 use webapi::shadow_root::{ShadowRootMode, ShadowRoot};
13 use webapi::dom_exception::{NotSupportedError, InvalidStateError};
14 
15 error_enum_boilerplate! {
16     AttachShadowError,
17     NotSupportedError, InvalidStateError
18 }
19 
20 /// The `IElement` interface represents an object of a [Document](struct.Document.html).
21 /// This interface describes methods and properties common to all
22 /// kinds of elements.
23 ///
24 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element)
25 // https://dom.spec.whatwg.org/#element
26 pub trait IElement: INode + IParentNode + IChildNode + ISlotable {
27     /// The Element.namespaceURI read-only property returns the namespace URI
28     /// of the element, or null if the element is not in a namespace.
29     ///
30     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/namespaceURI)
31     // https://dom.spec.whatwg.org/#ref-for-dom-element-namespaceuri
namespace_uri( &self ) -> Option< String >32     fn namespace_uri( &self ) -> Option< String > {
33         js!(
34             return @{self.as_ref()}.namespaceURI;
35         ).try_into().unwrap()
36     }
37 
38     /// The Element.classList is a read-only property which returns a live
39     /// [TokenList](struct.TokenList.html) collection of the class attributes
40     /// of the element.
41     ///
42     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList)
43     // https://dom.spec.whatwg.org/#ref-for-dom-element-classlist
class_list( &self ) -> TokenList44     fn class_list( &self ) -> TokenList {
45         unsafe {
46             js!( return @{self.as_ref()}.classList; ).into_reference_unchecked().unwrap()
47         }
48     }
49 
50     /// The Element.hasAttribute() method returns a Boolean value indicating whether
51     /// the specified element has the specified attribute or not.
52     ///
53     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/hasAttribute)
54     // https://dom.spec.whatwg.org/#ref-for-dom-element-hasattribute
has_attribute( &self, name: &str ) -> bool55     fn has_attribute( &self, name: &str ) -> bool {
56         js!(
57             return @{self.as_ref()}.hasAttribute( @{name} );
58         ).try_into().unwrap()
59     }
60 
61     /// Element.getAttribute() returns the value of a specified attribute on the element.
62     /// If the given attribute does not exist, the value returned will either be
63     /// null or "" (the empty string);
64     ///
65     /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute)
66     // https://dom.spec.whatwg.org/#ref-for-dom-element-getattribute
get_attribute( &self, name: &str ) -> Option< String >67     fn get_attribute( &self, name: &str ) -> Option< String > {
68         js!(
69             return @{self.as_ref()}.getAttribute( @{name} );
70         ).try_into().unwrap()
71     }
72 
73     /// Sets the value of an attribute on the specified element. If the attribute already
74     /// exists, the value is updated; otherwise a new attribute is added with the
75     /// specified name and value.
76     ///
77     /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute)
78     // https://dom.spec.whatwg.org/#ref-for-dom-element-setattribute
set_attribute( &self, name: &str, value: &str ) -> Result< (), InvalidCharacterError >79     fn set_attribute( &self, name: &str, value: &str ) -> Result< (), InvalidCharacterError > {
80         js_try!(
81             return @{self.as_ref()}.setAttribute( @{name}, @{value} );
82         ).unwrap()
83     }
84 
85     /// Gets the the number of pixels that an element's content is scrolled vertically.
86     ///
87     /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop)
88     // https://drafts.csswg.org/cssom-view/#ref-for-dom-element-scrolltop%E2%91%A0
scroll_top( &self ) -> f6489     fn scroll_top( &self ) -> f64 {
90         js!(
91             return @{self.as_ref()}.scrollTop;
92         ).try_into().unwrap()
93     }
94 
95     /// Sets the the number of pixels that an element's content is scrolled vertically.
96     ///
97     /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop)
98     // https://drafts.csswg.org/cssom-view/#ref-for-dom-element-scrolltop%E2%91%A0
set_scroll_top( &self, value: f64 )99     fn set_scroll_top( &self, value: f64 ) {
100         js! { @(no_return)
101             @{self.as_ref()}.scrollTop = @{value};
102         }
103     }
104 
105     /// Gets the the number of pixels that an element's content is scrolled to the left.
106     ///
107     /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft)
108     // https://drafts.csswg.org/cssom-view/#ref-for-dom-element-scrollleft%E2%91%A0
scroll_left( &self ) -> f64109     fn scroll_left( &self ) -> f64 {
110         js!(
111             return @{self.as_ref()}.scrollLeft;
112         ).try_into().unwrap()
113     }
114 
115     /// Sets the the number of pixels that an element's content is scrolled to the left.
116     ///
117     /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft)
118     // https://drafts.csswg.org/cssom-view/#ref-for-dom-element-scrollleft%E2%91%A0
set_scroll_left( &self, value: f64 )119     fn set_scroll_left( &self, value: f64 ) {
120         js! { @(no_return)
121             @{self.as_ref()}.scrollLeft = @{value};
122         }
123     }
124 
125     /// Element.getAttributeNames() returns the attribute names of the element
126     /// as an Array of strings. If the element has no attributes it returns an empty array.
127     ///
128     /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttributeNames)
129     // https://dom.spec.whatwg.org/#ref-for-dom-element-getattributenames
get_attribute_names( &self ) -> Vec<String>130     fn get_attribute_names( &self ) -> Vec<String> {
131         js!(
132             return @{self.as_ref()}.getAttributeNames();
133         ).try_into().unwrap()
134     }
135 
136     /// Element.removeAttribute removes an attribute from the specified element.
137     ///
138     /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute)
139     // https://dom.spec.whatwg.org/#ref-for-dom-element-removeattribute
remove_attribute( &self, name: &str )140     fn remove_attribute( &self, name: &str ) {
141         js! { @(no_return)
142             @{self.as_ref()}.removeAttribute( @{name} );
143         }
144     }
145 
146     /// The Element.hasAttributes() method returns Boolean value, indicating if
147     /// the current element has any attributes or not.
148     ///
149     /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/hasAttributes)
150     // https://dom.spec.whatwg.org/#ref-for-dom-element-hasattributes
has_attributes( &self ) -> bool151     fn has_attributes( &self ) -> bool {
152         js!(
153             return @{self.as_ref()}.hasAttributes();
154         ).try_into().unwrap()
155     }
156 
157     /// Returns the closest ancestor of the element (or the element itself) which matches the selectors
158     /// given in parameter. If there isn't such an ancestor, it returns
159     /// `None`.
160     ///
161     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/closest)
162     // https://dom.spec.whatwg.org/#ref-for-dom-element-closest
closest( &self, selectors: &str) -> Result<Option<Element>, SyntaxError>163     fn closest( &self, selectors: &str) -> Result<Option<Element>, SyntaxError> {
164         js_try!(
165             return @{self.as_ref()}.closest(@{selectors});
166         ).unwrap()
167     }
168 
169     /// Designates a specific element as the capture target of future pointer events.
170     ///
171     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture)
172     // https://w3c.github.io/pointerevents/#dom-element-setpointercapture
173     #[inline]
set_pointer_capture( &self, pointer_id: i32 ) -> Result< (), InvalidPointerId >174     fn set_pointer_capture( &self, pointer_id: i32 ) -> Result< (), InvalidPointerId > {
175         js_try!(
176             return @{self.as_ref()}.setPointerCapture( @{pointer_id} );
177         ).unwrap()
178     }
179 
180     /// Releases pointer capture that was previously set for a specific pointer
181     ///
182     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/releasePointerCapture)
183     // https://w3c.github.io/pointerevents/#dom-element-releasepointercapture
184     #[inline]
release_pointer_capture( &self, pointer_id: i32 ) -> Result< (), InvalidPointerId >185     fn release_pointer_capture( &self, pointer_id: i32 ) -> Result< (), InvalidPointerId > {
186         js_try!(
187             return @{self.as_ref()}.releasePointerCapture( @{pointer_id} );
188         ).unwrap()
189     }
190 
191     /// Returns a boolean indicating if the element has captured the specified pointer
192     ///
193     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/hasPointerCapture)
194     // https://w3c.github.io/pointerevents/#dom-element-haspointercapture
195     #[inline]
has_pointer_capture( &self, pointer_id: i32 ) -> bool196     fn has_pointer_capture( &self, pointer_id: i32 ) -> bool {
197         js!( return @{self.as_ref()}.hasPointerCapture( @{pointer_id} ); ).try_into().unwrap()
198     }
199 
200     /// Insert nodes from HTML fragment into specified position.
201     ///
202     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML)
203     // https://w3c.github.io/DOM-Parsing/#widl-Element-insertAdjacentHTML-void-DOMString-position-DOMString-text
insert_adjacent_html( &self, position: InsertPosition, html: &str ) -> Result<(), InsertAdjacentError>204     fn insert_adjacent_html( &self, position: InsertPosition, html: &str ) -> Result<(), InsertAdjacentError> {
205         js_try!( @(no_return)
206             @{self.as_ref()}.insertAdjacentHTML( @{position.as_str()}, @{html} );
207         ).unwrap()
208     }
209 
210     /// Insert nodes from HTML fragment before element.
211     ///
212     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML)
insert_html_before( &self, html: &str ) -> Result<(), InsertAdjacentError>213     fn insert_html_before( &self, html: &str ) -> Result<(), InsertAdjacentError> {
214         self.insert_adjacent_html(InsertPosition::BeforeBegin, html)
215     }
216 
217     /// Insert nodes from HTML fragment as the first children of the element.
218     ///
219     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML)
prepend_html( &self, html: &str ) -> Result<(), InsertAdjacentError>220     fn prepend_html( &self, html: &str ) -> Result<(), InsertAdjacentError> {
221         self.insert_adjacent_html(InsertPosition::AfterBegin, html)
222     }
223 
224     /// Insert nodes from HTML fragment as the last children of the element.
225     ///
226     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML)
append_html( &self, html: &str ) -> Result<(), InsertAdjacentError>227     fn append_html( &self, html: &str ) -> Result<(), InsertAdjacentError> {
228         self.insert_adjacent_html(InsertPosition::BeforeEnd, html)
229     }
230 
231     /// Insert nodes from HTML fragment after element.
232     ///
233     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML)
insert_html_after( &self, html: &str ) -> Result<(), InsertAdjacentError>234     fn insert_html_after( &self, html: &str ) -> Result<(), InsertAdjacentError> {
235         self.insert_adjacent_html(InsertPosition::AfterEnd, html)
236     }
237 
238     /// The slot property of the Element interface returns the name of the shadow DOM
239     /// slot the element is inserted in.
240     ///
241     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/slot)
242     // https://dom.spec.whatwg.org/#ref-for-dom-element-slot
slot( &self ) -> String243     fn slot( &self ) -> String {
244         js!(
245             return @{self.as_ref()}.slot;
246         ).try_into().unwrap()
247     }
248 
249     /// Attach a shadow DOM tree to the specified element and returns a reference to its `ShadowRoot`.
250     /// It returns a shadow root if successfully attached or `None` if the element cannot be attached.
251     ///
252     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow)
253     // https://dom.spec.whatwg.org/#ref-for-dom-element-attachshadow
attach_shadow( &self, mode: ShadowRootMode ) -> Result<ShadowRoot, AttachShadowError>254     fn attach_shadow( &self, mode: ShadowRootMode ) -> Result<ShadowRoot, AttachShadowError> {
255         js_try!(
256             return @{self.as_ref()}.attachShadow( { mode: @{mode.as_str()}} )
257         ).unwrap()
258     }
259 
260     /// Returns the shadow root of the current element or `None`.
261     ///
262     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/shadowRoot)
263     // https://dom.spec.whatwg.org/#ref-for-dom-element-shadowroot
shadow_root( &self ) -> Option<ShadowRoot>264     fn shadow_root( &self ) -> Option<ShadowRoot> {
265         unsafe {
266             js!(
267                 return @{self.as_ref()}.shadowRoot;
268             ).into_reference_unchecked()
269         }
270     }
271 
272     /// Request this element and its children be made fullscreen
273     ///
274     /// Note: this may only be called during a user interaction.
275     /// Not all elements may be full-screened, see JS docs for details.
276     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen)
277     // https://fullscreen.spec.whatwg.org/#ref-for-dom-element-requestfullscreen
278     #[cfg(feature = "experimental_features_which_may_break_on_minor_version_bumps")]
request_fullscreen( &self ) -> TypedPromise<(), TypeError>279     fn request_fullscreen( &self ) -> TypedPromise<(), TypeError> {
280         let promise: Promise = js!( return @{self.as_ref()}.requestFullscreen(); )
281             .try_into().unwrap();
282 
283         TypedPromise::new( promise )
284     }
285 }
286 
287 
288 /// A reference to a JavaScript object which implements the [IElement](trait.IElement.html)
289 /// interface.
290 ///
291 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element)
292 // https://dom.spec.whatwg.org/#element
293 #[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
294 #[reference(instance_of = "Element")]
295 #[reference(subclass_of(EventTarget, Node))]
296 pub struct Element( Reference );
297 
298 impl IEventTarget for Element {}
299 impl INode for Element {}
300 impl IElement for Element {}
301 
302 impl< T: IElement > IParentNode for T {}
303 impl< T: IElement > IChildNode for T {}
304 impl< T: IElement > ISlotable for T {}
305 
306 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
307 pub enum InsertPosition {
308     /// Insert into the parent directly before the reference element.
309     BeforeBegin,
310     /// Insert at the start of the reference element.
311     AfterBegin,
312     /// Insert at the end of the reference element.
313     BeforeEnd,
314     /// Insert into the parent directly after the reference element.
315     AfterEnd,
316 }
317 
318 /// Errors thrown by `Element::insert_adjacent_html`.
319 error_enum_boilerplate! {
320     InsertAdjacentError,
321     NoModificationAllowedError, SyntaxError
322 }
323 
324 impl InsertPosition {
as_str(&self) -> &str325     fn as_str(&self) -> &str {
326         match *self {
327             InsertPosition::BeforeBegin => "beforebegin",
328             InsertPosition::AfterBegin => "afterbegin",
329             InsertPosition::BeforeEnd => "beforeend",
330             InsertPosition::AfterEnd => "afterend",
331         }
332     }
333 }
334 
335 #[cfg(all(test, feature = "web_test"))]
336 mod tests {
337     use super::*;
338     use webapi::document::document;
339     use webapi::shadow_root::ShadowRootMode;
340 
div() -> Element341     fn div() -> Element {
342         js!(
343             return document.createElement("div");
344         ).try_into().unwrap()
345     }
346 
h1() -> Element347     fn h1() -> Element {
348         js!(
349             return document.createElement("h1");
350         ).try_into().unwrap()
351     }
352 
353     #[test]
test_closest_finds_ancestor()354     fn test_closest_finds_ancestor() {
355         let parent = div();
356         let child = h1();
357         parent.append_child(&child);
358 
359         assert_eq!(child.closest("div").unwrap().unwrap().as_ref(), parent.as_ref());
360     }
361 
362     #[test]
test_closest_not_found()363     fn test_closest_not_found() {
364         let parent = div();
365         let child = h1();
366         parent.append_child(&child);
367 
368         assert!(child.closest("p").unwrap().is_none());
369     }
370 
371     #[test]
test_closest_syntax_error()372     fn test_closest_syntax_error() {
373         let parent = div();
374         let child = div();
375         parent.append_child(&child);
376 
377         assert!(child.closest("invalid syntax +#8$()@!(#").is_err());
378     }
379 
380     #[test]
insert_adjacent_html()381     fn insert_adjacent_html() {
382         let root = document().create_element("div").unwrap();
383         let child = document().create_element("span").unwrap();
384         child.set_text_content("child");
385         root.append_child(&child);
386 
387         child.insert_html_before(" <button>before begin</button> foo ").unwrap();
388         child.prepend_html("<i>afterbegin").unwrap();
389         child.append_html("<h1> Before end</h1>").unwrap();
390         child.insert_html_after("after end ").unwrap();
391 
392         let html = js!(return @{root}.innerHTML);
393         assert_eq!(html, " <button>before begin</button> foo <span><i>afterbegin</i>child<h1> Before end</h1></span>after end ");
394     }
395 
396     #[test]
insert_adjacent_html_empty()397     fn insert_adjacent_html_empty() {
398         let root = document().create_element("div").unwrap();
399         root.append_html("").unwrap();
400 
401         let html = js!(return @{root}.innerHTML);
402         assert_eq!(html, "");
403     }
404 
405     #[test]
insert_adjacent_html_not_modifiable()406     fn insert_adjacent_html_not_modifiable() {
407         let doc = document().document_element().unwrap();
408         assert!(match doc.insert_html_before("foobar").unwrap_err() {
409             InsertAdjacentError::NoModificationAllowedError(_) => true,
410             _ => false,
411         });
412     }
413 
414     #[test]
test_attach_shadow_mode_open()415     fn test_attach_shadow_mode_open() {
416         let element = document().create_element("div").unwrap();
417         let shadow_root = element.attach_shadow(ShadowRootMode::Open).unwrap();
418         assert_eq!(shadow_root.mode(), ShadowRootMode::Open);
419         assert_eq!(element.shadow_root(), Some(shadow_root));
420     }
421 
422     #[test]
test_attach_shadow_mode_closed()423     fn test_attach_shadow_mode_closed() {
424         let element = document().create_element("div").unwrap();
425         let shadow_root = element.attach_shadow(ShadowRootMode::Closed).unwrap();
426         assert_eq!(shadow_root.mode(), ShadowRootMode::Closed);
427         assert!(element.shadow_root().is_none());
428     }
429 }
430