use std::fs::File;
use std::io::{BufReader, Error, ErrorKind, Read, Result};
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{mpsc, Arc, RwLock};
use std::{env, process};
use crate::cef_utils::vec4_to_cef_color;
use crate::cx_web::parse_keyboard_event_from_js;
use crate::zerde::*;
use crate::*;
use wrflib_cef::{
create_browser_sync, execute_process, initialize, App, Browser, BrowserProcessHandler, BrowserSettings, Client, CommandLine,
ContextMenuHandler, Frame, MenuModel, ProcessId, ProcessMessage, RenderProcessHandler, RequestHandler, ResourceHandler,
ResourceRequestHandler, Settings, V8ArrayBufferReleaseCallback, V8Context, V8PropertyAttribute, V8Value, WindowHandle,
WindowInfo,
};
pub(crate) enum MaybeCefBrowser {
#[allow(dead_code)] Initialized(CefBrowser),
Uninitialized {
messages_channel: (mpsc::Sender<CallJsEvent>, mpsc::Receiver<CallJsEvent>),
call_rust_in_same_thread_sync_fn: Option<CallRustInSameThreadSyncFn>,
},
}
impl MaybeCefBrowser {
pub(crate) fn new() -> Self {
Self::Uninitialized { messages_channel: mpsc::channel(), call_rust_in_same_thread_sync_fn: None }
}
pub(crate) fn call_js(&mut self, name: &str, params: Vec<WrfParam>) {
let call_js_event = CallJsEvent { name: name.to_string(), params };
match self {
MaybeCefBrowser::Initialized(cef_browser) => cef_browser.call_js(call_js_event),
MaybeCefBrowser::Uninitialized { messages_channel, .. } => messages_channel.0.send(call_js_event).unwrap(),
}
}
pub(crate) fn on_call_rust_in_same_thread_sync(&mut self, func: CallRustInSameThreadSyncFn) {
match self {
MaybeCefBrowser::Initialized(cef_browser) => cef_browser.on_call_rust_in_same_thread_sync(func),
MaybeCefBrowser::Uninitialized { call_rust_in_same_thread_sync_fn, .. } => {
*call_rust_in_same_thread_sync_fn = Some(func)
}
}
}
#[allow(dead_code)] pub(crate) fn initialize(
&mut self,
size: Vec2,
url: &str,
parent_window: WindowHandle,
#[cfg(feature = "cef-server")] get_resource_url_callback: Option<GetResourceUrlCallback>,
) {
match self {
MaybeCefBrowser::Initialized(_) => {
panic!("CEF is already initialized; we currently support only one browser at a time")
}
MaybeCefBrowser::Uninitialized { messages_channel, call_rust_in_same_thread_sync_fn } => {
let mut channel = mpsc::channel();
std::mem::swap(&mut channel, messages_channel);
let mut cef_browser = CefBrowser::new(
size,
url,
parent_window,
channel,
#[cfg(feature = "cef-server")]
get_resource_url_callback,
);
if let Some(func) = call_rust_in_same_thread_sync_fn {
cef_browser.on_call_rust_in_same_thread_sync(*func);
}
*self = MaybeCefBrowser::Initialized(cef_browser);
}
}
}
#[allow(dead_code)] pub(crate) fn set_mouse_cursor(&mut self, cursor: MouseCursor) {
match self {
MaybeCefBrowser::Initialized(cef_browser) => cef_browser.set_mouse_cursor(cursor),
MaybeCefBrowser::Uninitialized { .. } => {}
}
}
#[allow(dead_code)] pub(crate) fn set_ime_position(&mut self, pos: Vec2) {
match self {
MaybeCefBrowser::Initialized(cef_browser) => cef_browser.set_ime_position(pos),
MaybeCefBrowser::Uninitialized { .. } => {}
}
}
#[allow(dead_code)] pub(crate) fn return_to_js(&mut self, callback_id: u32, mut params: Vec<WrfParam>) {
params.insert(0, format!("{}", callback_id).into_param());
self.call_js("_wrflibReturnParams", params);
}
}
#[derive(Debug)]
pub(crate) struct CallJsEvent {
pub(crate) name: String,
pub(crate) params: Vec<WrfParam>,
}
struct MyRenderProcessHandler {
receive_channel: Arc<mpsc::Receiver<CallJsEvent>>,
ready_to_process: Arc<AtomicBool>,
call_rust_in_same_thread_sync_fn: Arc<RwLock<Option<CallRustInSameThreadSyncFn>>>,
}
fn make_mutable_buffer<T>(param_type: u32, mut buffer: Vec<T>) -> V8Value {
let ptr = buffer.as_mut_ptr();
let len = buffer.len();
let callback = Arc::new(MyV8ArrayBufferReleaseCallback { buffer: Arc::new(buffer) });
let buffer_data = V8Value::create_array(3);
let v8_buffer = V8Value::create_array_buffer(ptr as *const u8, len * std::mem::size_of::<T>(), callback);
buffer_data.set_value_byindex(0, &v8_buffer);
buffer_data.set_value_byindex(2, &V8Value::create_uint(param_type));
buffer_data
}
fn make_readonly_buffer<T>(param_type: u32, buffer: Arc<Vec<T>>) -> V8Value {
let callback = Arc::new(MyV8ArrayBufferReleaseCallback { buffer: Arc::clone(&buffer) });
let buffer_data = V8Value::create_array(3);
let v8_buffer = V8Value::create_array_buffer(buffer.as_ptr() as *const u8, buffer.len() * std::mem::size_of::<T>(), callback);
buffer_data.set_value_byindex(0, &v8_buffer);
let arc_ptr = V8Value::create_uint(Arc::as_ptr(&buffer) as u32);
buffer_data.set_value_byindex(1, &arc_ptr);
buffer_data.set_value_byindex(2, &V8Value::create_uint(param_type));
buffer_data
}
fn make_buffers_and_arc_ptrs(params: Vec<WrfParam>) -> V8Value {
let values = V8Value::create_array(params.len());
for (index, param) in params.into_iter().enumerate() {
let param_type = match ¶m {
WrfParam::String(_) => WRF_PARAM_STRING,
WrfParam::ReadOnlyU8Buffer(_) => WRF_PARAM_READ_ONLY_UINT8_BUFFER,
WrfParam::MutableU8Buffer(_) => WRF_PARAM_UINT8_BUFFER,
WrfParam::ReadOnlyF32Buffer(_) => WRF_PARAM_READ_ONLY_FLOAT32_BUFFER,
WrfParam::MutableF32Buffer(_) => WRF_PARAM_FLOAT32_BUFFER,
};
let value = match param {
WrfParam::String(str) => V8Value::create_string(&str),
WrfParam::ReadOnlyU8Buffer(buffer) => make_readonly_buffer(param_type, buffer),
WrfParam::MutableU8Buffer(buffer) => make_mutable_buffer(param_type, buffer),
WrfParam::ReadOnlyF32Buffer(buffer) => make_readonly_buffer(param_type, buffer),
WrfParam::MutableF32Buffer(buffer) => make_mutable_buffer(param_type, buffer),
};
values.set_value_byindex(index, &value);
}
values
}
fn get_wrf_params(array: &V8Value) -> Vec<WrfParam> {
assert!(array.is_array());
let mut params = Vec::new();
for index in 0..array.get_array_length() {
let param = array.get_value_byindex(index).unwrap();
let wrf_param = if param.is_string() {
param.get_string_value().into_param()
} else {
let array_buffer = param.get_value_byindex(0).unwrap();
let param_type = param.get_value_byindex(1).unwrap().get_int_value() as u32;
match param_type {
WRF_PARAM_READ_ONLY_UINT8_BUFFER => {
let callback =
array_buffer.get_array_buffer_release_callback::<MyV8ArrayBufferReleaseCallback<u8>>().unwrap();
Arc::clone(&callback.buffer).into_param()
}
WRF_PARAM_UINT8_BUFFER => {
let callback =
array_buffer.get_array_buffer_release_callback::<MyV8ArrayBufferReleaseCallback<u8>>().unwrap();
callback.buffer.to_vec().into_param()
}
WRF_PARAM_FLOAT32_BUFFER => {
let callback =
array_buffer.get_array_buffer_release_callback::<MyV8ArrayBufferReleaseCallback<f32>>().unwrap();
callback.buffer.to_vec().into_param()
}
WRF_PARAM_READ_ONLY_FLOAT32_BUFFER => {
let callback =
array_buffer.get_array_buffer_release_callback::<MyV8ArrayBufferReleaseCallback<f32>>().unwrap();
Arc::clone(&callback.buffer).into_param()
}
v => panic!("Invalid param type: {}", v),
}
};
params.push(wrf_param);
}
params
}
fn process_messages(receive_channel: &mpsc::Receiver<CallJsEvent>, frame: &Frame) {
while let Ok(data) = receive_channel.try_recv() {
let context = frame.get_v8context().unwrap();
assert!(context.enter());
let object = context.get_global().unwrap();
let transformed_params = make_buffers_and_arc_ptrs(data.params);
let obj_name = V8Value::create_string(&data.name);
let id = universal_rand::random_128();
let key_name = format!("name_{}", id);
let key_params = format!("params_{}", id);
assert!(object.set_value_bykey(&key_name, &obj_name, V8PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE));
assert!(object.set_value_bykey(&key_params, &transformed_params, V8PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE));
let code = format!(
r#"
window.fromCefCallJsFunction(window.{}, window.{});
delete window.{}; delete window.{};
"#,
key_name, key_params, key_name, key_params,
);
assert!(context.exit());
let script_url = "".to_string();
let start_line = 0;
frame.execute_javascript(&code, &script_url, start_line);
}
}
struct MyV8ArrayBufferReleaseCallback<T> {
#[allow(dead_code)]
buffer: Arc<Vec<T>>,
}
impl<T> V8ArrayBufferReleaseCallback for MyV8ArrayBufferReleaseCallback<T> {}
fn create_array_buffer<T: Default + Clone>(count: i32) -> V8Value {
let callback = Arc::new(MyV8ArrayBufferReleaseCallback::<T> { buffer: Arc::new(vec![T::default(); count as usize]) });
let value = V8Value::create_array(2);
let arc_ptr = V8Value::create_uint(Arc::as_ptr(&callback.buffer) as u32);
let v8_buffer = V8Value::create_array_buffer(
callback.buffer.as_ptr() as *const u8,
callback.buffer.len() * std::mem::size_of::<T>(),
callback,
);
value.set_value_byindex(0, &v8_buffer);
value.set_value_byindex(1, &arc_ptr);
value
}
impl RenderProcessHandler for MyRenderProcessHandler {
fn on_process_message_received(
&self,
_browser: &Browser,
frame: &Frame,
_source_process: ProcessId,
_message: &ProcessMessage,
) -> bool {
if self.ready_to_process.load(Ordering::Relaxed) {
process_messages(&self.receive_channel, frame);
}
true
}
fn on_context_created(&self, _browser: &Browser, frame: &Frame, context: &V8Context) {
let window = context.get_global().unwrap();
assert!(window.set_fn_value("cefCallRust", (), |_name, _obj, args, _other_data| {
let name = args[0].get_string_value();
let params = get_wrf_params(&args[1]);
let callback_id = args[2].get_uint_value();
Cx::send_event_from_any_thread(Event::SystemEvent(SystemEvent::WebRustCall(Some(WebRustCallEvent {
name,
params,
callback_id,
}))));
None
}));
assert!(window.set_fn_value(
"cefCallRustInSameThreadSync",
self.call_rust_in_same_thread_sync_fn.clone(),
|_name, _obj, args, call_rust_in_same_thread_sync_fn| {
let name = args[0].get_string_value();
let params = get_wrf_params(&args[1]);
if let Some(func) = *call_rust_in_same_thread_sync_fn.read().unwrap() {
let return_buffers = func(&name, params);
Some(Ok(make_buffers_and_arc_ptrs(return_buffers)))
} else {
panic!("call_rust_in_same_thread_sync was called without call_rust_in_same_thread_sync_fn being set");
}
}
));
assert!(window.set_fn_value(
"cefReadyForMessages",
(Arc::clone(&self.receive_channel), frame.clone(), self.ready_to_process.clone()),
|_, _, _, (receive_channel, frame, ready_to_process)| {
ready_to_process.store(true, Ordering::Relaxed);
process_messages(receive_channel, frame);
None
}
));
assert!(window.set_fn_value("cefCreateArrayBuffer", (), |_, _, args, _other_data| {
if args.is_empty() {
return Some(Err("Undefined buffer size".to_string()));
}
let count = args[0].get_int_value();
let param_type = args[1].get_int_value() as u32;
let value = match param_type {
WRF_PARAM_READ_ONLY_UINT8_BUFFER | WRF_PARAM_UINT8_BUFFER => create_array_buffer::<u8>(count),
WRF_PARAM_FLOAT32_BUFFER | WRF_PARAM_READ_ONLY_FLOAT32_BUFFER => create_array_buffer::<f32>(count),
v => panic!("Invalid param type: {}", v),
};
Some(Ok(value))
}));
assert!(window.set_fn_value("cefHandleKeyboardEvent", (), |_name, _obj, args, _other_data| {
let callback = args[0].get_array_buffer_release_callback::<MyV8ArrayBufferReleaseCallback<u8>>();
let buffer = &callback.unwrap().buffer;
let mut zerde_parser = ZerdeParser::from(buffer.as_ptr() as u64);
let msg_type = zerde_parser.parse_u32();
let event = parse_keyboard_event_from_js(msg_type, &mut zerde_parser);
Cx::send_event_from_any_thread(event);
None
}));
assert!(window.set_fn_value("cefTriggerCut", frame.clone(), |_name, _obj, _args, frame| {
frame.cut();
None
}));
assert!(window.set_fn_value("cefTriggerCopy", frame.clone(), |_name, _obj, _args, frame| {
frame.copy();
None
}));
assert!(window.set_fn_value("cefTriggerPaste", frame.clone(), |_name, _obj, _args, frame| {
frame.paste();
None
}));
assert!(window.set_fn_value("cefTriggerSelectAll", frame.clone(), |_name, _obj, _args, frame| {
frame.select_all();
None
}));
}
}
struct MyBrowserProcessHandler {}
impl BrowserProcessHandler for MyBrowserProcessHandler {
fn on_schedule_message_pump_work(&self, delay_ms: i64) {
Cx::cef_schedule_message_pump_work(delay_ms);
}
}
struct MyApp {
render_process_handler: Arc<MyRenderProcessHandler>,
browser_process_handler: Arc<MyBrowserProcessHandler>,
}
impl App for MyApp {
type OutBrowserProcessHandler = MyBrowserProcessHandler;
type OutRenderProcessHandler = MyRenderProcessHandler;
fn on_before_command_line_processing(&self, _process_type: &str, command_line: &CommandLine) {
#[cfg(target_os = "macos")]
command_line.append_switch("use-mock-keychain");
command_line.append_switch("single-process");
}
fn get_render_process_handler(&self) -> Option<Arc<Self::OutRenderProcessHandler>> {
Some(self.render_process_handler.clone())
}
fn get_browser_process_handler(&self) -> Option<Arc<Self::OutBrowserProcessHandler>> {
Some(self.browser_process_handler.clone())
}
}
struct MyContextMenuHandler {}
impl ContextMenuHandler for MyContextMenuHandler {
fn on_before_context_menu(&self, _browser: &Browser, _frame: &Frame, model: &MenuModel) {
model.clear();
}
}
pub type GetResourceUrlCallback = fn(&str, &str) -> String;
struct MyResourceHandler {
url: RwLock<String>,
mime_type: RwLock<Option<String>>,
contents: RwLock<Option<BufReader<File>>>,
get_resource_url_callback: Option<GetResourceUrlCallback>,
}
impl ResourceHandler for MyResourceHandler {
fn open(&self, url: &str) -> bool {
if self.contents.read().unwrap().is_some() {
panic!("Already loading this file!!!")
}
let url = {
if let Some(callback) = self.get_resource_url_callback {
#[cfg(not(all(target_os = "macos", feature = "cef-server")))]
let current_directory = "".to_string();
#[cfg(all(target_os = "macos", feature = "cef-server"))]
let current_directory = get_bundle_directory();
callback(url, ¤t_directory)
} else {
url.to_string()
}
};
*self.mime_type.write().unwrap() = match Path::new(&url).extension().unwrap().to_str().unwrap() {
"html" => Some("text/html".to_string()),
"js" => Some("text/javascript".to_string()),
"css" => Some("text/css".to_string()),
"wasm" => Some("application/wasm".to_string()),
"ico" => Some("image/vnd.microsoft.icon".to_string()),
"bin" => Some("application/octet-stream".to_string()),
_ => {
println!("Unknown mime type for url {}", &url);
None
}
};
*self.contents.write().unwrap() = match File::open(&url) {
Ok(f) => Some(BufReader::new(f)),
Err(e) => {
println!("Cannot read file: {}, {}", &url, &e);
None
}
};
*self.url.write().unwrap() = url;
self.contents.read().unwrap().is_some()
}
fn get_mime_type(&self) -> Option<String> {
self.mime_type.read().unwrap().clone()
}
fn get_status_code(&self) -> i32 {
if self.contents.read().unwrap().is_some() {
200
} else {
404
}
}
fn read(&self, buf: &mut [u8]) -> Result<usize> {
if let Some(contents) = self.contents.write().unwrap().as_mut() {
contents.read(buf)
} else {
Err(Error::new(ErrorKind::Unsupported, "Unsupported operation"))
}
}
}
struct MyResourceRequestHandler {
resource_handler: Arc<MyResourceHandler>,
}
impl ResourceRequestHandler for MyResourceRequestHandler {
type OutResourceHandler = MyResourceHandler;
fn get_resource_handler(&self) -> Option<Arc<Self::OutResourceHandler>> {
Some(self.resource_handler.clone())
}
}
struct MyRequestHandler {
handlers: RwLock<Vec<Arc<MyResourceRequestHandler>>>,
get_resource_url_callback: Option<GetResourceUrlCallback>,
}
impl RequestHandler for MyRequestHandler {
type OutResourceRequestHandler = MyResourceRequestHandler;
fn get_resource_request_handler(&self, url: &str) -> Option<Arc<Self::OutResourceRequestHandler>> {
let handler = Arc::new(MyResourceRequestHandler {
resource_handler: Arc::new(MyResourceHandler {
url: RwLock::new(url.to_string()),
mime_type: RwLock::new(None),
contents: RwLock::new(None),
get_resource_url_callback: self.get_resource_url_callback,
}),
});
self.handlers.write().unwrap().push(handler.clone());
Some(handler)
}
}
struct MyClient {
context_menu_handler: Arc<MyContextMenuHandler>,
#[cfg(feature = "cef-server")]
request_handler: Arc<MyRequestHandler>,
}
impl Client for MyClient {
type OutContextMenuHandler = MyContextMenuHandler;
type OutRequestHandler = MyRequestHandler;
fn get_context_menu_handler(&self) -> Option<Arc<Self::OutContextMenuHandler>> {
Some(self.context_menu_handler.clone())
}
fn get_request_handler(&self) -> Option<Arc<Self::OutRequestHandler>> {
#[cfg(not(feature = "cef-server"))]
{
None
}
#[cfg(feature = "cef-server")]
Some(self.request_handler.clone())
}
}
pub(crate) struct CefBrowser {
pub(crate) browser: Arc<Browser>,
call_rust_in_same_thread_sync_fn: Arc<RwLock<Option<CallRustInSameThreadSyncFn>>>,
send_channel: mpsc::Sender<CallJsEvent>,
}
impl CefBrowser {
#[allow(dead_code)] fn new(
size: Vec2,
url: &str,
parent_window: WindowHandle,
(tx, rx): (mpsc::Sender<CallJsEvent>, mpsc::Receiver<CallJsEvent>),
#[cfg(feature = "cef-server")] get_resource_url_callback: Option<GetResourceUrlCallback>,
) -> Self {
let call_rust_in_same_thread_sync_fn = Arc::new(RwLock::new(None));
let app = Arc::new(MyApp {
render_process_handler: Arc::new(MyRenderProcessHandler {
receive_channel: Arc::new(rx),
ready_to_process: Arc::new(AtomicBool::new(false)),
call_rust_in_same_thread_sync_fn: Arc::clone(&call_rust_in_same_thread_sync_fn),
}),
browser_process_handler: Arc::new(MyBrowserProcessHandler {}),
});
let result_code = execute_process(&app);
if result_code != -1 {
process::exit(0);
}
let mut settings = Settings::default();
let framework_dir_path = env::current_exe()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap()
.join("Frameworks")
.join("Chromium Embedded Framework.framework");
settings.framework_dir_path = Some(framework_dir_path.to_str().unwrap());
#[allow(unused_variables)]
let main_bundle_path = env::current_exe().unwrap().parent().unwrap().join("dummy.app");
#[cfg(feature = "cef-debug")]
{
if !main_bundle_path.exists() {
std::fs::create_dir(&main_bundle_path).unwrap();
std::fs::create_dir(&main_bundle_path.join("Contents")).unwrap();
std::fs::write(
&main_bundle_path.join("Contents").join("Info.plist"),
r#"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN""http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>dummy</string>
<key>CFBundleIdentifier</key>
<string>com.dummy</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Dummy</string>
<key>CFBundleShortVersionString</key>
<string>0.0.1</string>
<key>CFBundleVersion</key>
<string>20210625.235709</string>
</dict>
</plist>
"#,
)
.unwrap();
}
settings.main_bundle_path = Some(main_bundle_path.to_str().unwrap());
}
settings.external_message_pump = true;
settings.log_file = Some("cef.log");
#[cfg(feature = "cef-debug")]
{
settings.log_severity = wrflib_cef::LogSeverity::LOGSEVERITY_VERBOSE;
}
initialize(settings, &app);
let window_info = WindowInfo { width: size.x as u32, height: size.y as u32, parent_window, ..Default::default() };
let browser_settings = BrowserSettings { background_color: vec4_to_cef_color(&Vec4::color("3")), ..Default::default() };
let client = Arc::new(MyClient {
context_menu_handler: Arc::new(MyContextMenuHandler {}),
#[cfg(feature = "cef-server")]
request_handler: Arc::new(MyRequestHandler { handlers: RwLock::new(vec![]), get_resource_url_callback }),
});
let browser = Arc::new(create_browser_sync(window_info, &client, url, browser_settings));
#[cfg(feature = "cef-dev-tools")]
browser.get_host().unwrap().show_dev_tools();
Self { browser, send_channel: tx, call_rust_in_same_thread_sync_fn }
}
fn call_js(&mut self, call_js_event: CallJsEvent) {
if self.send_channel.send(call_js_event).is_err() {
log!("Error while sending CallJsEvent");
}
let frame = self.browser.get_main_frame().unwrap();
let message = ProcessMessage::create("call_js");
frame.send_process_message(ProcessId::PID_RENDERER, &message);
}
fn set_mouse_cursor(&mut self, cursor: MouseCursor) {
let code = format!("if (window.fromCefSetMouseCursor) window.fromCefSetMouseCursor({});", cursor as u8);
let script_url = "".to_string();
let start_line = 0;
let frame = self.browser.get_main_frame().unwrap();
frame.execute_javascript(&code, &script_url, start_line);
}
fn set_ime_position(&mut self, pos: Vec2) {
let code = format!("if (window.fromCefSetIMEPosition) window.fromCefSetIMEPosition({}, {});", pos.x, pos.y);
let script_url = "".to_string();
let start_line = 0;
let frame = self.browser.get_main_frame().unwrap();
frame.execute_javascript(&code, &script_url, start_line);
}
fn on_call_rust_in_same_thread_sync(&mut self, func: CallRustInSameThreadSyncFn) {
*self.call_rust_in_same_thread_sync_fn.write().unwrap() = Some(func);
}
}
impl Cx {
#[allow(dead_code)] pub(crate) fn cef_do_message_loop_work(&mut self) {
if let MaybeCefBrowser::Initialized(_) = self.cef_browser {
wrflib_cef::do_message_loop_work();
}
}
}