use std::sync::{Arc, Mutex};
use cookie::Cookie;
use futures_util::{FutureExt, TryFutureExt};
use inherit_methods_macro::inherit_methods;
use jni::{
objects::{JObject, JString},
refs::{LoaderContext, Reference},
};
use jni_min_helper::DynamicProxy;
use winio_callback::SyncCallback;
use winio_handle::AsContainer;
use winio_primitive::{Point, Size};
use crate::{
BaseWidget, Result, current_activity,
java::{
android::webkit::{
CookieManager, ValueCallback, WebChromeClient as AWebChromeClient, WebView as AWebView,
},
custom::WinioWebViewClient,
lang::JRunnable,
},
vm_exec,
};
#[derive(Debug)]
pub struct WebView {
inner: BaseWidget<AWebView<'static>>,
on_started: Arc<SyncCallback>,
#[allow(dead_code)]
started_proxy: DynamicProxy,
on_finished: Arc<SyncCallback>,
#[allow(dead_code)]
finished_proxy: DynamicProxy,
}
#[inherit_methods(from = "self.inner")]
impl WebView {
pub async fn new(parent: impl AsContainer) -> Result<Self> {
vm_exec(|env| {
CookieManager::get_instance(env)?.set_accept_cookie(env, true)?;
let act = current_activity(env)?;
let widget = AWebView::new(env, act)?;
widget
.get_settings(env)?
.set_java_script_enabled(env, true)?;
let chrome_client = AWebChromeClient::new(env)?;
widget.set_web_chrome_client(env, &chrome_client)?;
let inner = BaseWidget::new_with_env(env, parent.as_container(), widget)?;
let client = WinioWebViewClient::new(env)?;
let on_started = Arc::new(SyncCallback::new());
let started_proxy =
DynamicProxy::build(env, &LoaderContext::None, [JRunnable::class_name()], {
let on_started = on_started.clone();
move |_env, _method, _args| {
on_started.signal(());
Ok(JObject::null())
}
})?;
client.set_on_page_started(env, &started_proxy)?;
let on_finished = Arc::new(SyncCallback::new());
let finished_proxy =
DynamicProxy::build(env, &LoaderContext::None, [JRunnable::class_name()], {
let on_finished = on_finished.clone();
move |_env, _method, _args| {
on_finished.signal(());
Ok(JObject::null())
}
})?;
client.set_on_page_finished(env, &finished_proxy)?;
inner.set_web_view_client(env, client)?;
Ok(Self {
inner,
on_started,
started_proxy,
on_finished,
finished_proxy,
})
})
}
pub fn is_visible(&self) -> Result<bool>;
pub fn set_visible(&mut self, v: bool) -> Result<()>;
pub fn is_enabled(&self) -> Result<bool>;
pub fn set_enabled(&mut self, v: bool) -> Result<()>;
pub fn loc(&self) -> Result<Point>;
pub fn set_loc(&mut self, p: Point) -> Result<()>;
pub fn size(&self) -> Result<Size>;
pub fn set_size(&mut self, v: Size) -> Result<()>;
pub fn source(&self) -> Result<String> {
vm_exec(|env| Ok(self.inner.get_url(env)?.try_to_string(env)?))
}
pub fn set_source(&mut self, s: impl AsRef<str>) -> Result<()> {
vm_exec(|env| {
let url = env.new_string(s.as_ref())?;
self.inner.load_url(env, &url)?;
Ok(())
})
}
pub fn set_html(&mut self, s: impl AsRef<str>) -> Result<()> {
use base64::prelude::*;
let s = BASE64_STANDARD.encode(s.as_ref());
vm_exec(|env| {
let data = env.new_string(s)?;
let encoding = env.new_string("base64")?;
self.inner
.load_data(env, &data, JString::null(), &encoding)?;
Ok(())
})
}
pub fn can_go_forward(&self) -> Result<bool> {
vm_exec(|env| Ok(self.inner.can_go_forward(env)?))
}
pub fn go_forward(&mut self) -> Result<()> {
vm_exec(|env| {
self.inner.go_forward(env)?;
Ok(())
})
}
pub fn can_go_back(&self) -> Result<bool> {
vm_exec(|env| Ok(self.inner.can_go_back(env)?))
}
pub fn go_back(&mut self) -> Result<()> {
vm_exec(|env| {
self.inner.go_back(env)?;
Ok(())
})
}
pub fn reload(&mut self) -> Result<()> {
vm_exec(|env| {
self.inner.reload(env)?;
Ok(())
})
}
pub fn stop(&mut self) -> Result<()> {
vm_exec(|env| {
self.inner.stop_loading(env)?;
Ok(())
})
}
pub async fn wait_navigating(&self) {
self.on_started.wait().await;
}
pub async fn wait_navigated(&self) {
self.on_finished.wait().await;
}
pub async fn cookies(&self) -> Result<Vec<Cookie<'static>>> {
let source = self.source()?;
if source.is_empty() {
return Ok(vec![]);
}
vm_exec(|env| {
let url = env.new_string(&source)?;
let cookies = CookieManager::get_instance(env)?.get_cookie(env, &url)?;
let cookies = cookies.try_to_string(env)?;
if let Ok(cookie) = Cookie::parse(cookies) {
Ok(vec![cookie])
} else {
Ok(vec![])
}
})
}
async fn set_cookie_impl(&mut self, cookie: &str) -> Result<()> {
let source = self.source()?;
if source.is_empty() {
return Ok(());
}
let (rx, _proxy) = vm_exec(|env| {
let url = env.new_string(&source)?;
let value = env.new_string(cookie)?;
let (tx, rx) = oneshot::channel();
let tx = Arc::new(Mutex::new(Some(tx)));
let proxy =
DynamicProxy::build(env, &LoaderContext::None, [ValueCallback::class_name()], {
move |_env, _method, _args| {
if let Some(tx) = tx.lock().unwrap().take() {
tx.send(()).ok();
}
Ok(JObject::null())
}
})?;
CookieManager::get_instance(env)?.set_cookie(env, &url, &value, &proxy)?;
Result::Ok((rx, proxy))
})?;
rx.await.map_err(std::io::Error::other)?;
Ok(())
}
pub async fn set_cookie(&mut self, c: &Cookie<'_>) -> Result<()> {
self.set_cookie_impl(&c.to_string()).await
}
pub async fn delete_cookie(&mut self, c: &Cookie<'_>) -> Result<()> {
let mut cookie = c.clone();
cookie.set_max_age(time::Duration::seconds(0));
self.set_cookie_impl(&cookie.to_string()).await
}
pub fn run_javascript(
&mut self,
script: impl AsRef<str>,
) -> Result<impl Future<Output = Result<String>> + 'static> {
let (tx, rx) = oneshot::channel();
let _proxy = vm_exec(|env| {
let script = env.new_string(script.as_ref())?;
let tx = Arc::new(Mutex::new(Some(tx)));
let callback =
DynamicProxy::build(env, &LoaderContext::None, [ValueCallback::class_name()], {
move |env, method, args| {
let name = method.get_name(env)?.try_to_string(env)?;
if name == "onReceiveValue" {
let value = args.get_element(env, 0)?;
let value = env.cast_local::<JString>(value)?;
let result = if value.is_null() {
None
} else {
Some(value.try_to_string(env)?)
};
if let Some(tx) = tx.lock().unwrap().take() {
tx.send(result).ok();
}
}
Ok(JObject::null())
}
})?;
self.inner.evaluate_javascript(env, &script, &callback)?;
Result::Ok(callback)
})?;
Ok(rx
.into_future()
.map_err(|e| std::io::Error::other(e).into())
.map(|res| res.map(|res| res.unwrap_or_default())))
}
}
winio_handle::impl_as_widget!(WebView, inner);