Rust-wasm 0.1.0

A sample project with wasm-pack
Documentation
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
use wasm_bindgen::
{
    prelude::*,
    JsCast,
};
use web_sys::
{
    XmlHttpRequest,
    XmlHttpRequestResponseType,
    ProgressEvent,
};
use js_sys::
{
    ArrayBuffer,
    Uint8Array,
};
use std::
{
    cell::{RefCell, Cell},
    rc::Rc,
};

use crate::{clone, borrow_mut};
use crate::log;
/// Type alias for the format of a closure for a request
type RequestClosure = Closure<dyn FnMut(ProgressEvent)>;
/// Handle for a new request added into the resource loader
pub type RequestHandle = usize;
/// Handle for XmlHttpRequest
/// This owns the closures for all of a XmlHttpRequest's callbacks so they can be cleaned
/// up when the request is complete
pub struct HttpRequest
{
    // Handle
    internal: XmlHttpRequest,
    method: String,
    url: String,
    // Events
    onabort: Option<RequestClosure>,
    onerror: Option<RequestClosure>,
    onload: Option<RequestClosure>,
    onloadstart: Option<RequestClosure>,
    onloadend: Option<RequestClosure>,
    onprogress: Option<RequestClosure>,
}
impl HttpRequest
{
    fn new(method: String, url: String) -> Result<HttpRequest, JsValue>
    {
        Ok(HttpRequest
        {
            internal: XmlHttpRequest::new()?,
            method, url,
            onabort: None, 
            onerror: None,
            onload: None, 
            onloadstart: None, 
            onloadend: None, 
            onprogress: None
        })
    }
}

pub struct CallbackArgs(pub ProgressEvent, pub XmlHttpRequest);
pub struct OnloadCallbackArgs(pub CallbackArgs, pub Vec<u8>);

/// Resource Loader
///
/// This is for loading resources from a URL. It's intended for loading visualization assets but
/// it can be used for any sort of XmlHttpRequest
pub struct ResourceLoader
{
    requests_left: Cell<i32>,
    // Total work that is already done
    work_total: Cell<f64>,
    // Total work to be done across all resources
    work_loaded: Cell<f64>,
    global_onloadend: Cell<Option<Box<dyn FnOnce()>>>,
    global_onprogress: Option<Box<dyn FnMut(f64, f64)>>,
    http_requests: Vec<HttpRequest>,
}

impl ResourceLoader
{
    /// Create empty resource loader
    pub fn new() -> Self
    {
        ResourceLoader
        {
            requests_left: Cell::new(0),
            work_total: Cell::new(0.0),
            work_loaded: Cell::new(0.0),
            global_onloadend: Cell::new(None),
            global_onprogress: None,
            http_requests: vec![],
        }
    }
    /// Add an overall "when everything is done" function to the loader
    #[allow(dead_code)]
    pub fn set_onloadend<F>(&self, callback: F) where F: 'static + FnOnce()
    {
        self.global_onloadend.set(Some(Box::new(callback)));
    }
    /// Add an overall "onprogress" function to the loader
    ///
    /// First arg of `callback` is amount of work already performed
    /// Second arg of `callback` is total amount of work to be done
    #[allow(dead_code)]
    pub fn set_onprogress<F>(&mut self, callback: F) where F: 'static + FnMut(f64, f64)
    {
        self.global_onprogress = Some(Box::new(callback));
    }

    /// Add a new resource request
    ///
    /// `method` is the HTTP method to use (GET, POST, etc)
    /// `url` is the resource URL
    /// Returns a handle to the request for use in assigning callbacks
    #[allow(dead_code)]
    pub fn add_request(&mut self, method: impl Into<String>, url: impl Into<String>) -> Result<RequestHandle, JsValue>
    {
        // Set the response type to Arraybuffer so that the response can be read into a byte array
        let request = HttpRequest::new(method.into(), url.into())?;
        request.internal.set_response_type(XmlHttpRequestResponseType::Arraybuffer);
        // Add the new request
        self.http_requests.push(request);
        // Increment the number of requests that are going to be executed
        self.requests_left.set(self.requests_left.get() + 1);

        Ok(self.http_requests.len()-1)
    }

