1use crate::error::Error;
2use crate::result::Result;
3use futures::future::{join_all, BoxFuture, FutureExt};
4use js_sys::{Array, Uint8Array};
5use std::collections::HashMap;
6use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
7use std::sync::Arc;
8use std::sync::Mutex;
9use web_sys::{Blob, Document, Url};
10use workflow_core::channel::oneshot;
11use workflow_core::lookup::*;
12use workflow_core::time::*;
13use workflow_log::*;
14use workflow_wasm::callback::*;
15
16pub type Id = u64;
17pub type ContentMap = HashMap<Id, Arc<Content>>;
18pub type ContentList<'l> = &'l [(Id, Arc<Content>)];
19
20static mut DOCUMENT_ROOT: Option<web_sys::Element> = None;
21
22pub fn document() -> Document {
23 web_sys::window().unwrap().document().unwrap()
24}
25
26pub fn root() -> web_sys::Element {
27 unsafe {
28 match DOCUMENT_ROOT.as_ref() {
29 Some(root) => root.clone(),
30 None => {
31 let root = {
32 let collection = document().get_elements_by_tag_name("head");
33 if collection.length() > 0 {
34 collection.item(0).unwrap()
35 } else {
36 document().get_elements_by_tag_name("body").item(0).unwrap()
37 }
38 };
39 DOCUMENT_ROOT = Some(root.clone());
40 root
41 }
42 }
43 }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub enum ContentType {
48 Module,
49 Script,
50 Style,
51}
52
53impl ContentType {
54 pub fn is_js(&self) -> bool {
55 self == &ContentType::Script || self == &ContentType::Module
56 }
57}
58
59#[allow(dead_code)]
60pub enum Reference {
61 Module,
62 Script,
63 Style,
64 Export,
65}
66
67#[allow(dead_code)]
68#[derive(Debug, Clone)]
69pub enum ContentStatus {
70 Loaded,
71 Exists,
72 Error,
73}
74
75pub struct Content {
76 pub content_type: ContentType,
77 pub url: Mutex<Option<String>>,
78 pub id: Id,
79 pub ident: &'static str,
80 pub content: &'static str,
81 pub references: Option<&'static [(Reference, Option<&'static str>, Id)]>,
82 pub is_loaded: AtomicBool,
83}
84
85impl Content {
89 pub fn url(&self) -> Option<String> {
90 self.url.lock().unwrap().clone()
91 }
92
93 fn content(&self, ctx: &Context) -> Result<String> {
95 let mut text = String::new();
96
97 if let Some(references) = &self.references {
98 let mut imports = Vec::new();
99 let mut exports = Vec::new();
100
101 for (kind, what, id) in references.iter() {
102 let module = ctx
103 .get(id)
104 .ok_or(format!("unable to lookup module `{}`", self.ident))?;
105 let url = module
106 .url()
107 .ok_or(format!("[{}] module is not loaded `{}`", self.ident, id))?;
108 match kind {
109 Reference::Module => match what {
110 Some(detail) => {
111 imports.push(format!("import {detail} from \"{url}\";"));
112 }
113 None => {
114 imports.push(format!("import \"{url}\";"));
115 }
116 },
117 Reference::Export => {
118 let module = ctx
119 .get(id)
120 .ok_or(format!("unable to lookup module `{}`", self.ident))?;
121 let url = module
122 .url()
123 .ok_or(format!("[{}] module is not loaded `{}`", self.ident, id))?;
124 exports.push(format!("export {} from \"{}\";", what.unwrap(), url));
125 }
126 _ => {}
127 }
128 }
129
130 let imports = imports.join("\n");
131 let exports = exports.join("\n");
132
133 text += &imports;
134 text += self.content;
135 text += &exports;
136 Ok(text)
137 } else {
138 Ok(self.content.to_string())
139 }
140 }
141
142 pub fn is_loaded(&self) -> bool {
143 self.is_loaded.load(Ordering::SeqCst)
144 }
145
146 fn load_deps(self: Arc<Self>, ctx: Arc<Context>) -> BoxFuture<'static, Result<()>> {
147 async move {
148 if let Some(references) = &self.references {
149 let futures = references
150 .iter()
151 .filter_map(|(_, _, id)| {
152 if let Some(content) = ctx.get(id) {
153 if !content.is_loaded.load(Ordering::SeqCst) {
154 Some(content.load(&ctx))
155 } else {
156 None
157 }
158 } else {
159 log_error!("Unable to locate module {}", id);
160 None
161 }
162 })
163 .collect::<Vec<_>>();
164
165 join_all(futures).await;
166
167 }
171 Ok(())
172 }
173 .boxed()
174 }
175
176 pub async fn load(self: Arc<Self>, ctx: &Arc<Context>) -> Result<ContentStatus> {
177 ctx.load_content(self).await
178 }
179
180 fn create_blob_url(&self, ctx: &Arc<Context>) -> Result<String> {
181 let content = self.content(ctx)?;
182 let args = Array::new_with_length(1);
183 args.set(0, unsafe { Uint8Array::view(content.as_bytes()).into() });
184 let options = web_sys::BlobPropertyBag::new();
185 match self.content_type {
186 ContentType::Module | ContentType::Script => {
187 options.set_type("application/javascript");
188 }
189 ContentType::Style => {
190 options.set_type("text/css");
191 }
192 }
193
194 let blob = Blob::new_with_u8_array_sequence_and_options(&args, &options)?;
195 let url = Url::create_object_url_with_blob(&blob)?;
196 self.url.lock().unwrap().replace(url.clone());
197 Ok(url)
198 }
199
200 async fn load_impl(self: &Arc<Self>, ctx: &Arc<Context>) -> Result<ContentStatus> {
201 if self.is_loaded() {
202 return Ok(ContentStatus::Exists);
203 }
204
205 self.clone().load_deps(ctx.clone()).await?;
206 let (sender, receiver) = oneshot();
209 let url = self.create_blob_url(ctx)?;
210
211 let callback = callback!(move |_event: web_sys::CustomEvent| {
213 let status = ContentStatus::Loaded;
216 sender.try_send(status).expect("unable to post load event");
217 });
218
219 match &self.content_type {
220 ContentType::Module | ContentType::Script => {
221 self.inject_script(&url, &callback)?;
222 }
223 ContentType::Style => {
224 self.inject_style(&url, &callback)?;
225 }
226 };
227 let status = receiver.recv().await.expect("unable to recv() load event");
228 self.is_loaded.store(true, Ordering::SeqCst);
229 Ok(status)
230 }
231
232 fn inject_script<C>(&self, url: &str, callback: &C) -> Result<()>
233 where
234 C: AsRef<js_sys::Function>,
235 {
236 let script = document().create_element("script")?;
237 script.add_event_listener_with_callback("load", callback.as_ref())?;
238
239 match &self.content_type {
240 ContentType::Module => {
241 script.set_attribute("module", "true")?;
242 script.set_attribute("type", "module")?;
243 }
244 ContentType::Script => {
245 script.set_attribute("type", "application/javascript")?;
246 }
247 _ => {
248 panic!(
249 "inject_script() unsupported content type `{:?}`",
250 self.content_type
251 )
252 }
253 }
254 script.set_attribute("src", url)?;
255 script.set_attribute("id", self.ident)?;
256 root().append_child(&script)?;
257 Ok(())
258 }
259
260 fn inject_style<C>(&self, url: &str, callback: &C) -> Result<()>
261 where
262 C: AsRef<js_sys::Function>,
263 {
264 let style = document().create_element("link")?;
265 style.add_event_listener_with_callback("load", callback.as_ref())?;
266 style.set_attribute("type", "text/css")?;
267 style.set_attribute("rel", "stylesheet")?;
268 style.set_attribute("href", url)?;
269 style.set_attribute("id", self.ident)?;
270 root().append_child(&style)?;
271 println!("injecting style `{}`", self.ident);
272 Ok(())
273 }
274}
275
276pub struct Context {
277 pub content: Arc<Mutex<ContentMap>>,
278 pub lookup_handler: LookupHandler<Id, ContentStatus, Error>,
279 pub loaded: AtomicUsize,
280}
281
282impl Default for Context {
283 fn default() -> Self {
284 Context {
285 content: Arc::new(Mutex::new(ContentMap::new())),
286 lookup_handler: LookupHandler::new(),
287 loaded: AtomicUsize::new(0),
288 }
289 }
290}
291
292impl Context {
293 pub fn declare(&self, content: ContentList) {
302 self.content.lock().unwrap().extend(content.iter().cloned());
303 }
308
309 pub fn get(&self, id: &Id) -> Option<Arc<Content>> {
310 self.content.lock().unwrap().get(id).cloned()
311 }
312
313 pub async fn load_content(self: &Arc<Self>, content: Arc<Content>) -> Result<ContentStatus> {
314 if content.is_loaded() {
315 Ok(ContentStatus::Exists)
316 } else {
317 match self.lookup_handler.queue(&content.id).await {
318 RequestType::New(receiver) => {
319 self.loaded.fetch_add(1, Ordering::SeqCst);
320 let result = content.load_impl(self).await;
321 self.lookup_handler.complete(&content.id, result).await;
322 receiver.recv().await?
323 }
324 RequestType::Pending(receiver) => receiver.recv().await?,
325 }
326 }
327 }
328
329 pub async fn load_ids(self: &Arc<Self>, list: &[Id]) -> Result<()> {
330 let start = Instant::now();
331
332 let futures = list
339 .iter()
340 .filter_map(|id| {
341 if let Some(module) = self.get(id) {
342 Some(module.load(self))
343 } else {
344 log_error!("Unable to locate module {}", id);
345 None
347 }
348 })
349 .collect::<Vec<_>>();
350
351 for future in futures {
352 match future.await {
353 Ok(_event) => {}
354 Err(err) => {
355 log_error!("{}", err);
356 }
357 }
358 }
359
360 let elapsed = start.elapsed();
361 let loaded = self.loaded.load(Ordering::SeqCst);
362 log_info!(
363 "Loaded {} references in {} msec",
364 loaded,
365 elapsed.as_millis()
366 );
367
368 Ok(())
369 }
370}
371
372static mut CONTEXT: Option<Arc<Context>> = None;
373
374pub fn context() -> Arc<Context> {
375 unsafe {
376 if let Some(context) = CONTEXT.as_ref() {
377 context.clone()
378 } else {
379 let context = Arc::new(Context::default());
380 CONTEXT = Some(context.clone());
381 context
382 }
383 }
384}
385
386pub fn declare(content: ContentList) -> Arc<Context> {
387 let ctx = context();
388 ctx.declare(content);
389 ctx
390}