use std::{
ffi::{CString, c_void},
marker::PhantomData,
ops::Deref,
ptr::null,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
thread,
};
use parking_lot::Mutex;
use crate::{
Error, MainThreadMessageLoop, MessagePumpLoop, MultiThreadMessageLoop, NativeWindowWebView,
WindowlessRenderWebView,
request::{CustomSchemeAttributes, ICustomRequestHandlerFactory},
sys,
utils::{AnyStringCast, Args, GetSharedRef, ThreadSafePointer, is_main_thread},
webview::{
MixWebviewHnadler, WebView, WebViewAttributes, WebViewHandler,
WindowlessRenderWebViewHandler,
},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LogLevel {
Off,
Info,
Error,
Warn,
Debug,
Trace,
}
#[derive(Default)]
pub struct RuntimeAttributes<R, W> {
_r: PhantomData<R>,
_w: PhantomData<W>,
custom_scheme: Option<CustomSchemeAttributes>,
windowless_rendering_enabled: bool,
cache_path: Option<CString>,
root_cache_path: Option<CString>,
browser_subprocess_path: Option<CString>,
framework_dir_path: Option<CString>,
main_bundle_path: Option<CString>,
external_message_pump: bool,
multi_threaded_message_loop: bool,
command_line_args_disabled: bool,
persist_session_cookies: bool,
user_agent: Option<CString>,
user_agent_product: Option<CString>,
locale: Option<CString>,
log_file: Option<CString>,
log_severity: Option<LogLevel>,
javascript_flags: Option<CString>,
resources_dir_path: Option<CString>,
locales_dir_path: Option<CString>,
background_color: u32,
disable_signal_handlers: bool,
}
impl<W> RuntimeAttributes<MainThreadMessageLoop, W> {
pub fn create_runtime<T>(&self, handler: T) -> Result<Runtime<MainThreadMessageLoop, W>, Error>
where
T: RuntimeHandler + 'static,
{
Runtime::new(self, MixRuntimeHnadler::RuntimeHandler(Box::new(handler)))
}
}
impl<W> RuntimeAttributes<MultiThreadMessageLoop, W> {
pub fn create_runtime<T>(&self, handler: T) -> Result<Runtime<MultiThreadMessageLoop, W>, Error>
where
T: RuntimeHandler + 'static,
{
Runtime::new(self, MixRuntimeHnadler::RuntimeHandler(Box::new(handler)))
}
}
impl<W> RuntimeAttributes<MessagePumpLoop, W> {
pub fn create_runtime<T>(&self, handler: T) -> Result<Runtime<MessagePumpLoop, W>, Error>
where
T: MessagePumpRuntimeHandler + 'static,
{
Runtime::new(
self,
MixRuntimeHnadler::MessagePumpRuntimeHandler(Box::new(handler)),
)
}
}
#[derive(Default)]
pub struct RuntimeAttributesBuilder<R, W>(RuntimeAttributes<R, W>);
impl<R, W> RuntimeAttributesBuilder<R, W> {
pub fn with_custom_scheme(mut self, scheme: CustomSchemeAttributes) -> Self {
self.0.custom_scheme = Some(scheme);
self
}
pub fn with_cache_path(mut self, value: &str) -> Self {
self.0.cache_path = Some(CString::new(value).unwrap());
self
}
pub fn with_root_cache_path(mut self, value: &str) -> Self {
self.0.root_cache_path = Some(CString::new(value).unwrap());
self
}
pub fn with_browser_subprocess_path(mut self, value: &str) -> Self {
self.0.browser_subprocess_path = Some(CString::new(value).unwrap());
self
}
pub fn with_framework_dir_path(mut self, value: &str) -> Self {
self.0.framework_dir_path = Some(CString::new(value).unwrap());
self
}
pub fn with_main_bundle_path(mut self, value: &str) -> Self {
self.0.main_bundle_path = Some(CString::new(value).unwrap());
self
}
pub fn with_user_agent(mut self, value: &str) -> Self {
self.0.user_agent = Some(CString::new(value).unwrap());
self
}
pub fn with_user_agent_product(mut self, value: &str) -> Self {
self.0.user_agent_product = Some(CString::new(value).unwrap());
self
}
pub fn with_locale(mut self, value: &str) -> Self {
self.0.locale = Some(CString::new(value).unwrap());
self
}
pub fn with_log_file(mut self, value: &str) -> Self {
self.0.log_file = Some(CString::new(value).unwrap());
self
}
pub fn with_log_severity(mut self, value: LogLevel) -> Self {
self.0.log_severity = Some(value);
self
}
pub fn with_javascript_flags(mut self, value: &str) -> Self {
self.0.javascript_flags = Some(CString::new(value).unwrap());
self
}
pub fn with_resources_dir_path(mut self, value: &str) -> Self {
self.0.resources_dir_path = Some(CString::new(value).unwrap());
self
}
pub fn with_locales_dir_path(mut self, value: &str) -> Self {
self.0.locales_dir_path = Some(CString::new(value).unwrap());
self
}
pub fn with_background_color(mut self, value: u32) -> Self {
self.0.background_color = value;
self
}
pub fn with_disable_signal_handlers(mut self, value: bool) -> Self {
self.0.disable_signal_handlers = value;
self
}
pub fn with_command_line_args_disabled(mut self, value: bool) -> Self {
self.0.command_line_args_disabled = value;
self
}
pub fn with_persist_session_cookies(mut self, value: bool) -> Self {
self.0.persist_session_cookies = value;
self
}
}
impl RuntimeAttributesBuilder<MultiThreadMessageLoop, NativeWindowWebView> {
pub fn build(mut self) -> RuntimeAttributes<MultiThreadMessageLoop, NativeWindowWebView> {
self.0.windowless_rendering_enabled = false;
self.0.multi_threaded_message_loop = true;
self.0.external_message_pump = false;
self.0
}
}
impl RuntimeAttributesBuilder<MainThreadMessageLoop, NativeWindowWebView> {
pub fn build(mut self) -> RuntimeAttributes<MainThreadMessageLoop, NativeWindowWebView> {
self.0.windowless_rendering_enabled = false;
self.0.multi_threaded_message_loop = false;
self.0.external_message_pump = false;
self.0
}
}
impl RuntimeAttributesBuilder<MessagePumpLoop, NativeWindowWebView> {
pub fn build(mut self) -> RuntimeAttributes<MessagePumpLoop, NativeWindowWebView> {
self.0.windowless_rendering_enabled = false;
self.0.multi_threaded_message_loop = false;
self.0.external_message_pump = true;
self.0
}
}
impl RuntimeAttributesBuilder<MultiThreadMessageLoop, WindowlessRenderWebView> {
pub fn build(mut self) -> RuntimeAttributes<MultiThreadMessageLoop, WindowlessRenderWebView> {
self.0.windowless_rendering_enabled = true;
self.0.multi_threaded_message_loop = true;
self.0.external_message_pump = false;
self.0
}
}
impl RuntimeAttributesBuilder<MainThreadMessageLoop, WindowlessRenderWebView> {
pub fn build(mut self) -> RuntimeAttributes<MainThreadMessageLoop, WindowlessRenderWebView> {
self.0.windowless_rendering_enabled = true;
self.0.multi_threaded_message_loop = false;
self.0.external_message_pump = false;
self.0
}
}
impl RuntimeAttributesBuilder<MessagePumpLoop, WindowlessRenderWebView> {
pub fn build(mut self) -> RuntimeAttributes<MessagePumpLoop, WindowlessRenderWebView> {
self.0.windowless_rendering_enabled = true;
self.0.multi_threaded_message_loop = false;
self.0.external_message_pump = true;
self.0
}
}
impl<R, W> Deref for RuntimeAttributesBuilder<R, W> {
type Target = RuntimeAttributes<R, W>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[allow(unused_variables)]
pub trait RuntimeHandler: Send + Sync {
fn on_context_initialized(&self) {}
}
#[allow(unused_variables)]
pub trait MessagePumpRuntimeHandler: RuntimeHandler {
fn on_schedule_message_pump_work(&self, delay: u64) {}
}
pub(crate) static RUNTIME_RUNNING: AtomicBool = AtomicBool::new(false);
pub(crate) struct IRuntime {
#[allow(unused)]
request_handler_factory: Option<Arc<ICustomRequestHandlerFactory>>,
initialized: Arc<AtomicBool>,
multi_threaded_message_loop: bool,
context: ThreadSafePointer<RuntimeContext>,
raw: Mutex<Arc<ThreadSafePointer<c_void>>>,
}
impl IRuntime {
fn new<R, W>(
attr: &RuntimeAttributes<R, W>,
handler: MixRuntimeHnadler,
) -> Result<Self, Error> {
if RUNTIME_RUNNING.load(Ordering::Relaxed) {
return Err(Error::RuntimeAlreadyExists);
}
if !is_main_thread() {
return Err(Error::NonUIThread);
}
let custom_scheme = attr
.custom_scheme
.as_ref()
.map(|attr| sys::CustomSchemeAttributes {
name: attr.name.as_raw(),
domain: attr.domain.as_raw(),
factory: attr.handler.as_raw().as_ptr(),
});
let options = sys::RuntimeSettings {
cache_path: attr.cache_path.as_raw(),
root_cache_path: attr.root_cache_path.as_raw(),
background_color: attr.background_color,
command_line_args_disabled: attr.command_line_args_disabled,
disable_signal_handlers: attr.disable_signal_handlers,
javascript_flags: attr.javascript_flags.as_raw(),
persist_session_cookies: attr.persist_session_cookies,
user_agent: attr.user_agent.as_raw(),
user_agent_product: attr.user_agent_product.as_raw(),
locale: attr.locale.as_raw(),
log_file: attr.log_file.as_raw(),
resources_dir_path: attr.resources_dir_path.as_raw(),
locales_dir_path: attr.locales_dir_path.as_raw(),
browser_subprocess_path: attr.browser_subprocess_path.as_raw(),
windowless_rendering_enabled: attr.windowless_rendering_enabled,
main_bundle_path: attr.main_bundle_path.as_raw(),
framework_dir_path: attr.framework_dir_path.as_raw(),
external_message_pump: attr.external_message_pump,
multi_threaded_message_loop: attr.multi_threaded_message_loop,
log_severity: attr.log_severity.unwrap_or(LogLevel::Off).into(),
custom_scheme: custom_scheme
.as_ref()
.map(|it| it as *const _)
.unwrap_or_else(null),
};
let initialized: Arc<AtomicBool> = Default::default();
let context: *mut RuntimeContext = Box::into_raw(Box::new(RuntimeContext {
initialized: initialized.clone(),
handler,
}));
let ptr = unsafe {
sys::create_runtime(
&options,
sys::RuntimeHandler {
context: context as _,
on_context_initialized: Some(on_context_initialized_callback),
on_schedule_message_pump_work: Some(on_schedule_message_pump_work_callback),
},
)
};
let raw = if ptr.is_null() {
return Err(Error::FailedToCreateRuntime);
} else {
Arc::new(ThreadSafePointer::new(ptr))
};
{
let args = Args::default();
if attr.multi_threaded_message_loop {
let raw = raw.clone();
thread::spawn(move || unsafe {
sys::execute_runtime(raw.as_ptr(), args.size() as _, args.as_ptr() as _);
});
} else {
unsafe {
sys::execute_runtime(raw.as_ptr(), args.size() as _, args.as_ptr() as _);
}
}
}
RUNTIME_RUNNING.store(true, Ordering::Relaxed);
Ok(Self {
initialized,
raw: Mutex::new(raw),
context: ThreadSafePointer::new(context),
multi_threaded_message_loop: attr.multi_threaded_message_loop,
request_handler_factory: attr
.custom_scheme
.as_ref()
.map(|it| it.handler.get_shared_ref()),
})
}
fn is_initialized(&self) -> bool {
self.initialized.load(Ordering::Relaxed)
}
pub(crate) fn get_raw(&self) -> Arc<ThreadSafePointer<c_void>> {
self.raw.lock().clone()
}
}
impl Drop for IRuntime {
fn drop(&mut self) {
if self.multi_threaded_message_loop {
MainThreadMessageLoop.quit();
}
RUNTIME_RUNNING.store(false, Ordering::Relaxed);
unsafe {
sys::close_runtime(self.raw.lock().as_ptr());
}
drop(unsafe { Box::from_raw(self.context.as_ptr()) });
}
}
#[derive(Clone)]
pub struct Runtime<R, W> {
_r: PhantomData<R>,
_w: PhantomData<W>,
inner: Arc<IRuntime>,
}
impl<R, W> Runtime<R, W> {
pub(crate) fn new(
attr: &RuntimeAttributes<R, W>,
handler: MixRuntimeHnadler,
) -> Result<Self, Error> {
Ok(Self {
_r: PhantomData,
_w: PhantomData,
inner: Arc::new(IRuntime::new(attr, handler)?),
})
}
}
impl<R, W> GetSharedRef for Runtime<R, W> {
type Ref = Arc<IRuntime>;
fn get_shared_ref(&self) -> Self::Ref {
self.inner.clone()
}
}
impl<R> Runtime<R, WindowlessRenderWebView> {
pub fn create_webview<T>(
&self,
url: &str,
attr: WebViewAttributes,
handler: T,
) -> Result<WebView<WindowlessRenderWebView>, Error>
where
T: WindowlessRenderWebViewHandler + 'static,
R: Clone,
{
if !self.inner.is_initialized() {
return Err(Error::RuntimeNotInitialization);
}
WebView::new(
self,
url,
attr,
MixWebviewHnadler::WindowlessRenderWebViewHandler(Box::new(handler)),
)
}
}
impl<R> Runtime<R, NativeWindowWebView> {
pub fn create_webview<T>(
&self,
url: &str,
attr: WebViewAttributes,
handler: T,
) -> Result<WebView<NativeWindowWebView>, Error>
where
T: WebViewHandler + 'static,
R: Clone,
{
if !self.inner.is_initialized() {
return Err(Error::RuntimeNotInitialization);
}
WebView::new(
self,
url,
attr,
MixWebviewHnadler::WebViewHandler(Box::new(handler)),
)
}
}
impl From<LogLevel> for sys::LogLevel {
fn from(val: LogLevel) -> Self {
match val {
LogLevel::Off => sys::LogLevel::WEW_LOG_DISABLE,
LogLevel::Info => sys::LogLevel::WEW_LOG_INFO,
LogLevel::Error => sys::LogLevel::WEW_LOG_ERROR,
LogLevel::Warn => sys::LogLevel::WEW_LOG_WARNING,
LogLevel::Debug => sys::LogLevel::WEW_LOG_DEBUG,
LogLevel::Trace => sys::LogLevel::WEW_LOG_VERBOSE,
}
}
}
struct RuntimeContext {
handler: MixRuntimeHnadler,
initialized: Arc<AtomicBool>,
}
pub(crate) enum MixRuntimeHnadler {
RuntimeHandler(Box<dyn RuntimeHandler>),
MessagePumpRuntimeHandler(Box<dyn MessagePumpRuntimeHandler>),
}
extern "C" fn on_context_initialized_callback(context: *mut c_void) {
if context.is_null() {
return;
}
let context = unsafe { &*(context as *mut RuntimeContext) };
context.initialized.store(true, Ordering::Relaxed);
match &context.handler {
MixRuntimeHnadler::RuntimeHandler(handler) => handler.on_context_initialized(),
MixRuntimeHnadler::MessagePumpRuntimeHandler(handler) => handler.on_context_initialized(),
}
}
extern "C" fn on_schedule_message_pump_work_callback(delay: i64, context: *mut c_void) {
if context.is_null() {
return;
}
let context = unsafe { &*(context as *mut RuntimeContext) };
if let MixRuntimeHnadler::MessagePumpRuntimeHandler(handler) = &context.handler {
handler.on_schedule_message_pump_work(delay as u64);
}
}