    /// Set the `onabort` event callback for a request
    #[allow(dead_code)]
    pub fn set_request_onabort<F>(&mut self, handle: RequestHandle, onabort: F)
        -> bool where F: 'static + FnOnce(CallbackArgs)
    {
        // Make sure the handle is valid
        if let Some(http_request) = self.http_requests.get_mut(handle)
        {
            // Create the closure
            clone!(http_request.internal);
            let closure = Closure::once(move |event: ProgressEvent|
                {
                    onabort(CallbackArgs(event, internal));
                });
            // Assign the closure to the request and it's internal JS object
            http_request.onabort = Some(closure);
            http_request.internal.set_onabort(Some(http_request.onabort.as_ref().unwrap().as_ref().unchecked_ref()));

            true
        }
        else { false }
    }

    /// Set the `onerror` event callback for a request
    #[allow(dead_code)]
    pub fn set_request_onerror<F>(&mut self, handle: RequestHandle, onerror: F)
        -> bool where F: 'static + FnOnce(CallbackArgs)
    {
        // Make sure the handle is valid
        if let Some(http_request) = self.http_requests.get_mut(handle)
        {
            // Create the closure
            clone!(http_request.internal);
            let closure = Closure::once(move |event: ProgressEvent|
                {
                    onerror(CallbackArgs(event, internal));
                });
            // Assign the closure to the request and it's internal JS object
            http_request.onerror = Some(closure);
            http_request.internal.set_onerror(Some(http_request.onerror.as_ref().unwrap().as_ref().unchecked_ref()));

            true
        }
        else { false }
    }

    /// Set the `onload` event callback for a request
    #[allow(dead_code)]
    pub fn set_request_onload<F>(&mut self, handle: RequestHandle, onload: F)
        -> bool where F: 'static + FnOnce(OnloadCallbackArgs)
    {
        // Make sure the handle is valid
        if let Some(http_request) = self.http_requests.get_mut(handle)
        {
            // Create the closure
            clone!(http_request.internal);
            let closure = Closure::once(move |event: ProgressEvent|
                {
                    let mut response_vec: Vec<u8> = vec![];
                    match internal.response()
                    {
                        Ok(response) =>
                            {
                                let buffer: ArrayBuffer = response.into();
                                let byte_arr = Uint8Array::new(&buffer);
                                response_vec = byte_arr.to_vec();
                            },
                        Err(err) =>
                            {
                                log!("Error getting request response: {:?}", err);
                            }
                    }
                    onload(OnloadCallbackArgs(CallbackArgs(event, internal), response_vec));
                });
            // Assign the closure to the request and it's internal JS object
            http_request.onload = Some(closure);
            http_request.internal.set_onload(Some(http_request.onload.as_ref().unwrap().as_ref().unchecked_ref()));

            true
        }
        else { false }
    }

    /// Set the `onloadstart` event callback for a request
    #[allow(dead_code)]
    pub fn set_request_onloadstart<F>(&mut self, handle: RequestHandle, onloadstart: F)
        -> bool where F: 'static + FnOnce(CallbackArgs)
    {
        // Make sure the handle is valid// Make sure the handle is valid// Make sure the handle is valid// Make sure the handle is valid
        if let Some(http_request) = self.http_requests.get_mut(handle)
        {
            // Create the closure// Create the closure// Create the closure// Create the closure
            clone!(http_request.internal);
            let closure = Closure::once(move |event: ProgressEvent|
                {
                    onloadstart(CallbackArgs(event, internal));
                });
            // Assign the closure to the request and it's internal JS object// Assign the closure to the request and it's internal JS object// Assign the closure to the request and it's internal JS object// Assign the closure to the request and it's internal JS object
            http_request.onloadstart = Some(closure);
            http_request.internal.set_onloadstart(Some(http_request.onloadstart.as_ref().unwrap().as_ref().unchecked_ref()));

            true
        }
        else { false }
    }

