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 if (customElements.get(name)) {
20 console.warn(`Custom element "${name}" is already registered; skipping duplicate registration (Perspective was loaded more than once on this page).`);
21 return;
22 }
23
24 const cls = psp[clsname];
25 const proto = cls.prototype;
26 class x extends HTMLElement {
27 constructor() {
28 super();
29 this._instance = new cls(this);
30 }
31 }
32
33 const names = Object.getOwnPropertyNames(proto);
34 for (const key of names) {
35 if ('get' in Object.getOwnPropertyDescriptor(proto, key)) {
36 Object.defineProperty(x.prototype, key, {
37 get: function() {
38 return this._instance[key];
39 }
40 });
41 } else {
42 Object.defineProperty(x.prototype, key, {
43 value: function(...args) {
44 return this._instance[key].call(this._instance, ...args);
45 }
46 });
47 }
48 }
49
50 for (const key of statics) {
51 Object.defineProperty(x, key, {
52 value: function(...args) {
53 return psp[key].call(psp, ...args);
54 }
55 });
56 }
57
58 Object.defineProperty(x, '__wasm_module__', {
59 get() {
60 return psp;
61 },
62 });
63
64 customElements.define(name, x);
65 }
66"#)]
67extern "C" {
68 #[wasm_bindgen(js_name = "bootstrap")]
69 fn js_bootstrap(psp: &JsValue, name: &str, cls: &str, statics: js_sys::Array) -> JsValue;
70}
71
72/// A trait which allows the [`define_web_component`] method to create a
73/// Custom Element (which must by definition inherit from `HTMLElement`) from
74/// a `[wasm_bindgen]` annotated Rust struct (for which this trait is
75/// implemented).
76pub trait CustomElementMetadata {
77 /// The name of the element to register.
78 const CUSTOM_ELEMENT_NAME: &'static str;
79
80 /// [optional] The names of the methods which should be static on the
81 /// element.
82 const STATICS: &'static [&'static str] = [].as_slice();
83
84 /// [optional] The name of the Rust struct.
85 const TYPE_NAME: &'static str = std::any::type_name::<Self>();
86
87 /// [optional] A custom implementation of struct name to class name.
88 #[must_use]
89 fn struct_name() -> &'static str {
90 match &Self::TYPE_NAME.rfind(':') {
91 Some(pos) => &Self::TYPE_NAME[pos + 1..],
92 None => Self::TYPE_NAME,
93 }
94 }
95}
96
97/// Register the Custom Element globally.
98pub fn define_web_component<T: CustomElementMetadata>(module: &JsValue) {
99 js_bootstrap(
100 module,
101 T::CUSTOM_ELEMENT_NAME,
102 T::struct_name(),
103 T::STATICS.iter().cloned().map(JsValue::from).collect(),
104 );
105}