add: preload scripts (#6)
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Added `PreloadScripts` component for specifying JavaScript to be executed when the page is initialized.
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
- Fixed so that webview can detect pointers correctly even if it is not the root entity.
|
- Fixed so that webview can detect pointers correctly even if it is not the root entity.
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ use bevy::platform::collections::HashMap;
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_remote::BrpMessage;
|
use bevy_remote::BrpMessage;
|
||||||
use cef::{
|
use cef::{
|
||||||
Browser, BrowserHost, BrowserSettings, Client, CompositionUnderline, ImplBrowser,
|
Browser, BrowserHost, BrowserSettings, CefString, Client, CompositionUnderline,
|
||||||
ImplBrowserHost, ImplFrame, ImplListValue, ImplProcessMessage, ImplRequestContext,
|
DictionaryValue, ImplBrowser, ImplBrowserHost, ImplDictionaryValue, ImplFrame, ImplListValue,
|
||||||
MouseButtonType, ProcessId, Range, RequestContext, RequestContextSettings, WindowInfo,
|
ImplProcessMessage, ImplRequestContext, MouseButtonType, ProcessId, Range, RequestContext,
|
||||||
browser_host_create_browser_sync, process_message_create,
|
RequestContextSettings, WindowInfo, browser_host_create_browser_sync, dictionary_value_create,
|
||||||
|
process_message_create,
|
||||||
};
|
};
|
||||||
use cef_dll_sys::{cef_event_flags_t, cef_mouse_button_type_t};
|
use cef_dll_sys::{cef_event_flags_t, cef_mouse_button_type_t};
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
@@ -62,6 +63,7 @@ impl Browsers {
|
|||||||
ipc_event_sender: Sender<IpcEventRaw>,
|
ipc_event_sender: Sender<IpcEventRaw>,
|
||||||
brp_sender: Sender<BrpMessage>,
|
brp_sender: Sender<BrpMessage>,
|
||||||
system_cursor_icon_sender: SystemCursorIconSenderInner,
|
system_cursor_icon_sender: SystemCursorIconSenderInner,
|
||||||
|
initialize_scripts: &[String],
|
||||||
_window_handle: Option<RawWindowHandle>,
|
_window_handle: Option<RawWindowHandle>,
|
||||||
) {
|
) {
|
||||||
let mut context = Self::request_context(requester);
|
let mut context = Self::request_context(requester);
|
||||||
@@ -93,10 +95,11 @@ impl Browsers {
|
|||||||
windowless_frame_rate: 60,
|
windowless_frame_rate: 60,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
None,
|
Self::create_extra_info(initialize_scripts).as_mut(),
|
||||||
context.as_mut(),
|
context.as_mut(),
|
||||||
)
|
)
|
||||||
.expect("Failed to create browser");
|
.expect("Failed to create browser");
|
||||||
|
|
||||||
self.browsers.insert(
|
self.browsers.insert(
|
||||||
webview,
|
webview,
|
||||||
WebviewBrowser {
|
WebviewBrowser {
|
||||||
@@ -415,6 +418,18 @@ impl Browsers {
|
|||||||
.get(webview)
|
.get(webview)
|
||||||
.and_then(|b| b.client.focused_frame().is_some().then_some(b))
|
.and_then(|b| b.client.focused_frame().is_some().then_some(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_extra_info(scripts: &[String]) -> Option<DictionaryValue> {
|
||||||
|
if scripts.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let extra = dictionary_value_create()?;
|
||||||
|
extra.set_string(
|
||||||
|
Some(&CefString::from(INIT_SCRIPT_KEY)),
|
||||||
|
Some(&CefString::from(scripts.join(";").as_str())),
|
||||||
|
);
|
||||||
|
Some(extra)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn modifiers_from_mouse_buttons<'a>(buttons: impl IntoIterator<Item = &'a MouseButton>) -> u32 {
|
pub fn modifiers_from_mouse_buttons<'a>(buttons: impl IntoIterator<Item = &'a MouseButton>) -> u32 {
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ use bevy::platform::collections::HashMap;
|
|||||||
use bevy_remote::BrpResult;
|
use bevy_remote::BrpResult;
|
||||||
use cef::rc::{Rc, RcImpl};
|
use cef::rc::{Rc, RcImpl};
|
||||||
use cef::{
|
use cef::{
|
||||||
Browser, Frame, ImplFrame, ImplListValue, ImplProcessMessage, ImplRenderProcessHandler,
|
Browser, CefString, DictionaryValue, Frame, ImplBrowser, ImplDictionaryValue, ImplFrame,
|
||||||
ImplV8Context, ImplV8Value, ProcessId, ProcessMessage, V8Context, V8Propertyattribute, V8Value,
|
ImplListValue, ImplProcessMessage, ImplRenderProcessHandler, ImplV8Context, ImplV8Value,
|
||||||
WrapRenderProcessHandler, sys, v8_value_create_function, v8_value_create_object,
|
ProcessId, ProcessMessage, V8Context, V8Propertyattribute, V8Value, WrapRenderProcessHandler,
|
||||||
|
sys, v8_value_create_function, v8_value_create_object,
|
||||||
};
|
};
|
||||||
use std::os::raw::c_int;
|
use std::os::raw::c_int;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
@@ -18,6 +19,9 @@ use std::sync::Mutex;
|
|||||||
pub(crate) static BRP_PROMISES: Mutex<HashMap<String, V8Value>> = Mutex::new(HashMap::new());
|
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());
|
pub(crate) static LISTEN_EVENTS: Mutex<HashMap<String, V8Value>> = Mutex::new(HashMap::new());
|
||||||
|
|
||||||
|
static INIT_SCRIPTS: Mutex<HashMap<c_int, String>> = Mutex::new(HashMap::new());
|
||||||
|
pub const INIT_SCRIPT_KEY: &str = "init_script";
|
||||||
|
|
||||||
pub const PROCESS_MESSAGE_BRP: &str = "brp";
|
pub const PROCESS_MESSAGE_BRP: &str = "brp";
|
||||||
pub const PROCESS_MESSAGE_HOST_EMIT: &str = "host-emit";
|
pub const PROCESS_MESSAGE_HOST_EMIT: &str = "host-emit";
|
||||||
pub const PROCESS_MESSAGE_JS_EMIT: &str = "js-emit";
|
pub const PROCESS_MESSAGE_JS_EMIT: &str = "js-emit";
|
||||||
@@ -61,14 +65,80 @@ impl Clone for RenderProcessHandlerBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ImplRenderProcessHandler for RenderProcessHandlerBuilder {
|
impl ImplRenderProcessHandler for RenderProcessHandlerBuilder {
|
||||||
|
fn on_browser_created(
|
||||||
|
&self,
|
||||||
|
browser: Option<&mut Browser>,
|
||||||
|
extra: Option<&mut DictionaryValue>,
|
||||||
|
) {
|
||||||
|
if let (Some(browser), Some(extra)) = (browser, extra) {
|
||||||
|
let script = extra.string(Some(&INIT_SCRIPT_KEY.into())).into_string();
|
||||||
|
if script.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let id = browser.identifier();
|
||||||
|
INIT_SCRIPTS.lock().unwrap().insert(id, script);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn on_context_created(
|
fn on_context_created(
|
||||||
&self,
|
&self,
|
||||||
_browser: Option<&mut Browser>,
|
browser: Option<&mut Browser>,
|
||||||
frame: Option<&mut Frame>,
|
frame: Option<&mut Frame>,
|
||||||
context: Option<&mut V8Context>,
|
context: Option<&mut V8Context>,
|
||||||
) {
|
) {
|
||||||
if let Some(g) = context.and_then(|c| c.global())
|
if let Some(context) = context
|
||||||
&& let Some(frame) = frame
|
&& let Some(frame) = frame
|
||||||
|
&& let Some(browser) = browser
|
||||||
|
{
|
||||||
|
inject_initialize_scripts(browser, context, frame);
|
||||||
|
inject_cef_api(context, frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_process_message_received(
|
||||||
|
&self,
|
||||||
|
_browser: Option<&mut Browser>,
|
||||||
|
frame: Option<&mut Frame>,
|
||||||
|
_: ProcessId,
|
||||||
|
message: Option<&mut ProcessMessage>,
|
||||||
|
) -> c_int {
|
||||||
|
if let Some(message) = message
|
||||||
|
&& let Some(frame) = frame
|
||||||
|
&& let Some(ctx) = frame.v8_context()
|
||||||
|
{
|
||||||
|
match message.name().into_string().as_str() {
|
||||||
|
PROCESS_MESSAGE_BRP => {
|
||||||
|
handle_brp_message(message, ctx);
|
||||||
|
}
|
||||||
|
PROCESS_MESSAGE_HOST_EMIT => {
|
||||||
|
handle_listen_message(message, ctx);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn get_raw(&self) -> *mut sys::_cef_render_process_handler_t {
|
||||||
|
self.object.cast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inject_initialize_scripts(browser: &mut Browser, context: &mut V8Context, frame: &mut Frame) {
|
||||||
|
let id = browser.identifier();
|
||||||
|
if let Some(script) = INIT_SCRIPTS.lock().ok().and_then(|scripts| {
|
||||||
|
let script = scripts.get(&id)?;
|
||||||
|
Some(CefString::from(script.as_str()))
|
||||||
|
}) {
|
||||||
|
context.enter();
|
||||||
|
frame.execute_java_script(Some(&script), Some(&(&frame.url()).into()), 0);
|
||||||
|
context.exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inject_cef_api(context: &mut V8Context, frame: &mut Frame) {
|
||||||
|
if let Some(g) = context.global()
|
||||||
&& let Some(mut cef) = v8_value_create_object(
|
&& let Some(mut cef) = v8_value_create_object(
|
||||||
Some(&mut V8DefaultAccessorBuilder::build()),
|
Some(&mut V8DefaultAccessorBuilder::build()),
|
||||||
Some(&mut V8DefaultInterceptorBuilder::build()),
|
Some(&mut V8DefaultInterceptorBuilder::build()),
|
||||||
@@ -106,36 +176,6 @@ impl ImplRenderProcessHandler for RenderProcessHandlerBuilder {
|
|||||||
Some(&mut cef),
|
Some(&mut cef),
|
||||||
V8Propertyattribute::default(),
|
V8Propertyattribute::default(),
|
||||||
);
|
);
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_process_message_received(
|
|
||||||
&self,
|
|
||||||
_browser: Option<&mut Browser>,
|
|
||||||
frame: Option<&mut Frame>,
|
|
||||||
_: ProcessId,
|
|
||||||
message: Option<&mut ProcessMessage>,
|
|
||||||
) -> c_int {
|
|
||||||
if let Some(message) = message
|
|
||||||
&& let Some(frame) = frame
|
|
||||||
&& let Some(ctx) = frame.v8_context()
|
|
||||||
{
|
|
||||||
match message.name().into_string().as_str() {
|
|
||||||
PROCESS_MESSAGE_BRP => {
|
|
||||||
handle_brp_message(message, ctx);
|
|
||||||
}
|
|
||||||
PROCESS_MESSAGE_HOST_EMIT => {
|
|
||||||
handle_listen_message(message, ctx);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
1
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn get_raw(&self) -> *mut sys::_cef_render_process_handler_t {
|
|
||||||
self.object.cast()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
44
examples/preload_scripts.rs
Normal file
44
examples/preload_scripts.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
//! You can use [`PreloadScripts`] to execute scripts when the webview is initialized.
|
||||||
|
//!
|
||||||
|
//! [`PreloadScripts`] is executed before the scripts in the HTML.
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_cef::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins((DefaultPlugins, CefPlugin))
|
||||||
|
.add_systems(
|
||||||
|
Startup,
|
||||||
|
(spawn_camera, spawn_directional_light, spawn_webview),
|
||||||
|
)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_camera(mut commands: Commands) {
|
||||||
|
commands.spawn((
|
||||||
|
Camera3d::default(),
|
||||||
|
Transform::from_translation(Vec3::new(0., 0., 3.)).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_directional_light(mut commands: Commands) {
|
||||||
|
commands.spawn((
|
||||||
|
DirectionalLight::default(),
|
||||||
|
Transform::from_translation(Vec3::new(1., 1., 1.)).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_webview(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
|
||||||
|
) {
|
||||||
|
commands.spawn((
|
||||||
|
CefWebviewUri::new("https://github.com/not-elm/bevy_cef"),
|
||||||
|
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
||||||
|
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())),
|
||||||
|
// Here, we add a simple script to show an alert.
|
||||||
|
PreloadScripts::from(["alert('Hello World!')"]),
|
||||||
|
));
|
||||||
|
}
|
||||||
@@ -9,7 +9,9 @@ impl Plugin for WebviewCoreComponentsPlugin {
|
|||||||
app.register_type::<WebviewSize>()
|
app.register_type::<WebviewSize>()
|
||||||
.register_type::<CefWebviewUri>()
|
.register_type::<CefWebviewUri>()
|
||||||
.register_type::<HostWindow>()
|
.register_type::<HostWindow>()
|
||||||
.register_type::<ZoomLevel>();
|
.register_type::<ZoomLevel>()
|
||||||
|
.register_type::<AudioMuted>()
|
||||||
|
.register_type::<PreloadScripts>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,7 +23,7 @@ impl Plugin for WebviewCoreComponentsPlugin {
|
|||||||
/// Alternatively, you can also use [`CefWebviewUri::local`].
|
/// Alternatively, you can also use [`CefWebviewUri::local`].
|
||||||
#[derive(Component, Debug, Clone, PartialEq, Eq, Hash, Reflect)]
|
#[derive(Component, Debug, Clone, PartialEq, Eq, Hash, Reflect)]
|
||||||
#[reflect(Component, Debug)]
|
#[reflect(Component, Debug)]
|
||||||
#[require(WebviewSize, ZoomLevel, AudioMuted)]
|
#[require(WebviewSize, ZoomLevel, AudioMuted, PreloadScripts)]
|
||||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
|
||||||
pub struct CefWebviewUri(pub String);
|
pub struct CefWebviewUri(pub String);
|
||||||
@@ -78,3 +80,20 @@ pub struct ZoomLevel(pub f64);
|
|||||||
#[derive(Reflect, Component, Debug, Copy, Clone, PartialEq, Default, Serialize, Deserialize)]
|
#[derive(Reflect, Component, Debug, Copy, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||||
#[reflect(Component, Debug, Default, Serialize, Deserialize)]
|
#[reflect(Component, Debug, Default, Serialize, Deserialize)]
|
||||||
pub struct AudioMuted(pub bool);
|
pub struct AudioMuted(pub bool);
|
||||||
|
|
||||||
|
/// This component is used to preload scripts in the webview.
|
||||||
|
///
|
||||||
|
/// Scripts specified in this component are executed before the scripts in the HTML.
|
||||||
|
#[derive(Reflect, Component, Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||||
|
#[reflect(Component, Debug, Default, Serialize, Deserialize)]
|
||||||
|
pub struct PreloadScripts(pub Vec<String>);
|
||||||
|
|
||||||
|
impl<L, S> From<L> for PreloadScripts
|
||||||
|
where
|
||||||
|
L: IntoIterator<Item = S>,
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
fn from(scripts: L) -> Self {
|
||||||
|
Self(scripts.into_iter().map(Into::into).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::common::{CefWebviewUri, HostWindow, IpcEventRawSender, WebviewSize};
|
use crate::common::{CefWebviewUri, HostWindow, IpcEventRawSender, WebviewSize};
|
||||||
use crate::cursor_icon::SystemCursorIconSender;
|
use crate::cursor_icon::SystemCursorIconSender;
|
||||||
|
use crate::prelude::PreloadScripts;
|
||||||
use crate::webview::mesh::MeshWebviewPlugin;
|
use crate::webview::mesh::MeshWebviewPlugin;
|
||||||
use bevy::ecs::component::HookContext;
|
use bevy::ecs::component::HookContext;
|
||||||
use bevy::ecs::world::DeferredWorld;
|
use bevy::ecs::world::DeferredWorld;
|
||||||
@@ -104,13 +105,19 @@ fn create_webview(
|
|||||||
cursor_icon_sender: Res<SystemCursorIconSender>,
|
cursor_icon_sender: Res<SystemCursorIconSender>,
|
||||||
winit_windows: NonSend<WinitWindows>,
|
winit_windows: NonSend<WinitWindows>,
|
||||||
webviews: Query<
|
webviews: Query<
|
||||||
(Entity, &CefWebviewUri, &WebviewSize, Option<&HostWindow>),
|
(
|
||||||
|
Entity,
|
||||||
|
&CefWebviewUri,
|
||||||
|
&WebviewSize,
|
||||||
|
&PreloadScripts,
|
||||||
|
Option<&HostWindow>,
|
||||||
|
),
|
||||||
Added<CefWebviewUri>,
|
Added<CefWebviewUri>,
|
||||||
>,
|
>,
|
||||||
primary_window: Query<Entity, With<PrimaryWindow>>,
|
primary_window: Query<Entity, With<PrimaryWindow>>,
|
||||||
) {
|
) {
|
||||||
for (entity, uri, size, parent) in webviews.iter() {
|
for (entity, uri, size, initialize_scripts, host_window) in webviews.iter() {
|
||||||
let host_window = parent
|
let host_window = host_window
|
||||||
.and_then(|w| winit_windows.get_window(w.0))
|
.and_then(|w| winit_windows.get_window(w.0))
|
||||||
.or_else(|| winit_windows.get_window(primary_window.single().ok()?))
|
.or_else(|| winit_windows.get_window(primary_window.single().ok()?))
|
||||||
.and_then(|w| {
|
.and_then(|w| {
|
||||||
@@ -125,6 +132,7 @@ fn create_webview(
|
|||||||
ipc_event_sender.0.clone(),
|
ipc_event_sender.0.clone(),
|
||||||
brp_sender.clone(),
|
brp_sender.clone(),
|
||||||
cursor_icon_sender.clone(),
|
cursor_icon_sender.clone(),
|
||||||
|
&initialize_scripts.0,
|
||||||
host_window,
|
host_window,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user