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:
elm
2026-02-04 16:08:32 +09:00
committed by GitHub
parent b7900a24a0
commit 519aa2b2bf
21 changed files with 473 additions and 454 deletions

View File

@@ -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::*;

View File

@@ -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(),
))
}

View File

@@ -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) {

View 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()
}
}

View File

@@ -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::*;

View File

@@ -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.

View File

@@ -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
}

View 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
}
}

View File

@@ -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()
}
}

View File

@@ -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()
}
}

View File

@@ -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,
);
}
}

View File

@@ -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";