Desktop: Isolate CEF-rendered UI into separate crate and process (#4321)

* Extract CEF rendered UI into a separate process and crate

* Review

* Review

* Review

* Review

* Remove necessary workarounds

* Block on frame copy ack

* Crop and resample frames correctly

* Skip blank frames

* Fix deps

* Fix fmt

* Fix clippy warning

* Review

* Fix todo
This commit is contained in:
Timon
2026-07-14 14:51:20 -07:00
committed by Keavon Chambers
parent 97a43e66fb
commit ba0a97aefe
82 changed files with 4768 additions and 1291 deletions
@@ -0,0 +1,134 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_app_t, cef_base_ref_counted_t};
use cef::{BrowserProcessHandler, CefString, ImplApp, ImplCommandLine, SchemeRegistrar, WrapApp};
use super::browser_process_handler::BrowserProcessHandlerImpl;
use super::scheme_handler_factory::register_schemes;
pub(crate) struct BrowserProcessAppImpl {
object: *mut RcImpl<_cef_app_t, Self>,
accelerated_paint: bool,
}
impl BrowserProcessAppImpl {
pub(crate) fn new(accelerated_paint: bool) -> Self {
Self {
object: std::ptr::null_mut(),
accelerated_paint,
}
}
}
impl ImplApp for BrowserProcessAppImpl {
fn browser_process_handler(&self) -> Option<BrowserProcessHandler> {
Some(BrowserProcessHandler::new(BrowserProcessHandlerImpl::new()))
}
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
register_schemes(registrar);
}
fn on_before_command_line_processing(&self, _process_type: Option<&cef::CefString>, command_line: Option<&mut cef::CommandLine>) {
if let Some(cmd) = command_line {
cmd.append_switch_with_value(Some(&"renderer-process-limit".into()), Some(&"1".into()));
cmd.append_switch_with_value(Some(&"password-store".into()), Some(&"basic".into()));
cmd.append_switch_with_value(Some(&"disk-cache-size".into()), Some(&"0".into()));
cmd.append_switch(Some(&"no-sandbox".into()));
cmd.append_switch(Some(&"no-first-run".into()));
cmd.append_switch(Some(&"noerrdialogs".into()));
cmd.append_switch(Some(&"no-default-browser-check".into()));
cmd.append_switch(Some(&"mute-audio".into()));
cmd.append_switch(Some(&"use-fake-device-for-media-stream".into()));
cmd.append_switch(Some(&"incognito".into()));
cmd.append_switch(Some(&"disable-sync".into()));
cmd.append_switch(Some(&"disable-file-system".into()));
cmd.append_switch(Some(&"disable-component-update".into()));
cmd.append_switch(Some(&"disable-geolocation".into()));
cmd.append_switch(Some(&"disable-notifications".into()));
cmd.append_switch(Some(&"disable-background-networking".into()));
cmd.append_switch(Some(&"disable-default-apps".into()));
cmd.append_switch(Some(&"disable-breakpad".into()));
cmd.append_switch_with_value(Some(&"disable-blink-features".into()), Some(&"WebBluetooth,WebUSB,Serial".into()));
let extra_disabled_features = ["OptimizationHints", "OnDeviceModelService", "TranslateUI"];
let disabled_features_switch = Some(&"disable-features".into());
let mut disabled_features: Vec<String> = CefString::from(&cmd.switch_value(disabled_features_switch))
.to_string()
.split(',')
.filter(|feature| !feature.is_empty())
.map(ToOwned::to_owned)
.collect();
disabled_features.extend(extra_disabled_features.into_iter().map(ToOwned::to_owned));
cmd.append_switch_with_value(disabled_features_switch, Some(&disabled_features.join(",").as_str().into()));
if self.accelerated_paint {
cmd.append_switch(Some(&"enable-gpu".into()));
cmd.append_switch(Some(&"enable-gpu-compositing".into()));
cmd.append_switch(Some(&"enable-begin-frame-scheduling".into()));
cmd.append_switch(Some(&"off-screen-rendering-enabled".into()));
cmd.append_switch(Some(&"enable-accelerated-2d-canvas".into()));
#[cfg(target_os = "linux")]
{
cmd.append_switch_with_value(Some(&"use-angle".into()), Some(&"gl-egl".into()));
let use_wayland = std::env::var("WAYLAND_DISPLAY")
.ok()
.filter(|var| !var.is_empty())
.or_else(|| std::env::var("WAYLAND_SOCKET").ok())
.filter(|var| !var.is_empty())
.is_some();
if use_wayland {
cmd.append_switch_with_value(Some(&"ozone-platform".into()), Some(&"wayland".into()));
}
}
} else {
cmd.append_switch(Some(&"disable-gpu".into()));
cmd.append_switch(Some(&"disable-gpu-compositing".into()));
}
#[cfg(target_os = "macos")]
{
// Hide user prompt asking for keychain access
cmd.append_switch(Some(&"use-mock-keychain".into()));
}
// Enable browser debugging via environment variable
if let Some(env) = std::env::var("GRAPHITE_BROWSER_DEBUG_PORT").ok()
&& let Some(port) = env.parse::<u16>().ok()
{
cmd.append_switch_with_value(Some(&"remote-debugging-port".into()), Some(&port.to_string().as_str().into()));
cmd.append_switch_with_value(Some(&"remote-allow-origins".into()), Some(&"*".into()));
}
}
}
fn get_raw(&self) -> *mut _cef_app_t {
self.object.cast()
}
}
impl Clone for BrowserProcessAppImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self {
object: self.object,
accelerated_paint: self.accelerated_paint,
}
}
}
impl Rc for BrowserProcessAppImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapApp for BrowserProcessAppImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) {
self.object = object;
}
}
@@ -0,0 +1,121 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_client_t, cef_base_ref_counted_t};
use cef::{ContextMenuHandler, DisplayHandler, ImplClient, LifeSpanHandler, LoadHandler, RenderHandler, RequestHandler, WrapClient};
use crate::delegate::BrowserDelegate;
use crate::frames::FrameStreamer;
use crate::ipc::{MessageType, UnpackMessage, UnpackedMessage};
use super::context_menu_handler::ContextMenuHandlerImpl;
use super::display_handler::DisplayHandlerImpl;
use super::life_span_handler::LifeSpanHandlerImpl;
use super::load_handler::LoadHandlerImpl;
use super::render_handler::RenderHandlerImpl;
use super::request_handler::RequestHandlerImpl;
pub(crate) struct BrowserProcessClientImpl {
object: *mut RcImpl<_cef_client_t, Self>,
delegate: BrowserDelegate,
load_handler: LoadHandler,
render_handler: RenderHandler,
display_handler: DisplayHandler,
request_handler: RequestHandler,
}
impl BrowserProcessClientImpl {
pub(crate) fn new(delegate: &BrowserDelegate, frames: FrameStreamer) -> Self {
Self {
object: std::ptr::null_mut(),
delegate: delegate.clone(),
load_handler: LoadHandler::new(LoadHandlerImpl::new(delegate.clone())),
render_handler: RenderHandler::new(RenderHandlerImpl::new(delegate.clone(), frames)),
display_handler: DisplayHandler::new(DisplayHandlerImpl::new(delegate.clone())),
request_handler: RequestHandler::new(RequestHandlerImpl::new()),
}
}
}
impl ImplClient for BrowserProcessClientImpl {
fn on_process_message_received(
&self,
_browser: Option<&mut cef::Browser>,
_frame: Option<&mut cef::Frame>,
_source_process: cef::ProcessId,
message: Option<&mut cef::ProcessMessage>,
) -> std::ffi::c_int {
let unpacked_message = unsafe { message.and_then(|m| m.unpack()) };
match unpacked_message {
Some(UnpackedMessage {
message_type: MessageType::Initialized,
data: _,
}) => self.delegate.initialized_web_communication(),
Some(UnpackedMessage {
message_type: MessageType::SendToNative,
data,
}) => self.delegate.receive_web_message(data),
_ => {
tracing::error!("Unexpected message type received in browser process");
return 0;
}
}
1
}
fn load_handler(&self) -> Option<cef::LoadHandler> {
Some(self.load_handler.clone())
}
fn render_handler(&self) -> Option<RenderHandler> {
Some(self.render_handler.clone())
}
fn life_span_handler(&self) -> Option<cef::LifeSpanHandler> {
Some(LifeSpanHandler::new(LifeSpanHandlerImpl::new()))
}
fn display_handler(&self) -> Option<cef::DisplayHandler> {
Some(self.display_handler.clone())
}
fn request_handler(&self) -> Option<cef::RequestHandler> {
Some(self.request_handler.clone())
}
fn context_menu_handler(&self) -> Option<cef::ContextMenuHandler> {
Some(ContextMenuHandler::new(ContextMenuHandlerImpl::new()))
}
fn get_raw(&self) -> *mut _cef_client_t {
self.object.cast()
}
}
impl Clone for BrowserProcessClientImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self {
object: self.object,
delegate: self.delegate.clone(),
load_handler: self.load_handler.clone(),
render_handler: self.render_handler.clone(),
display_handler: self.display_handler.clone(),
request_handler: self.request_handler.clone(),
}
}
}
impl Rc for BrowserProcessClientImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapClient for BrowserProcessClientImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_client_t, Self>) {
self.object = object;
}
}
@@ -0,0 +1,45 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_browser_process_handler_t, cef_base_ref_counted_t, cef_browser_process_handler_t};
use cef::{CefString, ImplBrowserProcessHandler, WrapBrowserProcessHandler};
pub(crate) struct BrowserProcessHandlerImpl {
object: *mut RcImpl<cef_browser_process_handler_t, Self>,
}
impl BrowserProcessHandlerImpl {
pub(crate) fn new() -> Self {
Self { object: std::ptr::null_mut() }
}
}
impl ImplBrowserProcessHandler for BrowserProcessHandlerImpl {
fn on_already_running_app_relaunch(&self, _command_line: Option<&mut cef::CommandLine>, _current_directory: Option<&CefString>) -> std::ffi::c_int {
1 // Return 1 to prevent default behavior of opening a empty browser window
}
fn get_raw(&self) -> *mut _cef_browser_process_handler_t {
self.object.cast()
}
}
impl Clone for BrowserProcessHandlerImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self { object: self.object }
}
}
impl Rc for BrowserProcessHandlerImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapBrowserProcessHandler for BrowserProcessHandlerImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_browser_process_handler_t, Self>) {
self.object = object;
}
}
@@ -0,0 +1,66 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_context_menu_handler_t, cef_base_ref_counted_t};
use cef::{ImplContextMenuHandler, WrapContextMenuHandler};
pub(crate) struct ContextMenuHandlerImpl {
object: *mut RcImpl<_cef_context_menu_handler_t, Self>,
}
impl ContextMenuHandlerImpl {
pub(crate) fn new() -> Self {
Self { object: std::ptr::null_mut() }
}
}
impl ImplContextMenuHandler for ContextMenuHandlerImpl {
fn run_context_menu(
&self,
_browser: Option<&mut cef::Browser>,
_frame: Option<&mut cef::Frame>,
_params: Option<&mut cef::ContextMenuParams>,
_model: Option<&mut cef::MenuModel>,
_callback: Option<&mut cef::RunContextMenuCallback>,
) -> std::ffi::c_int {
// Prevent context menu
1
}
fn run_quick_menu(
&self,
_browser: Option<&mut cef::Browser>,
_frame: Option<&mut cef::Frame>,
_location: Option<&cef::Point>,
_size: Option<&cef::Size>,
_edit_state_flags: cef::QuickMenuEditStateFlags,
_callback: Option<&mut cef::RunQuickMenuCallback>,
) -> std::ffi::c_int {
// Prevent quick menu
1
}
fn get_raw(&self) -> *mut _cef_context_menu_handler_t {
self.object.cast()
}
}
impl Clone for ContextMenuHandlerImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self { object: self.object }
}
}
impl Rc for ContextMenuHandlerImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapContextMenuHandler for ContextMenuHandlerImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_context_menu_handler_t, Self>) {
self.object = object;
}
}
+155
View File
@@ -0,0 +1,155 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_display_handler_t, cef_base_ref_counted_t, cef_cursor_type_t::*, cef_log_severity_t::*};
use cef::{CefString, ImplDisplayHandler, Point, Size, WrapDisplayHandler};
use winit::cursor::CursorIcon;
use crate::delegate::BrowserDelegate;
pub(crate) struct DisplayHandlerImpl {
object: *mut RcImpl<_cef_display_handler_t, Self>,
delegate: BrowserDelegate,
}
impl DisplayHandlerImpl {
pub fn new(delegate: BrowserDelegate) -> Self {
Self {
object: std::ptr::null_mut(),
delegate,
}
}
}
#[cfg(not(target_os = "macos"))]
type CefCursorHandle = cef::CursorHandle;
#[cfg(target_os = "macos")]
type CefCursorHandle = *mut u8;
impl ImplDisplayHandler for DisplayHandlerImpl {
fn on_cursor_change(&self, _browser: Option<&mut cef::Browser>, _cursor: CefCursorHandle, cursor_type: cef::CursorType, custom_cursor_info: Option<&cef::CursorInfo>) -> std::ffi::c_int {
if let Some(custom_cursor_info) = custom_cursor_info {
let Size { width, height } = custom_cursor_info.size;
let Point { x: hotspot_x, y: hotspot_y } = custom_cursor_info.hotspot;
let buffer_size = (width * height * 4) as usize;
let buffer_ptr = custom_cursor_info.buffer as *const u8;
if !buffer_ptr.is_null() && buffer_ptr.align_offset(std::mem::align_of::<u8>()) == 0 {
let buffer = unsafe { std::slice::from_raw_parts(buffer_ptr, buffer_size) }.to_vec();
self.delegate.cursor_change(crate::Cursor::Custom {
rgba: buffer,
width: width as u16,
height: height as u16,
hotspot_x: hotspot_x as u16,
hotspot_y: hotspot_y as u16,
});
return 1; // We handled the cursor change.
}
}
let cursor = match cursor_type.into() {
CT_POINTER => CursorIcon::Default,
CT_CROSS => CursorIcon::Crosshair,
CT_HAND => CursorIcon::Pointer,
CT_IBEAM => CursorIcon::Text,
CT_WAIT => CursorIcon::Wait,
CT_HELP => CursorIcon::Help,
CT_EASTRESIZE => CursorIcon::EResize,
CT_NORTHRESIZE => CursorIcon::NResize,
CT_NORTHEASTRESIZE => CursorIcon::NeResize,
CT_NORTHWESTRESIZE => CursorIcon::NwResize,
CT_SOUTHRESIZE => CursorIcon::SResize,
CT_SOUTHEASTRESIZE => CursorIcon::SeResize,
CT_SOUTHWESTRESIZE => CursorIcon::SwResize,
CT_WESTRESIZE => CursorIcon::WResize,
CT_NORTHSOUTHRESIZE => CursorIcon::NsResize,
CT_EASTWESTRESIZE => CursorIcon::EwResize,
CT_NORTHEASTSOUTHWESTRESIZE => CursorIcon::NeswResize,
CT_NORTHWESTSOUTHEASTRESIZE => CursorIcon::NwseResize,
CT_COLUMNRESIZE => CursorIcon::ColResize,
CT_ROWRESIZE => CursorIcon::RowResize,
CT_MIDDLEPANNING => CursorIcon::AllScroll,
CT_EASTPANNING => CursorIcon::AllScroll,
CT_NORTHPANNING => CursorIcon::AllScroll,
CT_NORTHEASTPANNING => CursorIcon::AllScroll,
CT_NORTHWESTPANNING => CursorIcon::AllScroll,
CT_SOUTHPANNING => CursorIcon::AllScroll,
CT_SOUTHEASTPANNING => CursorIcon::AllScroll,
CT_SOUTHWESTPANNING => CursorIcon::AllScroll,
CT_WESTPANNING => CursorIcon::AllScroll,
CT_MOVE => CursorIcon::Move,
CT_VERTICALTEXT => CursorIcon::VerticalText,
CT_CELL => CursorIcon::Cell,
CT_CONTEXTMENU => CursorIcon::ContextMenu,
CT_ALIAS => CursorIcon::Alias,
CT_PROGRESS => CursorIcon::Progress,
CT_NODROP => CursorIcon::NoDrop,
CT_COPY => CursorIcon::Copy,
CT_NOTALLOWED => CursorIcon::NotAllowed,
CT_ZOOMIN => CursorIcon::ZoomIn,
CT_ZOOMOUT => CursorIcon::ZoomOut,
CT_GRAB => CursorIcon::Grab,
CT_GRABBING => CursorIcon::Grabbing,
CT_MIDDLE_PANNING_VERTICAL => CursorIcon::AllScroll,
CT_MIDDLE_PANNING_HORIZONTAL => CursorIcon::AllScroll,
CT_DND_NONE => CursorIcon::Default,
CT_DND_MOVE => CursorIcon::Move,
CT_DND_COPY => CursorIcon::Copy,
CT_DND_LINK => CursorIcon::Alias,
CT_NUM_VALUES => CursorIcon::Default,
CT_NONE => {
self.delegate.cursor_change(crate::Cursor::None);
return 1; // We handled the cursor change.
}
_ => CursorIcon::Default,
};
self.delegate.cursor_change(cursor.into());
1 // We handled the cursor change.
}
fn on_console_message(&self, _browser: Option<&mut cef::Browser>, level: cef::LogSeverity, message: Option<&CefString>, source: Option<&CefString>, line: std::ffi::c_int) -> std::ffi::c_int {
let message = message.map(|m| m.to_string()).unwrap_or_default();
let source = source.map(|s| s.to_string()).unwrap_or_default();
let line = line as i64;
let browser_source = format!("{source}:{line}");
static BROWSER: &str = "browser";
match level.as_ref() {
LOGSEVERITY_FATAL | LOGSEVERITY_ERROR => tracing::error!(target: BROWSER, "{browser_source} {message}"),
LOGSEVERITY_WARNING => tracing::warn!(target: BROWSER, "{browser_source} {message}"),
LOGSEVERITY_INFO => tracing::info!(target: BROWSER, "{browser_source} {message}"),
LOGSEVERITY_DEFAULT | LOGSEVERITY_VERBOSE => tracing::debug!(target: BROWSER, "{browser_source} {message}"),
_ => tracing::trace!(target: BROWSER, "{browser_source} {message}"),
}
0
}
fn get_raw(&self) -> *mut _cef_display_handler_t {
self.object.cast()
}
}
impl Clone for DisplayHandlerImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self {
object: self.object,
delegate: self.delegate.clone(),
}
}
}
impl Rc for DisplayHandlerImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapDisplayHandler for DisplayHandlerImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_display_handler_t, Self>) {
self.object = object;
}
}
@@ -0,0 +1,64 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_life_span_handler_t, cef_base_ref_counted_t};
use cef::{ImplLifeSpanHandler, WrapLifeSpanHandler};
pub(crate) struct LifeSpanHandlerImpl {
object: *mut RcImpl<_cef_life_span_handler_t, Self>,
}
impl LifeSpanHandlerImpl {
pub(crate) fn new() -> Self {
Self { object: std::ptr::null_mut() }
}
}
impl ImplLifeSpanHandler for LifeSpanHandlerImpl {
fn on_before_popup(
&self,
_browser: Option<&mut cef::Browser>,
_frame: Option<&mut cef::Frame>,
_popup_id: std::ffi::c_int,
target_url: Option<&cef::CefString>,
_target_frame_name: Option<&cef::CefString>,
_target_disposition: cef::WindowOpenDisposition,
_user_gesture: std::ffi::c_int,
_popup_features: Option<&cef::PopupFeatures>,
_window_info: Option<&mut cef::WindowInfo>,
_client: Option<&mut Option<cef::Client>>,
_settings: Option<&mut cef::BrowserSettings>,
_extra_info: Option<&mut Option<cef::DictionaryValue>>,
_no_javascript_access: Option<&mut std::ffi::c_int>,
) -> std::ffi::c_int {
let target = target_url.map(|url| url.to_string()).unwrap_or("unknown".to_string());
tracing::error!("Browser tried to open a popup at URL: {}", target);
// Deny any popup by returning 1
1
}
fn get_raw(&self) -> *mut _cef_life_span_handler_t {
self.object.cast()
}
}
impl Clone for LifeSpanHandlerImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self { object: self.object }
}
}
impl Rc for LifeSpanHandlerImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapLifeSpanHandler for LifeSpanHandlerImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_life_span_handler_t, Self>) {
self.object = object;
}
}
+60
View File
@@ -0,0 +1,60 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_load_handler_t, cef_base_ref_counted_t, cef_load_handler_t};
use cef::{ImplBrowser, ImplBrowserHost, ImplLoadHandler, WrapLoadHandler};
use crate::delegate::BrowserDelegate;
pub(crate) struct LoadHandlerImpl {
object: *mut RcImpl<cef_load_handler_t, Self>,
delegate: BrowserDelegate,
}
impl LoadHandlerImpl {
pub(crate) fn new(delegate: BrowserDelegate) -> Self {
Self {
object: std::ptr::null_mut(),
delegate,
}
}
}
impl ImplLoadHandler for LoadHandlerImpl {
fn on_loading_state_change(&self, browser: Option<&mut cef::Browser>, is_loading: std::ffi::c_int, _can_go_back: std::ffi::c_int, _can_go_forward: std::ffi::c_int) {
let view_info = self.delegate.view_info();
if let Some(browser) = browser
&& is_loading == 0
{
browser.host().unwrap().set_zoom_level(view_info.zoom());
}
}
fn get_raw(&self) -> *mut _cef_load_handler_t {
self.object.cast()
}
}
impl Clone for LoadHandlerImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self {
object: self.object,
delegate: self.delegate.clone(),
}
}
}
impl Rc for LoadHandlerImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapLoadHandler for LoadHandlerImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_load_handler_t, Self>) {
self.object = object;
}
}
+92
View File
@@ -0,0 +1,92 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_render_handler_t, cef_base_ref_counted_t};
use cef::{Browser, ImplRenderHandler, PaintElementType, Rect, WrapRenderHandler};
use crate::delegate::BrowserDelegate;
use crate::frames::FrameStreamer;
pub(crate) struct RenderHandlerImpl {
object: *mut RcImpl<_cef_render_handler_t, Self>,
delegate: BrowserDelegate,
frames: FrameStreamer,
}
impl RenderHandlerImpl {
pub(crate) fn new(delegate: BrowserDelegate, frames: FrameStreamer) -> Self {
Self {
object: std::ptr::null_mut(),
delegate,
frames,
}
}
}
impl ImplRenderHandler for RenderHandlerImpl {
fn view_rect(&self, _browser: Option<&mut Browser>, rect: Option<&mut Rect>) {
if let Some(rect) = rect {
let view_info = self.delegate.view_info();
*rect = Rect {
x: 0,
y: 0,
width: view_info.width() as i32,
height: view_info.height() as i32,
};
}
}
fn on_paint(&self, _browser: Option<&mut Browser>, type_: PaintElementType, _dirty_rects: Option<&[Rect]>, buffer: *const u8, width: std::ffi::c_int, height: std::ffi::c_int) {
if type_ != PaintElementType::default() {
return;
}
let buffer_size = (width * height * 4) as usize;
let buffer_slice = unsafe { std::slice::from_raw_parts(buffer, buffer_size) };
self.frames.stage_buffer(buffer_slice, width as u32, height as u32);
self.frames.publish();
}
#[cfg(feature = "accelerated_paint")]
fn on_accelerated_paint(&self, _browser: Option<&mut Browser>, type_: PaintElementType, _dirty_rects: Option<&[Rect]>, info: Option<&cef::AcceleratedPaintInfo>) {
if type_ != PaintElementType::default() {
return;
}
let Some(info) = info else {
tracing::error!("Accelerated paint callback received no info about the painted frame");
return;
};
self.frames.stage_texture(info);
self.frames.publish();
}
fn get_raw(&self) -> *mut _cef_render_handler_t {
self.object.cast()
}
}
impl Clone for RenderHandlerImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self {
object: self.object,
delegate: self.delegate.clone(),
frames: self.frames.clone(),
}
}
}
impl Rc for RenderHandlerImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapRenderHandler for RenderHandlerImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_render_handler_t, Self>) {
self.object = object;
}
}
@@ -0,0 +1,59 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_app_t, cef_base_ref_counted_t};
use cef::{App, ImplApp, RenderProcessHandler, SchemeRegistrar, WrapApp};
use super::render_process_handler::RenderProcessHandlerImpl;
use super::scheme_handler_factory::register_schemes;
pub(crate) struct RenderProcessAppImpl {
object: *mut RcImpl<_cef_app_t, Self>,
render_process_handler: RenderProcessHandler,
}
impl RenderProcessAppImpl {
pub(crate) fn app() -> App {
App::new(Self {
object: std::ptr::null_mut(),
render_process_handler: RenderProcessHandler::new(RenderProcessHandlerImpl::new()),
})
}
}
impl ImplApp for RenderProcessAppImpl {
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
register_schemes(registrar);
}
fn render_process_handler(&self) -> Option<RenderProcessHandler> {
Some(self.render_process_handler.clone())
}
fn get_raw(&self) -> *mut _cef_app_t {
self.object.cast()
}
}
impl Clone for RenderProcessAppImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self {
object: self.object,
render_process_handler: self.render_process_handler.clone(),
}
}
}
impl Rc for RenderProcessAppImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapApp for RenderProcessAppImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) {
self.object = object;
}
}
@@ -0,0 +1,127 @@
use cef::rc::{ConvertReturnValue, Rc, RcImpl};
use cef::sys::{_cef_render_process_handler_t, cef_base_ref_counted_t, cef_render_process_handler_t, cef_v8_propertyattribute_t, cef_v8_value_create_array_buffer_with_copy};
use cef::{ImplFrame, ImplRenderProcessHandler, ImplV8Context, ImplV8Value, V8Handler, V8Propertyattribute, V8Value, WrapRenderProcessHandler, v8_value_create_function};
use crate::ipc::{MessageType, UnpackMessage, UnpackedMessage};
use super::render_process_v8_handler::RenderProcessV8HandlerImpl;
pub(crate) struct RenderProcessHandlerImpl {
object: *mut RcImpl<cef_render_process_handler_t, Self>,
}
impl RenderProcessHandlerImpl {
pub(crate) fn new() -> Self {
Self { object: std::ptr::null_mut() }
}
}
impl ImplRenderProcessHandler for RenderProcessHandlerImpl {
fn on_process_message_received(
&self,
_browser: Option<&mut cef::Browser>,
frame: Option<&mut cef::Frame>,
_source_process: cef::ProcessId,
message: Option<&mut cef::ProcessMessage>,
) -> std::ffi::c_int {
let unpacked_message = unsafe { message.and_then(|m| m.unpack()) };
match unpacked_message {
Some(UnpackedMessage {
message_type: MessageType::SendToJS,
data,
}) => {
let Some(frame) = frame else {
tracing::error!("Frame is not available");
return 0;
};
let Some(context) = frame.v8_context() else {
tracing::error!("V8 context is not available");
return 0;
};
if context.enter() == 0 {
tracing::error!("Failed to enter V8 context");
return 0;
}
let mut value: V8Value = unsafe { cef_v8_value_create_array_buffer_with_copy(data.as_ptr() as *mut std::ffi::c_void, data.len()) }.wrap_result();
let Some(global) = context.global() else {
tracing::error!("Global object is not available in V8 context");
return 0;
};
let function_name = "receiveNativeMessage";
let property_name = "receiveNativeMessageData";
let function_call = format!("window.{function_name}(window.{property_name})");
global.set_value_bykey(Some(&property_name.into()), Some(&mut value), cef_v8_propertyattribute_t::V8_PROPERTY_ATTRIBUTE_READONLY.wrap_result());
if global.value_bykey(Some(&function_name.into())).is_some() {
frame.execute_java_script(Some(&function_call.as_str().into()), None, 0);
}
if context.exit() == 0 {
tracing::error!("Failed to exit V8 context");
return 0;
}
}
_ => {
tracing::error!("Unexpected message type received in render process");
return 0;
}
}
1
}
fn on_context_created(&self, _browser: Option<&mut cef::Browser>, _frame: Option<&mut cef::Frame>, context: Option<&mut cef::V8Context>) {
let register_js_function = |context: &mut cef::V8Context, name: &'static str| {
let mut v8_handler = V8Handler::new(RenderProcessV8HandlerImpl::new());
let Some(mut function) = v8_value_create_function(Some(&name.into()), Some(&mut v8_handler)) else {
tracing::error!("Failed to create V8 function {name}");
return;
};
let Some(global) = context.global() else {
tracing::error!("Global object is not available in V8 context");
return;
};
global.set_value_bykey(Some(&name.into()), Some(&mut function), V8Propertyattribute::default());
};
let Some(context) = context else {
tracing::error!("V8 context is not available");
return;
};
let initialized_function_name = "initializeNativeCommunication";
let send_function_name = "sendNativeMessage";
register_js_function(context, initialized_function_name);
register_js_function(context, send_function_name);
}
fn get_raw(&self) -> *mut _cef_render_process_handler_t {
self.object.cast()
}
}
impl Clone for RenderProcessHandlerImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self { object: self.object }
}
}
impl Rc for RenderProcessHandlerImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapRenderProcessHandler for RenderProcessHandlerImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_render_process_handler_t, Self>) {
self.object = object;
}
}
@@ -0,0 +1,86 @@
use cef::{ImplV8Handler, ImplV8Value, V8Value, WrapV8Handler, rc::Rc, v8_context_get_current_context};
use crate::ipc::{MessageType, SendMessage};
pub struct RenderProcessV8HandlerImpl {
object: *mut cef::rc::RcImpl<cef::sys::_cef_v8_handler_t, Self>,
}
impl RenderProcessV8HandlerImpl {
pub(crate) fn new() -> Self {
Self { object: std::ptr::null_mut() }
}
}
impl ImplV8Handler for RenderProcessV8HandlerImpl {
fn execute(
&self,
name: Option<&cef::CefString>,
_object: Option<&mut V8Value>,
arguments: Option<&[Option<V8Value>]>,
_retval: Option<&mut Option<V8Value>>,
_exception: Option<&mut cef::CefString>,
) -> std::ffi::c_int {
match name.map(|s| s.to_string()).unwrap_or_default().as_str() {
"initializeNativeCommunication" => {
v8_context_get_current_context().send_message(MessageType::Initialized, vec![0u8].as_slice());
}
"sendNativeMessage" => {
let Some(args) = arguments else {
tracing::error!("No arguments provided to sendNativeMessage");
return 0;
};
let Some(arg1) = args.first() else {
tracing::error!("No arguments provided to sendNativeMessage");
return 0;
};
let Some(arg1) = arg1.as_ref() else {
tracing::error!("First argument to sendNativeMessage is not an ArrayBuffer");
return 0;
};
if arg1.is_array_buffer() == 0 {
tracing::error!("First argument to sendNativeMessage is not an ArrayBuffer");
return 0;
}
let size = arg1.array_buffer_byte_length();
let ptr = arg1.array_buffer_data();
let data = unsafe { std::slice::from_raw_parts_mut(ptr as *mut u8, size) };
v8_context_get_current_context().send_message(MessageType::SendToNative, data);
return 1;
}
name => {
tracing::error!("Unknown V8 function called: {}", name);
}
}
1
}
fn get_raw(&self) -> *mut cef::sys::_cef_v8_handler_t {
self.object.cast()
}
}
impl Clone for RenderProcessV8HandlerImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self { object: self.object }
}
}
impl Rc for RenderProcessV8HandlerImpl {
fn as_base(&self) -> &cef::sys::cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapV8Handler for RenderProcessV8HandlerImpl {
fn wrap_rc(&mut self, object: *mut cef::rc::RcImpl<cef::sys::_cef_v8_handler_t, Self>) {
self.object = object;
}
}
@@ -0,0 +1,84 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_request_handler_t, cef_base_ref_counted_t};
use cef::{AuthCallback, Browser, CefString, Frame, ImplRequest, ImplRequestHandler, Request, ResourceRequestHandler, WrapRequestHandler};
use std::ffi::c_int;
use super::resource_request_handler::ResourceRequestHandlerImpl;
use crate::consts::{RESOURCE_DOMAIN, RESOURCE_SCHEME};
pub(crate) struct RequestHandlerImpl {
object: *mut RcImpl<_cef_request_handler_t, Self>,
}
impl RequestHandlerImpl {
pub(crate) fn new() -> Self {
Self { object: std::ptr::null_mut() }
}
}
impl ImplRequestHandler for RequestHandlerImpl {
fn on_before_browse(&self, _browser: Option<&mut Browser>, _frame: Option<&mut Frame>, request: Option<&mut Request>, _user_gesture: c_int, _is_redirect: c_int) -> c_int {
let Some(request) = request else { return 1 };
let url = CefString::from(&request.url()).to_string();
if url.starts_with(&format!("{RESOURCE_SCHEME}://{RESOURCE_DOMAIN}/")) {
0
} else {
tracing::warn!("Blocked navigation to: {}", url);
1
}
}
fn resource_request_handler(
&self,
_browser: Option<&mut Browser>,
_frame: Option<&mut Frame>,
_request: Option<&mut Request>,
_is_navigation: c_int,
_is_download: c_int,
_request_initiator: Option<&CefString>,
_disable_default_handling: Option<&mut c_int>,
) -> Option<ResourceRequestHandler> {
Some(ResourceRequestHandler::new(ResourceRequestHandlerImpl::new()))
}
fn auth_credentials(
&self,
_browser: Option<&mut Browser>,
_origin_url: Option<&CefString>,
_is_proxy: c_int,
_host: Option<&CefString>,
_port: c_int,
_realm: Option<&CefString>,
_scheme: Option<&CefString>,
_callback: Option<&mut AuthCallback>,
) -> c_int {
0
}
fn get_raw(&self) -> *mut _cef_request_handler_t {
self.object.cast()
}
}
impl Clone for RequestHandlerImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self { object: self.object }
}
}
impl Rc for RequestHandlerImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapRequestHandler for RequestHandlerImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_request_handler_t, Self>) {
self.object = object;
}
}
+107
View File
@@ -0,0 +1,107 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_resource_handler_t, cef_base_ref_counted_t};
use cef::{Callback, CefString, ImplResourceHandler, ImplResponse, Request, ResourceReadCallback, Response, WrapResourceHandler};
use std::cell::RefCell;
use std::ffi::c_int;
use std::io::Read;
use crate::resources::{Resource, ResourceReader};
pub(crate) struct ResourceHandlerImpl {
object: *mut RcImpl<_cef_resource_handler_t, Self>,
reader: Option<RefCell<ResourceReader>>,
mimetype: Option<String>,
}
impl ResourceHandlerImpl {
pub fn new(resource: Option<Resource>) -> Self {
if let Some(resource) = resource {
Self {
object: std::ptr::null_mut(),
reader: Some(resource.reader.into()),
mimetype: resource.mimetype,
}
} else {
Self {
object: std::ptr::null_mut(),
reader: None,
mimetype: None,
}
}
}
}
impl ImplResourceHandler for ResourceHandlerImpl {
fn open(&self, _request: Option<&mut Request>, handle_request: Option<&mut c_int>, _callback: Option<&mut Callback>) -> c_int {
if let Some(handle_request) = handle_request {
*handle_request = 1;
}
1
}
fn response_headers(&self, response: Option<&mut Response>, response_length: Option<&mut i64>, _redirect_url: Option<&mut CefString>) {
if let Some(response_length) = response_length {
*response_length = -1; // Indicating that the length is unknown
}
if let Some(response) = response {
if self.reader.is_some() {
if let Some(mimetype) = &self.mimetype {
response.set_mime_type(Some(&mimetype.as_str().into()));
} else {
response.set_mime_type(None);
}
response.set_status(200);
} else {
response.set_status(404);
response.set_mime_type(Some(&"text/plain".into()));
}
}
}
fn read(&self, data_out: *mut u8, bytes_to_read: c_int, bytes_read: Option<&mut c_int>, _callback: Option<&mut ResourceReadCallback>) -> c_int {
let Some(bytes_read) = bytes_read else { unreachable!() };
let out = unsafe { std::slice::from_raw_parts_mut(data_out, bytes_to_read as usize) };
if let Some(reader) = &self.reader {
if let Ok(read) = reader.borrow_mut().read(out) {
*bytes_read = read as i32;
if read > 0 {
return 1; // Indicating that data was read
}
} else {
*bytes_read = -2; // Indicating ERR_FAILED
}
}
0 // Indicating no data was read
}
fn get_raw(&self) -> *mut _cef_resource_handler_t {
self.object.cast()
}
}
impl Clone for ResourceHandlerImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self {
object: self.object,
reader: self.reader.clone(),
mimetype: self.mimetype.clone(),
}
}
}
impl Rc for ResourceHandlerImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapResourceHandler for ResourceHandlerImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_resource_handler_t, Self>) {
self.object = object;
}
}
@@ -0,0 +1,60 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_resource_request_handler_t, cef_base_ref_counted_t};
use cef::{Browser, Callback, CefString, Frame, ImplRequest, ImplResourceRequestHandler, Request, ReturnValue, WrapResourceRequestHandler};
use crate::consts::{RESOURCE_DOMAIN, RESOURCE_SCHEME};
// TODO: Deny all external requests once we stop relying on google fonts for font preview
fn is_allowed_url(url: &str) -> bool {
url.starts_with(&format!("{RESOURCE_SCHEME}://{RESOURCE_DOMAIN}/")) || url.starts_with("https://fonts.googleapis.com/css2") || url.starts_with("https://fonts.gstatic.com/")
}
pub(crate) struct ResourceRequestHandlerImpl {
object: *mut RcImpl<_cef_resource_request_handler_t, Self>,
}
impl ResourceRequestHandlerImpl {
pub(crate) fn new() -> Self {
Self { object: std::ptr::null_mut() }
}
}
impl ImplResourceRequestHandler for ResourceRequestHandlerImpl {
fn on_before_resource_load(&self, _browser: Option<&mut Browser>, _frame: Option<&mut Frame>, request: Option<&mut Request>, _callback: Option<&mut Callback>) -> ReturnValue {
let Some(request) = request else { return ReturnValue::CANCEL };
let url = CefString::from(&request.url()).to_string();
if is_allowed_url(&url) {
ReturnValue::CONTINUE
} else {
tracing::error!("Blocked resource load: {}", url);
ReturnValue::CANCEL
}
}
fn get_raw(&self) -> *mut _cef_resource_request_handler_t {
self.object.cast()
}
}
impl Clone for ResourceRequestHandlerImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self { object: self.object }
}
}
impl Rc for ResourceRequestHandlerImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapResourceRequestHandler for ResourceRequestHandlerImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_resource_request_handler_t, Self>) {
self.object = object;
}
}
@@ -0,0 +1,74 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_scheme_handler_factory_t, cef_base_ref_counted_t, cef_scheme_options_t};
use cef::{Browser, CefString, Frame, ImplRequest, ImplSchemeHandlerFactory, ImplSchemeRegistrar, Request, ResourceHandler, SchemeRegistrar, WrapSchemeHandlerFactory};
use super::resource_handler::ResourceHandlerImpl;
use crate::consts::{RESOURCE_DOMAIN, RESOURCE_SCHEME};
use crate::delegate::BrowserDelegate;
pub(crate) struct SchemeHandlerFactoryImpl {
object: *mut RcImpl<_cef_scheme_handler_factory_t, Self>,
delegate: BrowserDelegate,
}
impl SchemeHandlerFactoryImpl {
pub(crate) fn new(delegate: BrowserDelegate) -> Self {
Self {
object: std::ptr::null_mut(),
delegate,
}
}
}
pub(crate) fn register_schemes(registrar: Option<&mut SchemeRegistrar>) {
if let Some(registrar) = registrar {
let mut scheme_options = 0;
scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_STANDARD as i32;
scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_FETCH_ENABLED as i32;
scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_SECURE as i32;
scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_CORS_ENABLED as i32;
registrar.add_custom_scheme(Some(&RESOURCE_SCHEME.into()), scheme_options);
}
}
impl ImplSchemeHandlerFactory for SchemeHandlerFactoryImpl {
fn create(&self, _browser: Option<&mut Browser>, _frame: Option<&mut Frame>, _scheme_name: Option<&CefString>, request: Option<&mut Request>) -> Option<ResourceHandler> {
if let Some(request) = request {
let url = CefString::from(&request.url()).to_string();
let path = url
.strip_prefix(&format!("{RESOURCE_SCHEME}://{RESOURCE_DOMAIN}/"))
.expect("CEF should only call this for our custom scheme and domain that we registered this factory for");
let resource = self.delegate.load_resource(path.to_string().into());
return Some(ResourceHandler::new(ResourceHandlerImpl::new(resource)));
}
None
}
fn get_raw(&self) -> *mut _cef_scheme_handler_factory_t {
self.object.cast()
}
}
impl Clone for SchemeHandlerFactoryImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self {
object: self.object,
delegate: self.delegate.clone(),
}
}
}
impl Rc for SchemeHandlerFactoryImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapSchemeHandlerFactory for SchemeHandlerFactoryImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_scheme_handler_factory_t, Self>) {
self.object = object;
}
}
+61
View File
@@ -0,0 +1,61 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_task_t, cef_base_ref_counted_t};
use cef::{ImplTask, WrapTask};
use std::cell::RefCell;
// Closure-based task wrapper following CEF patterns
pub struct ClosureTask<F> {
pub(crate) object: *mut RcImpl<_cef_task_t, Self>,
pub(crate) closure: RefCell<Option<F>>,
}
impl<F: FnOnce() + Send + 'static> ClosureTask<F> {
pub fn new(closure: F) -> Self {
Self {
object: std::ptr::null_mut(),
closure: RefCell::new(Some(closure)),
}
}
}
impl<F: FnOnce() + Send + 'static> ImplTask for ClosureTask<F> {
fn execute(&self) {
if let Some(closure) = self.closure.borrow_mut().take() {
closure();
}
}
fn get_raw(&self) -> *mut _cef_task_t {
self.object.cast()
}
}
impl<F: FnOnce() + Send + 'static> Clone for ClosureTask<F> {
fn clone(&self) -> Self {
unsafe {
if !self.object.is_null() {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
}
Self {
object: self.object,
closure: RefCell::new(None), // Closure can only be executed once
}
}
}
impl<F: FnOnce() + Send + 'static> Rc for ClosureTask<F> {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl<F: FnOnce() + Send + 'static> WrapTask for ClosureTask<F> {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_task_t, Self>) {
self.object = object;
}
}