1use crate::result::*;
12use crate::utils::*;
13use js_sys::{Array, Function, Uint8Array};
14use web_sys::Element;
15use web_sys::{Blob, Url};
16use workflow_core::channel::oneshot;
17use workflow_wasm::callback::*;
18
19pub type CustomEventCallback = Callback<CallbackClosureWithoutResult<web_sys::CustomEvent>>;
22
23pub enum Content<'content> {
26 Script(Option<&'content str>, &'content [u8]),
28 Module(Option<&'content str>, &'content [u8]),
30 Style(Option<&'content str>, &'content [u8]),
32}
33
34pub fn inject_css(id: Option<&str>, css: &str) -> Result<()> {
38 let doc = document();
39 let head = doc
40 .get_elements_by_tag_name("head")
41 .item(0)
42 .ok_or("Unable to locate head element")?;
43
44 let style_el = if let Some(id) = id {
45 match doc.get_element_by_id(id) {
46 Some(old_el) => old_el,
47 _ => {
48 let style_el = doc.create_element("style")?;
49 style_el.set_attribute("id", id)?;
50 head.append_child(&style_el)?;
51 style_el
52 }
53 }
54 } else {
55 let style_el = doc.create_element("style")?;
56 head.append_child(&style_el)?;
57 style_el
58 };
59
60 style_el.set_inner_html(css);
61 Ok(())
62}
63
64pub fn inject_blob_nowait(content: Content) -> Result<()> {
68 inject_blob_with_callback::<CustomEventCallback>(content, None)
69}
70
71pub async fn inject_blob(content: Content<'_>) -> Result<()> {
76 let (sender, receiver) = oneshot();
77 let callback = callback!(move |event: web_sys::CustomEvent| {
78 sender
79 .try_send(event)
80 .expect("inject_blob_with_callback(): unable to send load notification");
81 });
82 inject_blob_with_callback(content, Some(&callback))?;
83 let _notification = receiver.recv().await?;
84 Ok(())
85}
86
87pub fn inject_script<C>(
92 root: Element,
93 id: Option<&str>,
94 content: &[u8],
95 content_type: &str,
96 callback: Option<&C>,
97) -> Result<()>
98where
99 C: AsRef<Function>,
100{
101 let doc = document();
102 let string = String::from_utf8_lossy(content);
103 let regex = regex::Regex::new(r"//# sourceMappingURL.*$").unwrap();
104 let content = regex.replace(&string, "");
105
106 let args = Array::new_with_length(1);
107 args.set(0, unsafe { Uint8Array::view(content.as_bytes()).into() });
108 let options = web_sys::BlobPropertyBag::new();
109 options.set_type("application/javascript");
110 let blob = Blob::new_with_u8_array_sequence_and_options(&args, &options)?;
111 let url = Url::create_object_url_with_blob(&blob)?;
112
113 let script = doc.create_element("script")?;
114 if let Some(callback) = callback {
115 script.add_event_listener_with_callback("load", callback.as_ref())?;
116 }
117 if let Some(id) = id {
118 script.set_attribute("id", id)?;
119 }
120 script.set_attribute("type", content_type)?;
121 script.set_attribute("src", &url)?;
122 root.append_child(&script)?;
123
124 Ok(())
125}
126
127pub fn inject_stylesheet<C>(
131 root: Element,
132 id: Option<&str>,
133 content: &[u8],
134 callback: Option<&C>,
135) -> Result<()>
136where
137 C: AsRef<Function>,
138{
139 let args = Array::new_with_length(1);
140 args.set(0, unsafe { Uint8Array::view(content).into() });
141 let blob = Blob::new_with_u8_array_sequence(&args)?;
142 let url = Url::create_object_url_with_blob(&blob)?;
143
144 let style = document().create_element("link")?;
145 if let Some(callback) = callback {
146 style.add_event_listener_with_callback("load", callback.as_ref())?;
147 }
149 if let Some(id) = id {
150 style.set_attribute("id", id)?;
151 }
152 style.set_attribute("type", "text/css")?;
153 style.set_attribute("rel", "stylesheet")?;
154 style.set_attribute("href", &url)?;
155 root.append_child(&style)?;
156 Ok(())
157}
158
159pub fn inject_blob_with_callback<C>(content: Content, callback: Option<&C>) -> Result<()>
163where
165 C: AsRef<Function>,
166{
167 let doc = document();
168 let root = {
169 let collection = doc.get_elements_by_tag_name("head");
170 if collection.length() > 0 {
171 collection.item(0).unwrap()
172 } else {
173 doc.get_elements_by_tag_name("body").item(0).unwrap()
174 }
175 };
176
177 match content {
178 Content::Script(id, content) => {
179 inject_script(root, id, content, "text/javascript", callback)?;
180 }
181 Content::Module(id, content) => {
182 inject_script(root, id, content, "module", callback)?;
183 }
184 Content::Style(id, content) => {
185 inject_stylesheet(root, id, content, callback)?;
186 }
187 }
188
189 Ok(())
190}