use std::rc::Rc;
use std::sync::Mutex;
use std::thread;
use std::time::Instant;
use serde_json::Value;
use url::Url;
use crate::http::{Request as HttpRequest, Response as HttpResponse};
use crate::types::{KeyboardInput, MouseEvent, Texture, TickMode, Vec2, WindowSize};
use crate::window::HeadlessWindow;
use crate::{Error, Result};
pub mod web_context;
use web_context::{WebContext, WebContextImpl};
pub struct WebViewAttributes<T: HeadlessWindow> {
pub user_agent: Option<String>,
pub color: Color,
pub url: Option<Url>,
pub html: Option<String>,
pub initialization_scripts: Vec<String>,
pub custom_protocols: Vec<(String, Box<dyn Fn(&HttpRequest) -> Result<HttpResponse>>)>,
pub rpc_handler: Option<Box<dyn Fn(&T, RpcRequest) -> Option<RpcResponse>>>,
pub clipboard: bool,
}
impl<T: HeadlessWindow> Default for WebViewAttributes<T> {
fn default() -> Self {
Self {
user_agent: None,
color: Color::default(),
url: None,
html: None,
initialization_scripts: vec![],
custom_protocols: vec![],
rpc_handler: None,
clipboard: false,
}
}
}
pub struct WebviewBuilder<T: HeadlessWindow> {
pub webview: WebViewAttributes<T>,
web_context: Option<Rc<Mutex<WebContext<<T::Webview as EngineWebview>::WebContext>>>>,
window: T,
}
impl<T> WebviewBuilder<T>
where
T: HeadlessWindow,
{
pub fn new(window: T) -> Result<Self> {
let webview = WebViewAttributes::default();
let web_context = None;
Ok(Self {
webview,
web_context,
window,
})
}
pub fn with_color(mut self, color: Color) -> Self {
self.webview.color = color;
self
}
pub fn with_initialization_script(mut self, js: &str) -> Self {
self.webview.initialization_scripts.push(js.to_string());
self
}
#[cfg(feature = "protocol")]
pub fn with_custom_protocol<F>(mut self, name: String, handler: F) -> Self
where
F: Fn(&HttpRequest) -> Result<HttpResponse> + 'static,
{
self.webview
.custom_protocols
.push((name, Box::new(handler)));
self
}
pub fn with_rpc_handler<F>(mut self, handler: F) -> Self
where
F: Fn(&T, RpcRequest) -> Option<RpcResponse> + 'static,
{
self.webview.rpc_handler = Some(Box::new(handler));
self
}
pub fn with_url(mut self, url: &str) -> Result<Self> {
self.webview.url = Some(Url::parse(url)?);
Ok(self)
}
pub fn with_html(mut self, html: impl Into<String>) -> Result<Self> {
self.webview.html = Some(html.into());
Ok(self)
}
pub fn with_web_context(
mut self,
web_context: Rc<Mutex<WebContext<<T::Webview as EngineWebview>::WebContext>>>,
) -> Self {
self.web_context = Some(web_context);
self
}
pub fn with_user_agent(mut self, user_agent: &str) -> Self {
self.webview.user_agent = Some(user_agent.to_string());
self
}
pub fn build(mut self) -> Result<T::Webview> {
if self.webview.rpc_handler.is_some() {
self.webview
.initialization_scripts
.push(include_str!("javascript/rpc.js").to_string());
}
Ok(T::Webview::new(
Rc::new(self.window),
self.webview,
self.web_context,
)?)
}
}
pub trait EngineWebview {
type Window: HeadlessWindow;
type WebContext: WebContextImpl;
fn new(
window: Rc<Self::Window>,
attributes: WebViewAttributes<Self::Window>,
web_context: Option<Rc<Mutex<WebContext<Self::WebContext>>>>,
) -> Result<Self>
where
Self: Sized;
fn window(&self) -> &Self::Window;
fn evaluate_script(&self, js: &str) -> Result<()>;
fn resize(&self, new_size: WindowSize) -> Result<()>;
fn inner_size(&self) -> WindowSize {
self.window().inner_size()
}
fn version(&self) -> Result<String>;
fn send_keyboard_input(&self, keyboard_input: KeyboardInput);
fn send_mouse_position(&self, position: Vec2);
fn send_mouse_event(&self, mouse_event: MouseEvent);
fn get_texture(&mut self) -> Result<Option<Texture>>;
fn tick_once(&mut self);
fn tick(&mut self, tick_mode: TickMode) {
match tick_mode {
TickMode::Immediate => {
self.tick_once();
}
TickMode::WaitFor(duration) => {
let start = Instant::now();
if start.elapsed() < duration {
std::thread::sleep(duration - start.elapsed());
}
self.tick_once();
}
TickMode::PeriodicWait(periodic_wait) => {
let start = Instant::now();
loop {
self.tick_once();
if start.elapsed() >= periodic_wait.duration {
return;
}
thread::sleep(periodic_wait.tick_interval);
}
}
}
}
fn close(&mut self);
fn load_html(&self, html: String);
fn load_uri(&self, uri: String);
fn reload(&self);
fn set_is_visible(&mut self, is_visible: bool);
}
pub fn rpc_proxy<T: HeadlessWindow>(
window: &Rc<T>,
js: String,
handler: &dyn Fn(&T, RpcRequest) -> Option<RpcResponse>,
) -> Result<Option<String>> {
let req = serde_json::from_str::<RpcRequest>(&js)
.map_err(|e| Error::RpcScriptError(e.to_string(), js))?;
let mut response = (handler)(window, req);
if let Some(mut response) = response.take() {
if let Some(id) = response.id {
let js = if let Some(error) = response.error.take() {
RpcResponse::get_error_script(id, error)?
} else if let Some(result) = response.result.take() {
RpcResponse::get_result_script(id, result)?
} else {
RpcResponse::get_result_script(id, Value::Null)?
};
Ok(Some(js))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
const RPC_VERSION: &str = "2.0";
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RpcRequest {
jsonrpc: String,
pub id: Option<Value>,
pub method: String,
pub params: Option<Value>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct RpcResponse {
jsonrpc: String,
pub(crate) id: Option<Value>,
pub(crate) result: Option<Value>,
pub(crate) error: Option<Value>,
}
impl RpcResponse {
pub fn new_result(id: Option<Value>, result: Option<Value>) -> Self {
Self {
jsonrpc: RPC_VERSION.to_string(),
id,
result,
error: None,
}
}
pub fn new_error(id: Option<Value>, error: Option<Value>) -> Self {
Self {
jsonrpc: RPC_VERSION.to_string(),
id,
error,
result: None,
}
}
pub fn get_result_script(id: Value, result: Value) -> Result<String> {
let retval = serde_json::to_string(&result)?;
Ok(format!("window.external.rpc._result({}, {})", id, retval))
}
pub fn get_error_script(id: Value, result: Value) -> Result<String> {
let retval = serde_json::to_string(&result)?;
Ok(format!("window.external.rpc._error({}, {})", id, retval))
}
}
#[derive(Debug, Clone)]
pub struct Color {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
impl Color {
pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
Self { r, g, b, a }
}
}
impl Default for Color {
fn default() -> Self {
Self {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0,
}
}
}