1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use wasm_bindgen::UnwrapThrowExt;

pub mod window {
    use js_sys::Function;
    use wasm_bindgen::{JsValue, UnwrapThrowExt};

    use super::WINDOW;

    pub fn request_animation_frame(callback: &::js_sys::Function) {
        WINDOW.with(|win| win.request_animation_frame(callback).unwrap_throw());
    }

    pub fn history() -> web_sys::History {
        WINDOW.with(|win| win.history().unwrap_throw())
    }

    pub fn location() -> web_sys::Location {
        WINDOW.with(|win| win.location())
    }

    pub fn set_onpopstate(value: Option<&Function>) {
        WINDOW.with(|win| win.set_onpopstate(value));
    }

    pub fn local_storage() -> Result<web_sys::Storage, JsValue> {
        WINDOW.with(|w| w.local_storage().map(|w| w.unwrap_throw()))
    }

    pub fn session_storage() -> Result<web_sys::Storage, JsValue> {
        WINDOW.with(|w| w.session_storage().map(|w| w.unwrap_throw()))
    }

    pub fn performance() -> Option<web_sys::Performance> {
        WINDOW.with(|w| w.performance())
    }
}

pub mod document {
    use wasm_bindgen::{JsCast, JsValue, UnwrapThrowExt};
    use web_sys::HtmlBaseElement;

    use super::DOCUMENT;

    pub fn get_element_by_id(id: &str) -> Option<web_sys::Element> {
        DOCUMENT.with(|doc| doc.get_element_by_id(id))
    }

    pub fn create_element(tag: &str) -> web_sys::Element {
        DOCUMENT.with(|doc| doc.create_element(tag).unwrap_throw())
    }

    pub fn create_element_ns(namespace: &str, tag: &str) -> web_sys::Element {
        DOCUMENT.with(|doc| doc.create_element_ns(Some(namespace), tag).unwrap_throw())
    }

    pub fn create_text_node(text: &str) -> web_sys::Text {
        DOCUMENT.with(|doc| doc.create_text_node(text))
    }

    pub fn base_uri() -> Option<String> {
        DOCUMENT
            .with(|doc| doc.query_selector("base[href]"))
            .unwrap_throw()
            .map(|base_elem| {
                base_elem
                    .dyn_into::<HtmlBaseElement>()
                    .unwrap_throw()
                    .href()
            })
    }

    pub fn query_selector(selectors: &str) -> Result<Option<web_sys::Element>, JsValue> {
        DOCUMENT.with(|doc| doc.query_selector(selectors))
    }

    pub fn head() -> Option<web_sys::HtmlHeadElement> {
        DOCUMENT.with(|doc| doc.head())
    }

    pub fn body() -> Option<web_sys::HtmlElement> {
        DOCUMENT.with(|doc| doc.body())
    }
}

pub trait GlobalEventTarget {
    fn add_event_listener_with_callback(name: &'static str, listener: &::js_sys::Function);

    fn remove_event_listener_with_callback(name: &'static str, listener: &::js_sys::Function);
}

pub struct Document;

impl GlobalEventTarget for Document {
    fn add_event_listener_with_callback(name: &'static str, listener: &::js_sys::Function) {
        DOCUMENT.with(|doc| {
            doc.add_event_listener_with_callback(name, listener)
                .unwrap_throw()
        })
    }

    fn remove_event_listener_with_callback(name: &'static str, listener: &::js_sys::Function) {
        DOCUMENT.with(|doc| {
            doc.remove_event_listener_with_callback(name, listener)
                .unwrap_throw()
        })
    }
}

pub struct Window;

impl GlobalEventTarget for Window {
    fn add_event_listener_with_callback(name: &'static str, listener: &::js_sys::Function) {
        WINDOW.with(|win| {
            win.add_event_listener_with_callback(name, listener)
                .unwrap_throw()
        })
    }

    fn remove_event_listener_with_callback(name: &'static str, listener: &::js_sys::Function) {
        WINDOW.with(|win| {
            win.remove_event_listener_with_callback(name, listener)
                .unwrap_throw()
        })
    }
}

thread_local!(
    static WINDOW: web_sys::Window = web_sys::window().expect_throw("Window must be available");
    static DOCUMENT: web_sys::Document = WINDOW.with(|win| {
        win.document()
            .expect_throw("Window must contain a document")
    });
);