    /// Set the `onloadend` event callback for a request
    #[allow(dead_code)]
    pub fn set_request_onloadend<F>(&mut self, handle: RequestHandle, onloadend: F)
        -> bool where F: 'static + FnOnce(CallbackArgs)
    {
        // Make sure the handle is valid
        if let Some(http_request) = self.http_requests.get_mut(handle)
        {
            // Create the closure
            clone!(http_request.internal);
            let closure = Closure::once(move |event: ProgressEvent|
                {
                    onloadend(CallbackArgs(event, internal));
                });
            // Assign the closure to the request and it's internal JS object
            http_request.onloadend = Some(closure);
            http_request.internal.set_onloadend(Some(http_request.onloadend.as_ref().unwrap().as_ref().unchecked_ref()));

            true
        }
        else { false }
    }

    /// Set the `onprogress` event callback for a request
    #[allow(dead_code)]
    pub fn set_request_onprogress<F>(&mut self, handle: RequestHandle, mut onprogress: F)
        -> bool where F: 'static + FnMut(CallbackArgs)
    {
        // Make sure the handle is valid
        if let Some(http_request) = self.http_requests.get_mut(handle)
        {
           // Create the closure
            clone!(http_request.internal);
            let closure = Closure::wrap(Box::new(move |event: ProgressEvent|
                {
                    onprogress(CallbackArgs(event, internal.clone()));
                }) as Box<dyn FnMut(_)>);
            // Assign the closure to the request and it's internal JS object
            http_request.onprogress = Some(closure);
            http_request.internal.set_onprogress(Some(http_request.onprogress.as_ref().unwrap().as_ref().unchecked_ref()));

            true
        }
        else { false }
    }

    /// Add a resource to be loaded
    #[allow(dead_code)]
    pub fn submit(self)
    {
        let mut loader = self;
        // Move the http_requests out of the loader so that the requests can be mutably iterated
        //      over without conflicting with the loader being borrowed/mutably borrowed
        let mut http_requests = vec![];
        std::mem::swap(&mut loader.http_requests, &mut http_requests);
        // Wrap the loader in a Rc<RefCell<_>> so that it can be used within the closures
        let loader = Rc::new(RefCell::new(loader));

        for http_request in &mut http_requests
        {
            // New closure to wrap the `onloadend` event, call the `global_onloadend` callback,
            //      and make sure that everything stays alive until it is no longer needed
            let closure =
                {
                    // Move the request's `onloadend` callback out of the struct
                    //      so that it can be dropped once it has executed
                    let onloadend = http_request.onloadend.take();
                    clone!(loader);
                    Closure::once(move |event: ProgressEvent|
                        {
                            // If the user provided a callback, execute it
                            if let Some(onloadend) = onloadend
                            {
                                // Convert the closure into a JS function and call it
                                let onloadend: js_sys::Function = onloadend.into_js_value().into();
                                if let Err(err) = onloadend.call1(&JsValue::null(), &event)
                                {
                                   log!("Error executing request onloadend func: {:?}", err);
                                }
                            }

                            let loader_borrow = loader.borrow();
                            // Decrement the amount of requests left to execute
                            let left = loader_borrow.requests_left.get();
                            loader_borrow.requests_left.set(left - 1);

                            // If this was the last request being executed
                            if left <= 1
                            {
                                // Then call the user's overall `onloadend` callback if it exists
                                //      This is what keeps the ResourceLoader alive throughout
                                //      all of the request executions, and after this expression
                                //      the ResourceLoader and all of the requests are dropped
                                if let Some(global_onloadend) = loader_borrow.global_onloadend.take()
                                {
                                    global_onloadend();
                                }
                            }
                        })
                };
            // Assign the new closure to it's request and internal JS object
            http_request.onloadend = Some(closure);
            http_request.internal.set_onloadend(Some(http_request.onloadend.as_ref().unwrap().as_ref().unchecked_ref()));

            // New closure to wrap the `onprogress` event and call the `global_onprogress` callback
            let closure =
                {
                    // Take the `onprogress` closure out of the request so that we can use it
                    let onprogress = http_request.onprogress.take();

                    // Convert the closure into a JS function if the closure exists
                    let onprogress: Option<js_sys::Function> = if let Some(closure) = onprogress
                    {
                        Some(closure.into_js_value().into())
                    }
                    else { None };
                    // Wrap the callback in Rc<RefCell<_>> since we need to call it multiple times
                    let onprogress = Rc::new(RefCell::new(onprogress));
                    // Downgrade the loader Rc to a Weak reference so that it doesn't keep
                    //      the loader alive after `global_onloadend` has been called
                    let loader = Rc::downgrade(&loader);
                    // Has the work total for this request been set
                    let mut set_total = false;
                    clone!(onprogress);
                    Closure::wrap(Box::new(move |event: ProgressEvent|
                        {
                            // If the user provided a callback, execute it
                            if let Some(onprogress) = onprogress.borrow_mut().as_mut()
                            {
                                if let Err(err) = onprogress.call1(&JsValue::null(), &event)
                                {
                                    log!("Error executing request onprogress func: {:?}", err);
                                }
                            }

                            // Include this in the global progress tracking only if it is actually
                            //      able to be tracked
                            if event.length_computable()
                            {

                                // Attempt to access the loader. If this is false, then
                                //     `global_onloadend` has already been called. Technically
                                //      that will never actually happen, since this closure
                                //      will have been dropped right after `global_onloadend`
                                //      executes, however doing this allows the memory to properly
                                //      be cleaned up
                                if let Some(loader) = loader.upgrade()
                                {
                                    borrow_mut!(loader);
                                    // Add the work total into the overall work total if it hasn't been
                                    //      already
                                    if !set_total
                                    {
                                        loader.work_total.set(loader.work_total.get() + event.total());
                                        set_total = true;
                                    }

                                    // Get the total amount of work that has been completed by
                                    //      all requests
                                    let loaded = loader.work_loaded.get() + event.loaded();
                                    loader.work_loaded.set(loaded);

                                    let work_total = loader.work_total.get();
                                    // Call the user's `global_onprogress` callback if it exists
                                    if let Some(global_onprogress) = loader.global_onprogress.as_mut()
                                    {
                                        global_onprogress(loaded, work_total);
                                    }
                                }
                            }
                        }) as Box<dyn FnMut(_)>)
                };
            // Assign the new closure to it's request and internal JS object
            http_request.onprogress = Some(closure);
            http_request.internal.set_onprogress(Some(http_request.onprogress.as_ref().unwrap().as_ref().unchecked_ref()));

            // Setup the request with the specified HTTP method and resource URL
            http_request.internal.open(&http_request.method, &http_request.url).expect("request opened");
            // Send the request
            http_request.internal.send().expect("request sent");
        }
        // Now that all of the requests have been processed, pass them back into the ResourceLoader
        //      so that they can be cleaned up when everything is finished
        loader.borrow_mut().http_requests = http_requests;
    }
}

// #[cfg(test)]
// mod tests
// {
//     inject_wasm_test_boilerplate!();
//     use wasm_bindgen::JsValue;
//     use wasm_bindgen_futures::JsFuture;
//     use js_sys::Promise;

//     use crate::
//     {
//         resource::
//         {
//             loader::*
//         }, clone, log, rs::resource::loader::ResourceLoader
//     };
//     use std::{rc::Rc, cell::Cell};

//     // https://www.reddit.com/r/rust/comments/cpxjlw/wasmasync_discoveris_about_sleepawait_via/
//     pub async fn timer(ms: i32) -> Result<(), JsValue> {
//         let promise = Promise::new(&mut |yes, _| {
//             let win = window().unwrap();
//             win.set_timeout_with_callback_and_timeout_and_arguments_0(&yes, ms)
//                 .unwrap();
//         });
//         let js_fut = JsFuture::from(promise);
//         js_fut.await?;
//         Ok(())
//     }

//     #[wasm_bindgen_test]
//     async fn test_onabort()
//     {
//         let mut resource_loader = ResourceLoader::new();

//         let request_handle = resource_loader.add_request("GET", "/build.py").unwrap();

//         let abort_executed = Rc::new(Cell::new(false));

//         {
//             clone!(abort_executed);
//             resource_loader.set_request_onabort(request_handle, move |_|
//             {
//                 abort_executed.set(true);
//             });

//             resource_loader.set_request_onloadstart(request_handle, move |CallbackArgs(_, request)|
//                 {
//                     request.abort().unwrap();
//                 });
//         }
//         resource_loader.submit();

//         // Wait max of 100ms for request to be carried out
//         let mut counter = 0;
//         while !abort_executed.get() && counter < 100
//         {
//             counter += 1;
//             timer(1).await.unwrap();
//         }
//         assert!(abort_executed.get());
//     }

//     async fn test_onerror()
//     {
//         let mut resource_loader = ResourceLoader::new();

//         let request_handle = resource_loader.add_request("GET", "/nonexistentfile.txt").unwrap();

//         let error_executed = Rc::new(Cell::new(false));

//         {
//             clone!(error_executed);
//             resource_loader.set_request_onerror(request_handle, move |_|
//                 {
//                     error_executed.set(true);
//                 });

//             // Trigger the event
//             resource_loader.set_request_onload(request_handle, move |OnloadCallbackArgs(CallbackArgs(_, request), _)|
//                 {
//                     request.dispatch_event(&Event::new("error").unwrap()).unwrap();
//                 });
//         }
//         resource_loader.submit();


//         // Wait max of 100ms for request to be carried out
//         let mut counter = 0;
//         while !error_executed.get() && counter < 100
//         {
//             counter += 1;
//             timer(1).await.unwrap();
//         }
//         assert!(error_executed.get());
//     }

//     #[wasm_bindgen_test]
//     async fn test_onload()
//     {
//         let mut resource_loader = ResourceLoader::new();

//         let expected_contents = String::from(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/build.py")));
//         let request_handle = resource_loader.add_request("GET", "/build.py").unwrap();

//         let done = Rc::new(Cell::new(false));
//         let contents = Rc::new(Cell::new(None));

//         {
//             clone!(done, contents);
//             resource_loader.set_request_onload(request_handle, move |OnloadCallbackArgs(_, response)|
//                 {
//                     contents.set(Some(String::from_utf8(response).unwrap()));
//                     done.set(true);
//                 });
//         }
//         resource_loader.submit();

//         // Wait max of 100ms for request to be carried out
//         let mut counter = 0;
//         while !done.get() && counter < 100
//         {
//             counter += 1;
//             timer(1).await.unwrap();
//         }
//         assert_eq!(Some(expected_contents), contents.take());
//     }

//     #[wasm_bindgen_test]
//     async fn test_onloadstart()
//     {
//         let mut resource_loader = ResourceLoader::new();

//         let request_handle = resource_loader.add_request("GET", "/build.py").unwrap();

//         let loadstart_executed = Rc::new(Cell::new(false));

//         {
//             clone!(loadstart_executed);
//             resource_loader.set_request_onloadstart(request_handle, move |_|
//                 {
//                     loadstart_executed.set(true);
//                 });
//         }
//         resource_loader.submit();

//         // Wait max of 100ms for request to be carried out
//         let mut counter = 0;
//         while !loadstart_executed.get() && counter < 100
//         {
//             counter += 1;
//             timer(1).await.unwrap();
//         }
//         assert!(loadstart_executed.get());
//     }

//     #[wasm_bindgen_test]
//     async fn test_onloadend()
//     {
//         let mut resource_loader = ResourceLoader::new();

//         let request_handle = resource_loader.add_request("GET", "/build.py").unwrap();

//         let loadend_executed = Rc::new(Cell::new(false));

//         {
//             clone!(loadend_executed);
//             resource_loader.set_request_onloadend(request_handle, move |_|
//                 {
//                     loadend_executed.set(true);
//                 });
//         }
//         resource_loader.submit();

//         // Wait max of 100ms for request to be carried out
//         let mut counter = 0;
//         while !loadend_executed.get() && counter < 100
//         {
//             counter += 1;
//             timer(1).await.unwrap();
//         }
//         assert!(loadend_executed.get());
//     }

//     #[wasm_bindgen_test]
//     async fn test_onprogress()
//     {
//         let mut resource_loader = ResourceLoader::new();

//         let request_handle = resource_loader.add_request("GET", "/build.py").unwrap();

//         let onprogress_executed = Rc::new(Cell::new(false));
//         let work_total = Rc::new(Cell::new(0.0));

//         {
//             clone!(onprogress_executed, work_total);
//             resource_loader.set_request_onprogress(request_handle, move |CallbackArgs(event, _)|
//                 {
//                     onprogress_executed.set(true);
//                     work_total.set(event.total());
//                 });
//         }
//         resource_loader.submit();

//         // Wait max of 100ms for request to be carried out
//         let mut counter = 0;
//         while !onprogress_executed.get() && counter < 100
//         {
//             counter += 1;
//             timer(1).await.unwrap();
//         }
//         assert!(onprogress_executed.get());
//         assert!(work_total.get() > 0.0);
//     }

//     async fn test_global_onloadend()
//     {
//         let mut resource_loader = ResourceLoader::new();

//         resource_loader.add_request("GET", "/build.py").unwrap();
//         resource_loader.add_request("GET", "/nonexistentfile.txt").unwrap();

//         let global_loadend_executed = Rc::new(Cell::new(false));

//         {
//             clone!(global_loadend_executed);
//             resource_loader.set_onloadend(move ||
//                 {
//                     global_loadend_executed.set(true);
//                 });
//         }
//         resource_loader.submit();

//         // Wait max of 100ms for request to be carried out
//         let mut counter = 0;
//         while !global_loadend_executed.get() && counter < 100
//         {
//             counter += 1;
//             timer(1).await.unwrap();
//         }
//         assert!(global_loadend_executed.get());
//     }

//     #[wasm_bindgen_test]
//     async fn test_global_onprogress()
//     {
//         let mut resource_loader = ResourceLoader::new();

//         resource_loader.add_request("GET", "/build.py").unwrap();
//         resource_loader.add_request("GET", "/build.py").unwrap();

//         let work_loaded = Rc::new(Cell::new(0.0));
//         let work_total = Rc::new(Cell::new(0.0));
//         let num_executions = Rc::new(Cell::new(0));

//         {
//             clone!(work_loaded, work_total, num_executions);
//             resource_loader.set_onprogress(move |current, total|
//                 {
//                     work_loaded.set(current);
//                     work_total.set(total);
//                     log!("{} - {}", current, total);
//                     num_executions.set(num_executions.get() + 1);
//                 });
//         }
//         resource_loader.submit();

//         // Wait max of 100ms for request to be carried out
//         let mut counter = 0;
//         while !num_executions.get() < 2 && counter < 100
//         {
//             counter += 1;
//             timer(1).await.unwrap();
//         }
//         assert_eq!(num_executions.get(), 2);
//         assert!(work_total.get() > 0.0);
//         assert!(work_loaded.get() > 0.0);
//     }
// }