feat: add custom CEF V8 extensions support (#17)
* feat: Register extensions in render process - Add CefExtensions type to hold V8 extension code - Pass extensions through BrowserProcessAppBuilder - Register extensions in RenderProcessHandler on WebKit initialization - Decode JSON extensions from command line switch - Prefix extension names with "v8/" per CEF convention - Include actual JSON in error messages for debugging l * feat: refactor window.cef API and register as CEF extension * fix: remove debug print statements in render process handler * refactor: centralize EXTENSIONS_SWITCH constant in util.rs * fmt * refactor: implement Default trait for CefApiHandler * docs: add documentation for CefApiHandler and its JavaScript API functions --------- Co-authored-by: not-elm <elmgameinfo@gmail.com>
This commit is contained in:
@@ -5,6 +5,7 @@ mod client_handler;
|
||||
mod command_line_config;
|
||||
mod context_menu_handler;
|
||||
mod display_handler;
|
||||
mod extensions;
|
||||
mod localhost;
|
||||
mod message_pump;
|
||||
mod renderer_handler;
|
||||
@@ -16,6 +17,7 @@ pub use browsers::*;
|
||||
pub use client_handler::*;
|
||||
pub use command_line_config::*;
|
||||
pub use context_menu_handler::*;
|
||||
pub use extensions::*;
|
||||
pub use localhost::*;
|
||||
pub use message_pump::*;
|
||||
pub use renderer_handler::*;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::browser_process::CefExtensions;
|
||||
use crate::browser_process::CommandLineConfig;
|
||||
use crate::browser_process::MessageLoopTimer;
|
||||
use crate::browser_process::browser_process_handler::BrowserProcessHandlerBuilder;
|
||||
@@ -17,17 +18,20 @@ pub struct BrowserProcessAppBuilder {
|
||||
object: *mut RcImpl<_cef_app_t, Self>,
|
||||
message_loop_working_requester: Sender<MessageLoopTimer>,
|
||||
config: CommandLineConfig,
|
||||
extensions: CefExtensions,
|
||||
}
|
||||
|
||||
impl BrowserProcessAppBuilder {
|
||||
pub fn build(
|
||||
message_loop_working_requester: Sender<MessageLoopTimer>,
|
||||
config: CommandLineConfig,
|
||||
extensions: CefExtensions,
|
||||
) -> cef::App {
|
||||
cef::App::new(Self {
|
||||
object: core::ptr::null_mut(),
|
||||
message_loop_working_requester,
|
||||
config,
|
||||
extensions,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -43,6 +47,7 @@ impl Clone for BrowserProcessAppBuilder {
|
||||
object,
|
||||
message_loop_working_requester: self.message_loop_working_requester.clone(),
|
||||
config: self.config.clone(),
|
||||
extensions: self.extensions.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,7 +70,6 @@ impl ImplApp for BrowserProcessAppBuilder {
|
||||
let Some(command_line) = command_line else {
|
||||
return;
|
||||
};
|
||||
|
||||
for switch in &self.config.switches {
|
||||
command_line.append_switch(Some(&(*switch).into()));
|
||||
}
|
||||
@@ -84,6 +88,7 @@ impl ImplApp for BrowserProcessAppBuilder {
|
||||
fn browser_process_handler(&self) -> Option<BrowserProcessHandler> {
|
||||
Some(BrowserProcessHandlerBuilder::build(
|
||||
self.message_loop_working_requester.clone(),
|
||||
self.extensions.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::prelude::MessageLoopTimer;
|
||||
use crate::prelude::{CefExtensions, EXTENSIONS_SWITCH, MessageLoopTimer};
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::*;
|
||||
use std::sync::mpsc::Sender;
|
||||
@@ -9,15 +9,18 @@ use std::sync::mpsc::Sender;
|
||||
pub struct BrowserProcessHandlerBuilder {
|
||||
object: *mut RcImpl<cef_dll_sys::cef_browser_process_handler_t, Self>,
|
||||
message_loop_working_requester: Sender<MessageLoopTimer>,
|
||||
extensions: CefExtensions,
|
||||
}
|
||||
|
||||
impl BrowserProcessHandlerBuilder {
|
||||
pub fn build(
|
||||
message_loop_working_requester: Sender<MessageLoopTimer>,
|
||||
extensions: CefExtensions,
|
||||
) -> BrowserProcessHandler {
|
||||
BrowserProcessHandler::new(Self {
|
||||
object: core::ptr::null_mut(),
|
||||
message_loop_working_requester,
|
||||
extensions,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -48,6 +51,7 @@ impl Clone for BrowserProcessHandlerBuilder {
|
||||
Self {
|
||||
object,
|
||||
message_loop_working_requester: self.message_loop_working_requester.clone(),
|
||||
extensions: self.extensions.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,6 +68,15 @@ impl ImplBrowserProcessHandler for BrowserProcessHandlerBuilder {
|
||||
command_line.append_switch(Some(&"ignore-certificate-errors".into()));
|
||||
command_line.append_switch(Some(&"ignore-ssl-errors".into()));
|
||||
command_line.append_switch(Some(&"enable-logging=stderr".into()));
|
||||
// Pass extensions to render process via command line
|
||||
if !self.extensions.is_empty()
|
||||
&& let Ok(json) = serde_json::to_string(&self.extensions.0)
|
||||
{
|
||||
command_line.append_switch_with_value(
|
||||
Some(&EXTENSIONS_SWITCH.into()),
|
||||
Some(&json.as_str().into()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_schedule_message_pump_work(&self, delay_ms: i64) {
|
||||
|
||||
47
crates/bevy_cef_core/src/browser_process/extensions.rs
Normal file
47
crates/bevy_cef_core/src/browser_process/extensions.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use bevy::platform::collections::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Custom JavaScript extensions to register via CEF's `register_extension`.
|
||||
///
|
||||
/// Extensions are global to all webviews and loaded before any page scripts run.
|
||||
/// Use existing `window.cef.emit()`, `window.cef.listen()`, and `window.cef.brp()`
|
||||
/// APIs within your extension code for Bevy communication.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use bevy_cef_core::prelude::*;
|
||||
///
|
||||
/// let extensions = CefExtensions::new()
|
||||
/// .add("myGame", r#"
|
||||
/// var myGame = {
|
||||
/// sendScore: function(score) {
|
||||
/// window.cef.emit('score_update', { score: score });
|
||||
/// }
|
||||
/// };
|
||||
/// "#);
|
||||
/// ```
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct CefExtensions(pub HashMap<String, String>);
|
||||
|
||||
impl CefExtensions {
|
||||
/// Creates a new empty extensions collection.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Adds a JavaScript extension.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `name` - Extension name (will be prefixed with `v8/` internally)
|
||||
/// * `code` - JavaScript code defining the extension's API
|
||||
pub fn add(mut self, name: impl Into<String>, code: impl Into<String>) -> Self {
|
||||
self.0.insert(name.into(), code.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns true if no extensions are registered.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,6 @@ pub mod prelude {
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use crate::debug::*;
|
||||
pub use crate::render_process::app::*;
|
||||
pub use crate::render_process::brp::*;
|
||||
pub use crate::render_process::emit::*;
|
||||
pub use crate::render_process::execute_render_process;
|
||||
pub use crate::render_process::render_process_handler::*;
|
||||
pub use crate::util::*;
|
||||
|
||||
@@ -3,9 +3,7 @@ use cef::args::Args;
|
||||
use cef::{api_hash, execute_process, sys};
|
||||
|
||||
pub mod app;
|
||||
pub mod brp;
|
||||
pub mod emit;
|
||||
pub mod listen;
|
||||
pub mod cef_api_handler;
|
||||
pub mod render_process_handler;
|
||||
|
||||
/// Execute the CEF render process.
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
use crate::prelude::{BRP_PROMISES, PROCESS_MESSAGE_BRP, v8_value_to_json};
|
||||
use cef::rc::{ConvertParam, ConvertReturnValue, Rc, RcImpl};
|
||||
use cef::{
|
||||
CefString, Frame, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Handler, ImplV8Value,
|
||||
ProcessId, V8Handler, V8Value, WrapV8Handler, process_message_create, sys,
|
||||
v8_value_create_promise, v8_value_create_string,
|
||||
};
|
||||
use cef_dll_sys::{_cef_v8_handler_t, _cef_v8_value_t, cef_process_id_t, cef_string_t};
|
||||
use std::os::raw::c_int;
|
||||
use std::ptr::write;
|
||||
|
||||
/// Implements the `window.brp` function in JavaScript.
|
||||
///
|
||||
/// The function definition is `async <T>(request: BrpRequest) -> Promise<T>`.
|
||||
///
|
||||
/// The flow from the execution of the function to the return of the result to the Javascript side is as follows:
|
||||
/// 1. Send `BrpRequest` to the browser process.
|
||||
/// 2. The browser process receives `BrpResult` and sends it to the render process.
|
||||
/// 3. The render process receives the result in `on_process_message_received`.
|
||||
/// 4. The render process resolves the result to the `Promise` of `window.brp`.
|
||||
pub struct BrpBuilder {
|
||||
object: *mut RcImpl<_cef_v8_handler_t, Self>,
|
||||
frame: Frame,
|
||||
}
|
||||
|
||||
impl BrpBuilder {
|
||||
pub fn build(frame: Frame) -> V8Handler {
|
||||
V8Handler::new(Self {
|
||||
object: core::ptr::null_mut(),
|
||||
frame,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for BrpBuilder {
|
||||
fn as_base(&self) -> &sys::cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapV8Handler for BrpBuilder {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_v8_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for BrpBuilder {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
rc_impl
|
||||
};
|
||||
Self {
|
||||
object,
|
||||
frame: self.frame.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplV8Handler for BrpBuilder {
|
||||
fn execute(
|
||||
&self,
|
||||
_: Option<&CefString>,
|
||||
_: Option<&mut V8Value>,
|
||||
arguments: Option<&[Option<V8Value>]>,
|
||||
ret: Option<&mut Option<V8Value>>,
|
||||
_: Option<&mut CefString>,
|
||||
) -> c_int {
|
||||
if let Some(mut process) = process_message_create(Some(&PROCESS_MESSAGE_BRP.into()))
|
||||
&& let Some(promise) = v8_value_create_promise()
|
||||
{
|
||||
if let Some(arguments_list) = process.argument_list()
|
||||
&& let Some(arguments) = arguments
|
||||
&& let Some(Some(arg)) = arguments.first()
|
||||
&& let Some(brp_request) = v8_value_to_json(arg)
|
||||
&& let Ok(brp_request) = serde_json::to_string(&brp_request)
|
||||
&& let Some(ret) = ret
|
||||
{
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
arguments_list.set_string(0, Some(&id.as_str().into()));
|
||||
arguments_list.set_string(1, Some(&brp_request.as_str().into()));
|
||||
self.frame.send_process_message(
|
||||
ProcessId::from(cef_process_id_t::PID_BROWSER),
|
||||
Some(&mut process),
|
||||
);
|
||||
ret.replace(promise.clone());
|
||||
let mut promises = BRP_PROMISES.lock().unwrap();
|
||||
promises.insert(id, promise);
|
||||
} else {
|
||||
let mut exception =
|
||||
v8_value_create_string(Some(&"Failed to execute BRP request".into()));
|
||||
promise.resolve_promise(exception.as_mut());
|
||||
}
|
||||
}
|
||||
1
|
||||
}
|
||||
|
||||
fn init_methods(object: &mut _cef_v8_handler_t) {
|
||||
init_methods::<Self>(object);
|
||||
}
|
||||
|
||||
fn get_raw(&self) -> *mut _cef_v8_handler_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
|
||||
fn init_methods<I: ImplV8Handler>(object: &mut _cef_v8_handler_t) {
|
||||
object.execute = Some(execute::<I>);
|
||||
}
|
||||
|
||||
extern "C" fn execute<I: ImplV8Handler>(
|
||||
self_: *mut _cef_v8_handler_t,
|
||||
name: *const cef_string_t,
|
||||
object: *mut _cef_v8_value_t,
|
||||
arguments_count: usize,
|
||||
arguments: *const *mut _cef_v8_value_t,
|
||||
retval: *mut *mut _cef_v8_value_t,
|
||||
exception: *mut cef_string_t,
|
||||
) -> c_int {
|
||||
let (arg_self_, arg_name, arg_object, arg_arguments_count, arg_arguments, _, arg_exception) = (
|
||||
self_,
|
||||
name,
|
||||
object,
|
||||
arguments_count,
|
||||
arguments,
|
||||
retval,
|
||||
exception,
|
||||
);
|
||||
let arg_self_: &RcImpl<_, I> = RcImpl::get(arg_self_);
|
||||
let arg_name = if arg_name.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(arg_name.into())
|
||||
};
|
||||
let arg_name = arg_name.as_ref();
|
||||
let mut arg_object =
|
||||
unsafe { arg_object.as_mut() }.map(|arg| (arg as *mut _cef_v8_value_t).wrap_result());
|
||||
let arg_object = arg_object.as_mut();
|
||||
let vec_arguments = unsafe { arg_arguments.as_ref() }.map(|arg| {
|
||||
let arg =
|
||||
unsafe { std::slice::from_raw_parts(std::ptr::from_ref(arg), arg_arguments_count) };
|
||||
arg.iter()
|
||||
.map(|arg| {
|
||||
if arg.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some((*arg).wrap_result())
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
let arg_arguments = vec_arguments.as_deref();
|
||||
let mut arg_retval: Option<V8Value> = None;
|
||||
let arg = Some(&mut arg_retval);
|
||||
let mut arg_exception = if arg_exception.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(arg_exception.into())
|
||||
};
|
||||
let arg_exception = arg_exception.as_mut();
|
||||
let r = ImplV8Handler::execute(
|
||||
&arg_self_.interface,
|
||||
arg_name,
|
||||
arg_object,
|
||||
arg_arguments,
|
||||
arg,
|
||||
arg_exception,
|
||||
);
|
||||
if let Some(ret) = arg_retval {
|
||||
// When the result is received, the pointer should be updated here
|
||||
// and the exception should also be updated.
|
||||
unsafe {
|
||||
write(retval, ret.into_raw());
|
||||
}
|
||||
}
|
||||
r
|
||||
}
|
||||
166
crates/bevy_cef_core/src/render_process/cef_api_handler.rs
Normal file
166
crates/bevy_cef_core/src/render_process/cef_api_handler.rs
Normal file
@@ -0,0 +1,166 @@
|
||||
use crate::prelude::{BRP_PROMISES, LISTEN_EVENTS, PROCESS_MESSAGE_BRP, PROCESS_MESSAGE_JS_EMIT};
|
||||
use crate::util::{IntoString, v8_value_to_json};
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::{
|
||||
CefString, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Context, ImplV8Handler,
|
||||
ImplV8Value, ProcessId, V8Value, WrapV8Handler, process_message_create, sys,
|
||||
v8_context_get_current_context, v8_value_create_promise, v8_value_create_string,
|
||||
};
|
||||
use cef_dll_sys::cef_process_id_t;
|
||||
use std::os::raw::c_int;
|
||||
|
||||
/// Handles the `window.cef` JavaScript API functions.
|
||||
///
|
||||
/// This handler is registered as a CEF extension during `on_web_kit_initialized`
|
||||
/// and provides three native functions:
|
||||
/// - `__cef_brp`: Async Bevy Remote Protocol requests
|
||||
/// - `__cef_emit`: Send events from JavaScript to Bevy
|
||||
/// - `__cef_listen`: Register callbacks for events from Bevy
|
||||
///
|
||||
/// The Frame is obtained dynamically via `v8_context_get_current_context().frame()`
|
||||
/// since extensions are global and not bound to a specific context.
|
||||
pub struct CefApiHandler {
|
||||
object: *mut RcImpl<sys::_cef_v8_handler_t, Self>,
|
||||
}
|
||||
|
||||
impl Default for CefApiHandler {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
object: core::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for CefApiHandler {
|
||||
fn as_base(&self) -> &sys::cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapV8Handler for CefApiHandler {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<sys::_cef_v8_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for CefApiHandler {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
rc_impl
|
||||
};
|
||||
Self { object }
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplV8Handler for CefApiHandler {
|
||||
fn execute(
|
||||
&self,
|
||||
name: Option<&CefString>,
|
||||
_object: Option<&mut V8Value>,
|
||||
arguments: Option<&[Option<V8Value>]>,
|
||||
ret: Option<&mut Option<V8Value>>,
|
||||
_exception: Option<&mut CefString>,
|
||||
) -> c_int {
|
||||
let Some(name) = name else { return 0 };
|
||||
let name_str = name.to_string();
|
||||
|
||||
match name_str.as_str() {
|
||||
"__cef_brp" => self.execute_brp(arguments, ret),
|
||||
"__cef_emit" => self.execute_emit(arguments),
|
||||
"__cef_listen" => self.execute_listen(arguments),
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_raw(&self) -> *mut sys::_cef_v8_handler_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
|
||||
impl CefApiHandler {
|
||||
fn execute_brp(
|
||||
&self,
|
||||
arguments: Option<&[Option<V8Value>]>,
|
||||
ret: Option<&mut Option<V8Value>>,
|
||||
) -> c_int {
|
||||
let Some(context) = v8_context_get_current_context() else {
|
||||
return 0;
|
||||
};
|
||||
let Some(frame) = context.frame() else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
if let Some(mut process) = process_message_create(Some(&PROCESS_MESSAGE_BRP.into()))
|
||||
&& let Some(promise) = v8_value_create_promise()
|
||||
{
|
||||
if let Some(arguments_list) = process.argument_list()
|
||||
&& let Some(arguments) = arguments
|
||||
&& let Some(Some(arg)) = arguments.first()
|
||||
&& let Some(brp_request) = v8_value_to_json(arg)
|
||||
&& let Ok(brp_request) = serde_json::to_string(&brp_request)
|
||||
&& let Some(ret) = ret
|
||||
{
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
arguments_list.set_string(0, Some(&id.as_str().into()));
|
||||
arguments_list.set_string(1, Some(&brp_request.as_str().into()));
|
||||
frame.send_process_message(
|
||||
ProcessId::from(cef_process_id_t::PID_BROWSER),
|
||||
Some(&mut process),
|
||||
);
|
||||
ret.replace(promise.clone());
|
||||
let mut promises = BRP_PROMISES.lock().unwrap();
|
||||
promises.insert(id, promise);
|
||||
} else {
|
||||
let mut exception =
|
||||
v8_value_create_string(Some(&"Failed to execute BRP request".into()));
|
||||
promise.resolve_promise(exception.as_mut());
|
||||
}
|
||||
}
|
||||
1
|
||||
}
|
||||
|
||||
fn execute_emit(&self, arguments: Option<&[Option<V8Value>]>) -> c_int {
|
||||
let Some(context) = v8_context_get_current_context() else {
|
||||
return 0;
|
||||
};
|
||||
let Some(frame) = context.frame() else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
if let Some(mut process) = process_message_create(Some(&PROCESS_MESSAGE_JS_EMIT.into()))
|
||||
&& let Some(arguments_list) = process.argument_list()
|
||||
&& let Some(arguments) = arguments
|
||||
&& let Some(Some(arg)) = arguments.first()
|
||||
&& let Some(arg) = v8_value_to_json(arg)
|
||||
&& let Ok(arg) = serde_json::to_string(&arg)
|
||||
{
|
||||
arguments_list.set_string(0, Some(&arg.as_str().into()));
|
||||
frame.send_process_message(
|
||||
ProcessId::from(cef_process_id_t::PID_BROWSER),
|
||||
Some(&mut process),
|
||||
);
|
||||
}
|
||||
1
|
||||
}
|
||||
|
||||
fn execute_listen(&self, arguments: Option<&[Option<V8Value>]>) -> c_int {
|
||||
if let Some(arguments) = arguments
|
||||
&& let Some(Some(id)) = arguments.first()
|
||||
&& id.is_string().is_positive()
|
||||
&& let Some(Some(callback)) = arguments.get(1)
|
||||
&& callback.is_function().is_positive()
|
||||
{
|
||||
LISTEN_EVENTS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(id.string_value().into_string(), callback.clone());
|
||||
}
|
||||
1
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
use crate::prelude::PROCESS_MESSAGE_JS_EMIT;
|
||||
use crate::util::v8_value_to_json;
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::{
|
||||
CefString, Frame, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Handler, ProcessId,
|
||||
V8Handler, V8Value, WrapV8Handler, process_message_create, sys,
|
||||
};
|
||||
use cef_dll_sys::cef_process_id_t;
|
||||
use std::os::raw::c_int;
|
||||
|
||||
pub struct EmitBuilder {
|
||||
object: *mut RcImpl<sys::_cef_v8_handler_t, Self>,
|
||||
frame: Frame,
|
||||
}
|
||||
|
||||
impl EmitBuilder {
|
||||
pub fn build(frame: Frame) -> V8Handler {
|
||||
V8Handler::new(Self {
|
||||
object: core::ptr::null_mut(),
|
||||
frame,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for EmitBuilder {
|
||||
fn as_base(&self) -> &sys::cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapV8Handler for EmitBuilder {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<sys::_cef_v8_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for EmitBuilder {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
rc_impl
|
||||
};
|
||||
Self {
|
||||
object,
|
||||
frame: self.frame.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplV8Handler for EmitBuilder {
|
||||
fn execute(
|
||||
&self,
|
||||
_: Option<&CefString>,
|
||||
_: Option<&mut V8Value>,
|
||||
arguments: Option<&[Option<V8Value>]>,
|
||||
_: Option<&mut Option<V8Value>>,
|
||||
_: Option<&mut CefString>,
|
||||
) -> c_int {
|
||||
if let Some(mut process) = process_message_create(Some(&PROCESS_MESSAGE_JS_EMIT.into()))
|
||||
&& let Some(arguments_list) = process.argument_list()
|
||||
&& let Some(arguments) = arguments
|
||||
&& let Some(Some(arg)) = arguments.first()
|
||||
&& let Some(arg) = v8_value_to_json(arg)
|
||||
&& let Ok(arg) = serde_json::to_string(&arg)
|
||||
{
|
||||
arguments_list.set_string(0, Some(&arg.as_str().into()));
|
||||
self.frame.send_process_message(
|
||||
ProcessId::from(cef_process_id_t::PID_BROWSER),
|
||||
Some(&mut process),
|
||||
);
|
||||
}
|
||||
1
|
||||
}
|
||||
|
||||
fn get_raw(&self) -> *mut sys::_cef_v8_handler_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
use crate::prelude::LISTEN_EVENTS;
|
||||
use crate::util::IntoString;
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::{CefString, Frame, ImplV8Handler, ImplV8Value, V8Handler, V8Value, WrapV8Handler, sys};
|
||||
use std::os::raw::c_int;
|
||||
|
||||
pub struct ListenBuilder {
|
||||
object: *mut RcImpl<sys::_cef_v8_handler_t, Self>,
|
||||
frame: Frame,
|
||||
}
|
||||
|
||||
impl ListenBuilder {
|
||||
pub fn build(frame: Frame) -> V8Handler {
|
||||
V8Handler::new(Self {
|
||||
object: core::ptr::null_mut(),
|
||||
frame,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for ListenBuilder {
|
||||
fn as_base(&self) -> &sys::cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapV8Handler for ListenBuilder {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<sys::_cef_v8_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ListenBuilder {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
rc_impl
|
||||
};
|
||||
Self {
|
||||
object,
|
||||
frame: self.frame.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplV8Handler for ListenBuilder {
|
||||
fn execute(
|
||||
&self,
|
||||
_: Option<&CefString>,
|
||||
_: Option<&mut V8Value>,
|
||||
arguments: Option<&[Option<V8Value>]>,
|
||||
_: Option<&mut Option<V8Value>>,
|
||||
_: Option<&mut CefString>,
|
||||
) -> c_int {
|
||||
if let Some(arguments) = arguments
|
||||
&& let Some(Some(id)) = arguments.first()
|
||||
&& 0 < id.is_string()
|
||||
&& let Some(Some(callback)) = arguments.get(1)
|
||||
&& 0 < callback.is_function()
|
||||
{
|
||||
LISTEN_EVENTS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(id.string_value().into_string(), callback.clone());
|
||||
}
|
||||
1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_raw(&self) -> *mut sys::_cef_v8_handler_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::prelude::{EmitBuilder, IntoString};
|
||||
use crate::render_process::brp::BrpBuilder;
|
||||
use crate::render_process::listen::ListenBuilder;
|
||||
use crate::prelude::{EXTENSIONS_SWITCH, IntoString};
|
||||
use crate::render_process::cef_api_handler::CefApiHandler;
|
||||
use crate::util::json_to_v8;
|
||||
use crate::util::v8_accessor::V8DefaultAccessorBuilder;
|
||||
use crate::util::v8_interceptor::V8DefaultInterceptorBuilder;
|
||||
@@ -8,14 +7,30 @@ use bevy::platform::collections::HashMap;
|
||||
use bevy_remote::BrpResult;
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::{
|
||||
Browser, CefString, DictionaryValue, Frame, ImplBrowser, ImplDictionaryValue, ImplFrame,
|
||||
ImplListValue, ImplProcessMessage, ImplRenderProcessHandler, ImplV8Context, ImplV8Value,
|
||||
ProcessId, ProcessMessage, V8Context, V8Propertyattribute, V8Value, WrapRenderProcessHandler,
|
||||
sys, v8_value_create_function, v8_value_create_object,
|
||||
Browser, CefString, DictionaryValue, Frame, ImplBrowser, ImplCommandLine, ImplDictionaryValue,
|
||||
ImplFrame, ImplListValue, ImplProcessMessage, ImplRenderProcessHandler, ImplV8Context,
|
||||
ImplV8Value, ProcessId, ProcessMessage, V8Context, V8Handler, V8Value,
|
||||
WrapRenderProcessHandler, command_line_get_global, register_extension, sys,
|
||||
v8_value_create_object,
|
||||
};
|
||||
use std::collections::HashMap as StdHashMap;
|
||||
use std::os::raw::c_int;
|
||||
use std::sync::Mutex;
|
||||
|
||||
const CEF_API_EXTENSION_NAME: &str = "v8/bevy-cef-api";
|
||||
const CEF_API_EXTENSION_CODE: &str = r#"
|
||||
var cef;
|
||||
if (!cef) cef = {};
|
||||
(function() {
|
||||
native function __cef_brp();
|
||||
native function __cef_emit();
|
||||
native function __cef_listen();
|
||||
cef.brp = __cef_brp;
|
||||
cef.emit = __cef_emit;
|
||||
cef.listen = __cef_listen;
|
||||
})();
|
||||
"#;
|
||||
|
||||
pub(crate) static BRP_PROMISES: Mutex<HashMap<String, V8Value>> = Mutex::new(HashMap::new());
|
||||
pub(crate) static LISTEN_EVENTS: Mutex<HashMap<String, V8Value>> = Mutex::new(HashMap::new());
|
||||
|
||||
@@ -65,6 +80,11 @@ impl Clone for RenderProcessHandlerBuilder {
|
||||
}
|
||||
|
||||
impl ImplRenderProcessHandler for RenderProcessHandlerBuilder {
|
||||
fn on_web_kit_initialized(&self) {
|
||||
register_cef_api_extension();
|
||||
register_extensions_from_command_line();
|
||||
}
|
||||
|
||||
fn on_browser_created(
|
||||
&self,
|
||||
browser: Option<&mut Browser>,
|
||||
@@ -91,7 +111,6 @@ impl ImplRenderProcessHandler for RenderProcessHandlerBuilder {
|
||||
&& let Some(browser) = browser
|
||||
{
|
||||
inject_initialize_scripts(browser, context, frame);
|
||||
inject_cef_api(context, frame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,46 +156,12 @@ fn inject_initialize_scripts(browser: &mut Browser, context: &mut V8Context, fra
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_cef_api(context: &mut V8Context, frame: &mut Frame) {
|
||||
if let Some(g) = context.global()
|
||||
&& let Some(mut cef) = v8_value_create_object(
|
||||
Some(&mut V8DefaultAccessorBuilder::build()),
|
||||
Some(&mut V8DefaultInterceptorBuilder::build()),
|
||||
)
|
||||
&& let Some(mut brp) = v8_value_create_function(
|
||||
Some(&"brp".into()),
|
||||
Some(&mut BrpBuilder::build(frame.clone())),
|
||||
)
|
||||
&& let Some(mut emit) = v8_value_create_function(
|
||||
Some(&"emit".into()),
|
||||
Some(&mut EmitBuilder::build(frame.clone())),
|
||||
)
|
||||
&& let Some(mut listen) = v8_value_create_function(
|
||||
Some(&"listen".into()),
|
||||
Some(&mut ListenBuilder::build(frame.clone())),
|
||||
)
|
||||
{
|
||||
cef.set_value_bykey(
|
||||
Some(&"brp".into()),
|
||||
Some(&mut brp),
|
||||
V8Propertyattribute::default(),
|
||||
);
|
||||
cef.set_value_bykey(
|
||||
Some(&"emit".into()),
|
||||
Some(&mut emit),
|
||||
V8Propertyattribute::default(),
|
||||
);
|
||||
cef.set_value_bykey(
|
||||
Some(&"listen".into()),
|
||||
Some(&mut listen),
|
||||
V8Propertyattribute::default(),
|
||||
);
|
||||
g.set_value_bykey(
|
||||
Some(&"cef".into()),
|
||||
Some(&mut cef),
|
||||
V8Propertyattribute::default(),
|
||||
);
|
||||
}
|
||||
fn register_cef_api_extension() {
|
||||
register_extension(
|
||||
Some(&CEF_API_EXTENSION_NAME.into()),
|
||||
Some(&CEF_API_EXTENSION_CODE.into()),
|
||||
Some(&mut V8Handler::new(CefApiHandler::default())),
|
||||
);
|
||||
}
|
||||
|
||||
fn handle_brp_message(message: &ProcessMessage, ctx: V8Context) {
|
||||
@@ -229,3 +214,32 @@ fn handle_listen_message(message: &ProcessMessage, mut ctx: V8Context) {
|
||||
}
|
||||
ctx.exit();
|
||||
}
|
||||
|
||||
fn register_extensions_from_command_line() {
|
||||
let Some(cmd_line) = command_line_get_global() else {
|
||||
return;
|
||||
};
|
||||
if cmd_line.has_switch(Some(&EXTENSIONS_SWITCH.into())) == 0 {
|
||||
return;
|
||||
}
|
||||
let json = cmd_line
|
||||
.switch_value(Some(&EXTENSIONS_SWITCH.into()))
|
||||
.into_string();
|
||||
if json.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(extensions) = serde_json::from_str::<StdHashMap<String, String>>(&json) else {
|
||||
eprintln!("bevy_cef: failed to parse extensions JSON: {}", json);
|
||||
return;
|
||||
};
|
||||
|
||||
for (name, code) in extensions {
|
||||
let full_name = format!("v8/{}", name);
|
||||
register_extension(
|
||||
Some(&full_name.as_str().into()),
|
||||
Some(&code.as_str().into()),
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ use cef_dll_sys::cef_scheme_options_t::{
|
||||
use std::env::home_dir;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub const EXTENSIONS_SWITCH: &str = "bevy-cef-extensions";
|
||||
|
||||
pub const SCHEME_CEF: &str = "cef";
|
||||
|
||||
pub const HOST_CEF: &str = "localhost";
|
||||
|
||||
Reference in New Issue
Block a user