stdweb/webapi/
element.rs

1use webcore::value::Reference;
2use webcore::try_from::{TryFrom, TryInto};
3use webcore::promise::{Promise, TypedPromise};
4use webapi::error::TypeError;
5use webapi::dom_exception::{InvalidCharacterError, InvalidPointerId, NoModificationAllowedError, SyntaxError};
6use webapi::event_target::{IEventTarget, EventTarget};
7use webapi::node::{INode, Node};
8use webapi::token_list::TokenList;
9use webapi::parent_node::IParentNode;
10use webapi::child_node::IChildNode;
11use webapi::slotable::ISlotable;
12use webapi::shadow_root::{ShadowRootMode, ShadowRoot};
13use webapi::dom_exception::{NotSupportedError, InvalidStateError};
14
15error_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
26pub 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
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
44    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
55    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
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
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
89    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
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
109    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
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
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
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
151    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
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]
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]
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]
196    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
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)
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)
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)
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)
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
243    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
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
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")]
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))]
296pub struct Element( Reference );
297
298impl IEventTarget for Element {}
299impl INode for Element {}
300impl IElement for Element {}
301
302impl< T: IElement > IParentNode for T {}
303impl< T: IElement > IChildNode for T {}
304impl< T: IElement > ISlotable for T {}
305
306#[derive(Copy, Clone, PartialEq, Eq, Debug)]
307pub 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`.
319error_enum_boilerplate! {
320    InsertAdjacentError,
321    NoModificationAllowedError, SyntaxError
322}
323
324impl InsertPosition {
325    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"))]
336mod tests {
337    use super::*;
338    use webapi::document::document;
339    use webapi::shadow_root::ShadowRootMode;
340
341    fn div() -> Element {
342        js!(
343            return document.createElement("div");
344        ).try_into().unwrap()
345    }
346
347    fn h1() -> Element {
348        js!(
349            return document.createElement("h1");
350        ).try_into().unwrap()
351    }
352
353    #[test]
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]
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]
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]
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]
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]
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]
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]
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}