use crate::dom::Dom;
use crate::js_runtime::extensions::audio_ext::audio_extension;
use crate::js_runtime::extensions::canvas_ext::{canvas_extension, CanvasState};
use crate::js_runtime::extensions::console_ext::console_extension;
use crate::js_runtime::extensions::crypto_ext::crypto_extension;
use crate::js_runtime::extensions::dom_ext::dom_extension;
use crate::js_runtime::extensions::fetch_ext::{fetch_extension, FetchState};
use crate::js_runtime::extensions::layout_ext::layout_extension;
use crate::js_runtime::extensions::sse_ext::{sse_extension, SseState};
use crate::js_runtime::extensions::stealth_ext::{stealth_extension, StealthState};
use crate::js_runtime::extensions::timer_ext::{timer_extension, TimerState};
use crate::js_runtime::extensions::webgl_ext::{webgl_extension, WebGLState};
use crate::js_runtime::extensions::websocket_ext::{websocket_extension, WebSocketState};
use crate::js_runtime::extensions::worker_ext::worker_extension;
use crate::js_runtime::state::DomState;
use deno_core::{JsRuntimeForSnapshot, RuntimeOptions};
use std::path::PathBuf;
use std::sync::OnceLock;
static RUNTIME_SNAPSHOT: OnceLock<Box<[u8]>> = OnceLock::new();
fn snapshot_cache_path() -> Option<PathBuf> {
if std::env::var_os("BROWSER_OXIDE_NO_SNAPSHOT_CACHE").is_some() {
return None;
}
let exe = std::env::current_exe().ok()?;
let meta = std::fs::metadata(&exe).ok()?;
let len = meta.len();
let mtime = meta
.modified()
.ok()
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map(|d| d.as_nanos())
.unwrap_or(0);
let dir = std::env::var_os("BROWSER_OXIDE_SNAPSHOT_CACHE")
.map(PathBuf::from)
.unwrap_or_else(std::env::temp_dir);
Some(dir.join(format!("bo_v8_snapshot_{len}_{mtime}.bin")))
}
pub fn get_snapshot() -> &'static [u8] {
RUNTIME_SNAPSHOT.get_or_init(|| {
let cache_path = snapshot_cache_path();
if let Some(ref path) = cache_path {
if let Ok(bytes) = std::fs::read(path) {
if !bytes.is_empty() {
tracing::info!(
"Restored V8 snapshot from cache ({} bytes): {}",
bytes.len(),
path.display()
);
return bytes.into_boxed_slice();
}
}
}
tracing::info!("Creating cold V8 snapshot");
let mut runtime = JsRuntimeForSnapshot::new(RuntimeOptions {
extensions: vec![
console_extension::init(),
crypto_extension::init(),
dom_extension::init(),
timer_extension::init(),
stealth_extension::init(),
fetch_extension::init(),
canvas_extension::init(),
layout_extension::init(),
websocket_extension::init(),
webgl_extension::init(),
sse_extension::init(),
crate::js_runtime::extensions::input_ext::input_extension::init(),
worker_extension::init(),
audio_extension::init(),
crate::js_runtime::extensions::perf_ext::perf_extension::init(),
crate::js_runtime::extensions::nav_ext::nav_extension::init(),
],
create_params: Some(
deno_core::v8::CreateParams::default()
.heap_limits(1024 * 1024 * 1024, 4 * 1024 * 1024 * 1024),
),
..Default::default()
});
{
let op_state = runtime.op_state();
let mut op_state = op_state.borrow_mut();
op_state.put(DomState::new(Dom::new()));
op_state.put(TimerState::new());
op_state.put(StealthState::new_with_flags(None, false, true));
op_state.put(FetchState::new(None));
op_state.put(CanvasState::new());
op_state.put(WebSocketState::new());
op_state.put(WebGLState::new());
op_state.put(SseState::new());
op_state.put(crate::js_runtime::extensions::perf_ext::PerfState::new());
op_state.put(crate::js_runtime::extensions::nav_ext::NavSignal::new());
}
const BOOTSTRAP_JS: &str = concat!(
include_str!("js/console_bootstrap.js"),
"\n",
include_str!("js/stealth_bootstrap.js"),
"\n",
include_str!("js/interfaces_bootstrap.js"),
"\n",
include_str!("js/instances_bootstrap.js"),
"\n",
include_str!("js/fetch_bootstrap.js"),
"\n",
include_str!("js/timer_bootstrap.js"),
"\n",
include_str!("js/dom_bootstrap.js"),
"\n",
include_str!("js/event_bootstrap.js"),
"\n",
include_str!("js/canvas_bootstrap.js"),
"\n",
include_str!("js/window_bootstrap.js"),
"\n",
include_str!("js/streams_bootstrap.js"),
"\n",
include_str!("js/structured_clone.js"),
);
runtime
.execute_script("<anonymous>", BOOTSTRAP_JS)
.expect("snapshot bootstrap failed");
let snapshot = runtime.snapshot();
if let Some(ref path) = cache_path {
let tmp = path.with_extension(format!("tmp.{}", std::process::id()));
if std::fs::write(&tmp, &snapshot).is_ok() {
if let Err(e) = std::fs::rename(&tmp, path) {
tracing::debug!("snapshot cache rename failed: {e}");
let _ = std::fs::remove_file(&tmp);
} else {
tracing::info!("Cached V8 snapshot to {}", path.display());
}
}
}
snapshot
})
}