Skip to content

Commit 85e4e82

Browse files
futursolokoute
authored andcommitted
Shadow DOM and web components related APIs (#295)
1 parent 21edc80 commit 85e4e82

17 files changed

+590
-11
lines changed

ci/run_tests.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
set -euo pipefail
44
IFS=$'\n\t'
55

6+
export RUST_BACKTRACE=1
7+
68
CARGO_WEB=${CARGO_WEB:-cargo-web}
79
SKIP_RUNTIME_COMPATIBILITY_CHECK=${SKIP_RUNTIME_COMPATIBILITY_CHECK:-0}
810

src/lib.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ pub mod web {
248248
pub use webapi::html_element::{IHtmlElement, HtmlElement, Rect};
249249
pub use webapi::window_or_worker::IWindowOrWorker;
250250
pub use webapi::parent_node::IParentNode;
251+
pub use webapi::slotable::ISlotable;
251252
pub use webapi::non_element_parent_node::INonElementParentNode;
252253
pub use webapi::token_list::TokenList;
253254
pub use webapi::node_list::NodeList;
@@ -268,6 +269,8 @@ pub mod web {
268269
pub use webapi::child_node::IChildNode;
269270
pub use webapi::gamepad::{Gamepad, GamepadButton, GamepadMappingType};
270271
pub use webapi::selection::Selection;
272+
pub use webapi::shadow_root::{ShadowRootMode, ShadowRoot};
273+
pub use webapi::html_elements::SlotContentKind;
271274

272275
/// A module containing error types.
273276
pub mod error {
@@ -304,6 +307,8 @@ pub mod web {
304307
pub use webapi::html_elements::CanvasElement;
305308
pub use webapi::html_elements::SelectElement;
306309
pub use webapi::html_elements::OptionElement;
310+
pub use webapi::html_elements::TemplateElement;
311+
pub use webapi::html_elements::SlotElement;
307312
}
308313

309314
/// A module containing JavaScript DOM events.
@@ -427,6 +432,8 @@ pub mod web {
427432
DataTransferItem,
428433
DataTransferItemKind,
429434
};
435+
436+
pub use webapi::events::slot::SlotChangeEvent;
430437
}
431438

432439
#[cfg(feature = "experimental_features_which_may_break_on_minor_version_bumps")]
@@ -471,7 +478,8 @@ pub mod traits {
471478
IWindowOrWorker,
472479
IParentNode,
473480
INonElementParentNode,
474-
IChildNode
481+
IChildNode,
482+
ISlotable,
475483
};
476484

477485
#[doc(hidden)]

src/webapi/document.rs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
use webcore::value::{Reference, Value};
22
use webcore::try_from::{TryInto, TryFrom};
33
use webapi::event_target::{IEventTarget, EventTarget};
4-
use webapi::node::{INode, Node};
4+
use webapi::node::{INode, Node, CloneKind};
55
use webapi::element::Element;
66
use webapi::html_element::HtmlElement;
77
use webapi::document_fragment::DocumentFragment;
88
use webapi::text_node::TextNode;
99
use webapi::location::Location;
1010
use webapi::parent_node::IParentNode;
1111
use webapi::non_element_parent_node::INonElementParentNode;
12-
use webapi::dom_exception::{InvalidCharacterError, NamespaceError};
12+
use webapi::dom_exception::{InvalidCharacterError, NamespaceError, NotSupportedError};
1313

1414
/// The `Document` interface represents any web page loaded in the browser and
1515
/// serves as an entry point into the web page's content, which is the DOM tree.
@@ -181,12 +181,30 @@ impl Document {
181181
@{self}.exitPointerLock();
182182
);
183183
}
184+
185+
/// Import node from another document
186+
///
187+
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Document/importNode)
188+
// https://dom.spec.whatwg.org/#ref-for-dom-document-importnode
189+
pub fn import_node<N: INode>( &self, n: &N, kind: CloneKind ) -> Result<Node, NotSupportedError> {
190+
let deep = match kind {
191+
CloneKind::Deep => true,
192+
CloneKind::Shallow => false,
193+
};
194+
195+
js_try!(
196+
return @{self}.importNode( @{n.as_ref()}, @{deep} );
197+
).unwrap()
198+
}
184199
}
185200

186201

