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}