Skip to main content

Talking to Your Webview

Now that you have a webview rendering in your scene, the next step is communication. bevy_cef provides three IPC patterns, but the simplest starting point is JS Emit -- sending events from your webview's JavaScript into Bevy.

JS Emit: Webview to Bevy

JS Emit is a fire-and-forget pattern. JavaScript calls window.cef.emit() with an event name and data, and Bevy receives it as an EntityEvent on the webview entity.

Step 1: Define a Message Struct

Create a Rust struct that matches the shape of the data your JavaScript will send. It must implement Deserialize:

use serde::Deserialize;

#[derive(Deserialize)]
struct Message {
count: u32,
}

Step 2: Register the Plugin

Add a JsEmitEventPlugin for your message type. This tells bevy_cef to listen for events named after your type (lowercase) and deserialize them into your struct:

app.add_plugins(JsEmitEventPlugin::<Message>::default());

Step 3: Add an Observer

Use Bevy's observer pattern to react when a Receive<Message> event fires on a webview entity:

fn on_message(trigger: On<Receive<Message>>) {
info!("Received count: {}", trigger.count);
}

// In your app setup:
app.add_observer(on_message);

The Receive<T> wrapper is an EntityEvent -- it fires on the specific webview entity that emitted it, so you always know which webview the event came from.

Step 4: Emit from JavaScript

In your HTML file, call window.cef.emit() with the event name and a data object:

<!DOCTYPE html>
<html>
<body>
<button id="btn">Click me</button>
<script>
let count = 0;
document.getElementById('btn').addEventListener('click', () => {
count += 1;
window.cef.emit('message', { count });
});
</script>
</body>
</html>

Load this file with WebviewSource::local("js_emit.html") and place it in your assets/ directory.

Putting It Together

use bevy::prelude::*;
use bevy_cef::prelude::*;
use serde::Deserialize;

#[derive(Deserialize)]
struct Message {
count: u32,
}

fn main() {
App::new()
.add_plugins((
DefaultPlugins,
CefPlugin::default(),
JsEmitEventPlugin::<Message>::default(),
))
.add_systems(Startup, spawn_webview)
.add_observer(on_message)
.run();
}

fn on_message(trigger: On<Receive<Message>>) {
info!("Button clicked {} times", trigger.count);
}

fn spawn_webview(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
) {
commands.spawn((
WebviewSource::local("js_emit.html"),
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())),
));
}

What's Next

JS Emit handles the webview-to-Bevy direction. To send data the other way (Bevy to webview) or make async RPC calls, see the Communication section: