use crate::errors::Error;
use crate::window::get_window;
use futures::channel::oneshot::{channel, Receiver, Sender};
use futures::task::{Context, Poll};
use futures::Future;
use log::info;
use std::pin::Pin;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::*;
pub struct Image {
pub url: String,
pub img: Option<HtmlImageElement>,
state: ImageState,
closure_holders: Option<(Closure<dyn FnMut()>, Closure<dyn FnMut(JsValue)>)>,
}
enum ImageState {
Empty,
Loading {
receiver_err: Receiver<JsValue>,
receiver_success: Receiver<()>,
},
}
impl Future for Image {
type Output = Result<HtmlImageElement, Error>;
fn poll(mut self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Self::Output> {
match &mut self.state {
ImageState::Empty => {
let img = HtmlImageElement::new()?;
let has_same_origin = same_origin(&self.url)?;
if !has_same_origin {
img.set_cross_origin(Some(&"anonymous"));
}
img.set_src(&self.url);
let waker = ctx.waker().clone();
let (sender_success, receiver_success): (Sender<()>, Receiver<()>) = channel();
let mut sender_success = Option::from(sender_success);
let closure_success = Closure::wrap(Box::new(move || {
sender_success.take().unwrap().send(()).unwrap();
waker.wake_by_ref();
}) as Box<dyn FnMut()>);
img.set_onload(Some(closure_success.as_ref().unchecked_ref()));
let waker = ctx.waker().clone();
let (sender_err, receiver_err): (Sender<JsValue>, Receiver<JsValue>) = channel();
let mut sender_err = Option::from(sender_err);
let closure_err = Closure::wrap(Box::new(move |err| {
sender_err.take().unwrap().send(err).unwrap();
waker.wake_by_ref();
}) as Box<dyn FnMut(JsValue)>);
img.set_onerror(Some(closure_err.as_ref().unchecked_ref()));
self.img = Some(img);
self.state = ImageState::Loading {
receiver_err,
receiver_success,
};
self.closure_holders = Some((closure_success, closure_err));
ctx.waker().wake_by_ref();
Poll::Pending
}
ImageState::Loading {
receiver_err,
receiver_success,
} => {
let mut is_cancelled = false;
let error_state = match receiver_err.try_recv() {
Ok(result) => result,
_ => {
is_cancelled = true;
None
}
};
let success_state = match receiver_success.try_recv() {
Ok(result) => result,
_ => {
is_cancelled = true;
None
}
};
if let Some(result) = error_state {
Poll::Ready(Err(result.into()))
} else if let Some(_) = success_state {
Poll::Ready(Ok(self.img.as_ref().unwrap().clone()))
} else {
if !is_cancelled {
}
Poll::Pending
}
}
}
}
}
impl Image {
pub fn new(url: &str) -> Self {
let url = url.to_owned();
Self {
url,
img: None,
state: ImageState::Empty,
closure_holders: None,
}
}
}
pub fn same_origin(url: &str) -> Result<bool, JsValue> {
if url.starts_with("http://") || url.starts_with("https://") {
let location_origin = get_window()?.location().origin()?;
let url_origin = Url::new(url)?.origin();
Ok(url_origin == location_origin)
} else {
Ok(true)
}
}