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