187202
#[cfg(all(test, feature = "web_test"))]
188203
mod web_tests {
189204
use super::*;
205+
use webapi::node::{Node, INode, CloneKind};
206+
use webapi::html_elements::TemplateElement;
207+
use webapi::html_element::HtmlElement;
190208

191209
#[test]
192210
fn test_create_element_invalid_character() {
@@ -211,4 +229,22 @@ mod web_tests {
211229
v => panic!("expected NamespaceError, got {:?}", v),
212230
}
213231
}
214-
}
232+
233+
#[test]
234+
fn test_import_node() {
235+
let document = document();
236+
let tpl: TemplateElement = Node::from_html("<template><span>aaabbbcccddd</span></template>")
237+
.unwrap()
238+
.try_into()
239+
.unwrap();
240+
241+
let n = document.import_node(&tpl.content(), CloneKind::Deep).unwrap();
242+
let child_nodes = n.child_nodes();
243+
assert_eq!(child_nodes.len(), 1);
244+
245+
let span_element: HtmlElement = child_nodes.iter().next().unwrap().try_into().unwrap();
246+
247+
assert_eq!(span_element.node_name(), "SPAN");
248+
assert_eq!(js!( return @{span_element}.innerHTML; ), "aaabbbcccddd");
249+
}
250+
}

src/webapi/document_fragment.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use webcore::value::Reference;
22
use webapi::event_target::{IEventTarget, EventTarget};
33
use webapi::node::{INode, Node};
4+
use webapi::parent_node::IParentNode;
45

56
/// A reference to a JavaScript object DocumentFragment.
67
///
@@ -12,4 +13,5 @@ use webapi::node::{INode, Node};
1213
pub struct DocumentFragment( Reference );
1314

1415
impl IEventTarget for DocumentFragment {}
15-
impl INode for DocumentFragment {}
16+
impl INode for DocumentFragment {}
17+
impl IParentNode for DocumentFragment {}

src/webapi/element.rs

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
use webcore::value::Reference;
2-
use webcore::try_from::TryInto;
2+
use webcore::try_from::{TryFrom, TryInto};
33
use webapi::dom_exception::{InvalidCharacterError, InvalidPointerId, NoModificationAllowedError, SyntaxError};
44
use webapi::event_target::{IEventTarget, EventTarget};
55
use webapi::node::{INode, Node};
66
use webapi::token_list::TokenList;
77
use webapi::parent_node::IParentNode;
88
use webapi::child_node::IChildNode;
9-
use webcore::try_from::TryFrom;
9+
use webapi::slotable::ISlotable;
10+
use webapi::shadow_root::{ShadowRootMode, ShadowRoot};
11+
use webapi::dom_exception::{NotSupportedError, InvalidStateError};
12+
13+
error_enum_boilerplate! {
14+
AttachShadowError,
15+
NotSupportedError, InvalidStateError
16+
}
1017

1118
/// The `IElement` interface represents an object of a [Document](struct.Document.html).
1219
/// This interface describes methods and properties common to all
1320
/// kinds of elements.
1421
///
1522
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element)
1623
// https://dom.spec.whatwg.org/#element
17-
pub trait IElement: INode + IParentNode + IChildNode {
24+
pub trait IElement: INode + IParentNode + IChildNode + ISlotable {
1825
/// The Element.namespaceURI read-only property returns the namespace URI
1926
/// of the element, or null if the element is not in a namespace.
2027
///
@@ -225,6 +232,40 @@ pub trait IElement: INode + IParentNode + IChildNode {
225232
fn insert_html_after( &self, html: &str ) -> Result<(), InsertAdjacentError> {
226233
self.insert_adjacent_html(InsertPosition::AfterEnd, html)
227234
}
235+
236+
/// The slot property of the Element interface returns the name of the shadow DOM
237+
/// slot the element is inserted in.
238+
///
239+
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/slot)
240+
// https://dom.spec.whatwg.org/#ref-for-dom-element-slot
241+
fn slot( &self ) -> String {
242+
js!(
243+
return @{self.as_ref()}.slot;
244+
).try_into().unwrap()
245+
}
246+
247+
/// Attach a shadow DOM tree to the specified element and returns a reference to its `ShadowRoot`.
248+
/// It returns a shadow root if successfully attached or `None` if the element cannot be attached.
249+
///
250+
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow)
251+
// https://dom.spec.whatwg.org/#ref-for-dom-element-attachshadow
252+
fn attach_shadow( &self, mode: ShadowRootMode ) -> Result<ShadowRoot, AttachShadowError> {
253+
js_try!(
254+
return @{self.as_ref()}.attachShadow( { mode: @{mode.as_str()}} )
255+
).unwrap()
256+
}
257+
258+
/// Returns the shadow root of the current element or `None`.
259+
///
260+
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/shadowRoot)
261+
// https://dom.spec.whatwg.org/#ref-for-dom-element-shadowroot
262+
fn shadow_root( &self ) -> Option<ShadowRoot> {
263+
unsafe {
264+
js!(
265+
return @{self.as_ref()}.shadowRoot;
266+
).into_reference_unchecked()
267+
}
268+
}
228269
}
229270

230271

@@ -244,6 +285,7 @@ impl IElement for Element {}
244285

245286
impl< T: IElement > IParentNode for T {}
246287
impl< T: IElement > IChildNode for T {}
288+
impl< T: IElement > ISlotable for T {}
247289

248290
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
249291
pub enum InsertPosition {
@@ -278,6 +320,7 @@ impl InsertPosition {
278320
mod tests {
279321
use super::*;
280322
use webapi::document::document;
323+
use webapi::shadow_root::ShadowRootMode;
281324

282325
fn div() -> Element {
283326
js!(
@@ -351,4 +394,20 @@ mod tests {
351394
_ => false,
352395
});
353396
}
397+
398+
#[test]
399+
fn test_attach_shadow_mode_open() {
400+
let element = document().create_element("div").unwrap();
401+
let shadow_root = element.attach_shadow(ShadowRootMode::Open).unwrap();
402+
assert_eq!(shadow_root.mode(), ShadowRootMode::Open);
403+
assert_eq!(element.shadow_root(), Some(shadow_root));
404+
}
405+
406+
#[test]
407+
fn test_attach_shadow_mode_closed() {
408+
let element = document().create_element("div").unwrap();
409+
let shadow_root = element.attach_shadow(ShadowRootMode::Closed).unwrap();
410+
assert_eq!(shadow_root.mode(), ShadowRootMode::Closed);
411+
assert!(element.shadow_root().is_none());
412+
}
354413
}

src/webapi/events/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ pub mod mouse;
88
pub mod pointer;
99
pub mod progress;
1010
pub mod socket;
11+
pub mod slot;

src/webapi/events/slot.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use webcore::value::Reference;
2+
use webapi::event::{IEvent, Event};
3+
4+
/// The `slotchange` event is fired on an HTMLSlotElement instance
5+
/// (`<slot>` element) when the node(s) contained in that slot change.
6+
///
7+
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/Events/slotchange)
8+
// https://dom.spec.whatwg.org/#mutation-observers
9+
#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
10+
#[reference(instance_of = "Event")]
11+
#[reference(event = "slotchange")]
12+
#[reference(subclass_of(Event))]
13+
pub struct SlotChangeEvent( Reference );
14+
15+
impl IEvent for SlotChangeEvent {}

src/webapi/html_elements/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ mod input;
44
mod textarea;
55
mod select;
66
mod option;
7+
mod template;
8+
mod slot;
79

810
pub use self::canvas::CanvasElement;
911
pub use self::image::ImageElement;
1012
pub use self::input::InputElement;
1113
pub use self::textarea::TextAreaElement;
1214
pub use self::select::SelectElement;
1315
pub use self::option::OptionElement;
16+
pub use self::template::TemplateElement;
17+
pub use self::slot::{SlotElement, SlotContentKind};
1418

15-
pub use self::select::UnknownValueError;
19+
pub use self::select::UnknownValueError;

src/webapi/html_elements/option.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,4 @@ impl OptionElement {
3939
return @{self}.value;
4040
).try_into().unwrap()
4141
}
42-
}
42+
}

src/webapi/html_elements/select.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,4 +228,4 @@ mod tests{
228228
assert_eq!(se.selected_indices(), vec![0,2,4]);
229229
assert_eq!(se.selected_values(), vec!["first".to_string(), "third".to_string(), "".to_string()]);
230230
}
231-
}
231+
}

0 commit comments

Comments
 (0)