perspective_viewer/utils/custom_element.rs
1// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7// ┃ Copyright (c) 2017, the Perspective Authors. ┃
8// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9// ┃ This file is part of the Perspective library, distributed under the terms ┃
10// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
13//! Utilities for building JavaScript Custom Elements with Rust.
14
15use wasm_bindgen::prelude::*;
16
17#[wasm_bindgen(inline_js = r#"
18 export function bootstrap(psp, name, clsname, statics) {
19 const cls = psp[clsname];
20 const proto = cls.prototype;
21 class x extends HTMLElement {
22 constructor() {
23 super();
24 this._instance = new cls(this);
25 }
26 }
27
28 const names = Object.getOwnPropertyNames(proto);
29 for (const key of names) {
30 if ('get' in Object.getOwnPropertyDescriptor(proto, key)) {
31 Object.defineProperty(x.prototype, key, {
32 get: function() {
33 return this._instance[key];
34 }
35 });
36 } else {
37 Object.defineProperty(x.prototype, key, {
38 value: function(...args) {
39 return this._instance[key].call(this._instance, ...args);
40 }
41 });
42 }
43 }
44
45 for (const key of statics) {
46 Object.defineProperty(x, key, {
47 value: function(...args) {
48 return psp[key].call(psp, ...args);
49 }
50 });
51 }
52
53 Object.defineProperty(x, '__wasm_module__', {
54 get() {
55 return psp;
56 },
57 });
58
59 customElements.define(name, x);
60 }
61"#)]
62extern "C" {
63 #[wasm_bindgen(js_name = "bootstrap")]
64 fn js_bootstrap(psp: &JsValue, name: &str, cls: &str, statics: js_sys::Array) -> JsValue;
65}
66
67/// A trait which allows the [`define_web_component`] method to create a
68/// Custom Element (which must by definition inherit from `HTMLElement`) from
69/// a `[wasm_bindgen]` annotated Rust struct (for which this trait is
70/// implemented).
71pub trait CustomElementMetadata {
72 /// The name of the element to register.
73 const CUSTOM_ELEMENT_NAME: &'static str;
74
75 /// [optional] The names of the methods which should be static on the
76 /// element.
77 const STATICS: &'static [&'static str] = [].as_slice();
78
79 /// [optional] The name of the Rust struct.
80 const TYPE_NAME: &'static str = std::any::type_name::<Self>();
81
82 /// [optional] A custom implementation of struct name to class name.
83 #[must_use]
84 fn struct_name() -> &'static str {
85 match &Self::TYPE_NAME.rfind(':') {
86 Some(pos) => &Self::TYPE_NAME[pos + 1..],
87 None => Self::TYPE_NAME,
88 }
89 }
90}
91
92/// Register the Custom Element globally.
93pub fn define_web_component<T: CustomElementMetadata>(module: &JsValue) {
94 js_bootstrap(
95 module,
96 T::CUSTOM_ELEMENT_NAME,
97 T::struct_name(),
98 T::STATICS.iter().cloned().map(JsValue::from).collect(),
99 );
100}