pub mod extensions;
pub mod module_loader;
pub mod native_fns;
pub mod runtime;
pub mod snapshot;
pub mod state;
pub mod utils;
use crate::dom::Dom;
use crate::stealth::StealthProfile;
use deno_core::v8;
use deno_core::JsRuntime;
use extensions::nav_ext::NavSignal;
use runtime::{create_runtime_with_signals, BrowserRuntimeOptions};
use state::{ConsoleMessage, DomState};
pub struct BrowserJsRuntime {
inner: JsRuntime,
nav_signal: NavSignal,
}
struct IsolateEnterGuard {
isolate: *mut v8::Isolate,
}
impl IsolateEnterGuard {
fn enter(isolate: &mut v8::OwnedIsolate) -> Self {
let isolate: *mut v8::Isolate = &mut **isolate;
unsafe { (*isolate).enter() };
Self { isolate }
}
}
impl Drop for IsolateEnterGuard {
fn drop(&mut self) {
unsafe { (*self.isolate).exit() };
}
}
impl BrowserJsRuntime {
pub fn new(dom: Dom) -> Self {
let (inner, nav_signal) =
create_runtime_with_signals(dom, BrowserRuntimeOptions::default());
Self { inner, nav_signal }
}
pub fn with_profile(dom: Dom, profile: StealthProfile) -> Self {
let (inner, nav_signal) = create_runtime_with_signals(
dom,
BrowserRuntimeOptions {
stealth_profile: Some(profile),
..Default::default()
},
);
Self { inner, nav_signal }
}
pub fn with_options(dom: Dom, mut options: BrowserRuntimeOptions) -> Self {
if std::env::var_os("BROWSER_OXIDE_USE_SNAPSHOT").is_some()
&& options.startup_snapshot.is_none()
{
options.startup_snapshot = Some(snapshot::get_snapshot());
}
let (inner, nav_signal) = create_runtime_with_signals(dom, options);
Self { inner, nav_signal }
}
pub fn nav_pending(&self) -> bool {
self.nav_signal.pending()
}
pub fn reset_nav_pending(&self) {
self.nav_signal.reset();
}
pub fn isolate_handle(&mut self) -> deno_core::v8::IsolateHandle {
self.inner.v8_isolate().thread_safe_handle()
}
pub fn cancel_terminate_execution(&mut self) {
self.inner.v8_isolate().cancel_terminate_execution();
}
pub fn execute_script(
&mut self,
code: &str,
name: Option<&str>,
) -> Result<String, deno_core::error::AnyError> {
let __ctx = self.inner.main_context();
let _isolate_guard = IsolateEnterGuard::enter(self.inner.v8_isolate());
deno_core::v8::scope_with_context!(scope, self.inner.v8_isolate(), __ctx);
let source = deno_core::v8::String::new(scope, code)
.ok_or_else(|| deno_core::error::AnyError::msg("failed to create V8 string"))?;
let mut script_origin = None;
if let Some(n) = name {
let n_v8 = deno_core::v8::String::new(scope, n).unwrap();
let resource_name = n_v8.into();
script_origin = Some(deno_core::v8::ScriptOrigin::new(
scope,
resource_name,
0,
0,
false,
0,
None,
false,
false,
false,
None,
));
}
deno_core::v8::tc_scope!(let tc_scope, scope);
let script = deno_core::v8::Script::compile(tc_scope, source, script_origin.as_ref())
.ok_or_else(|| {
let exception = match tc_scope.exception() {
Some(exc) => exc,
None => return deno_core::error::AnyError::msg("script compilation failed"),
};
let msg = exception
.to_string(tc_scope)
.map(|s| s.to_rust_string_lossy(tc_scope))
.unwrap_or_default();
deno_core::error::AnyError::msg(msg)
})?;
match script.run(tc_scope) {
Some(value) => Ok(value
.to_string(tc_scope)
.map(|s| s.to_rust_string_lossy(tc_scope))
.unwrap_or_default()),
None => {
let exception = match tc_scope.exception() {
Some(exc) => exc,
None => return Err(deno_core::error::AnyError::msg("script execution failed")),
};
let msg = exception
.to_string(tc_scope)
.map(|s| s.to_rust_string_lossy(tc_scope))
.unwrap_or_default();
Err(deno_core::error::AnyError::msg(msg))
}
}
}
pub async fn run_event_loop(&mut self) -> Result<(), deno_core::error::AnyError> {
let _isolate_guard = IsolateEnterGuard::enter(self.inner.v8_isolate());
self.inner
.run_event_loop(deno_core::PollEventLoopOptions::default())
.await
.map_err(|e| deno_core::error::AnyError::msg(e.to_string()))
}
pub async fn load_eval_module_url(
&mut self,
url: &str,
) -> Result<(), deno_core::error::AnyError> {
let spec = deno_core::ModuleSpecifier::parse(url)
.map_err(|e| deno_core::error::AnyError::msg(format!("module url {url}: {e}")))?;
let _isolate_guard = IsolateEnterGuard::enter(self.inner.v8_isolate());
let mod_id = self.inner.load_main_es_module(&spec).await?;
self.eval_module(mod_id).await
}
pub async fn load_eval_module_code(
&mut self,
specifier: &str,
code: String,
) -> Result<(), deno_core::error::AnyError> {
let spec = deno_core::ModuleSpecifier::parse(specifier).map_err(|e| {
deno_core::error::AnyError::msg(format!("inline module spec {specifier}: {e}"))
})?;
let _isolate_guard = IsolateEnterGuard::enter(self.inner.v8_isolate());
let mod_id = self
.inner
.load_main_es_module_from_code(&spec, code)
.await?;
self.eval_module(mod_id).await
}
async fn eval_module(
&mut self,
mod_id: deno_core::ModuleId,
) -> Result<(), deno_core::error::AnyError> {
let _isolate_guard = IsolateEnterGuard::enter(self.inner.v8_isolate());
let eval = self.inner.mod_evaluate(mod_id);
self.inner
.run_event_loop(deno_core::PollEventLoopOptions::default())
.await
.map_err(|e| deno_core::error::AnyError::msg(e.to_string()))?;
eval.await
.map_err(|e| deno_core::error::AnyError::msg(e.to_string()))
}
pub fn console_output(&mut self) -> Vec<ConsoleMessage> {
let state = self.inner.op_state();
let state = state.borrow();
state.borrow::<DomState>().console_output.clone()
}
pub fn replace_dom(&mut self, dom: Dom, stylesheets: Vec<String>) {
let state = self.inner.op_state();
let mut state = state.borrow_mut();
let mut dom_state = DomState::new(dom);
dom_state.stylesheets = stylesheets;
dom_state.update_cached_rules();
state.put(dom_state);
state.put(extensions::timer_ext::TimerState::new());
}
pub fn take_dom(self) -> Dom {
let state = self.inner.op_state();
let mut state = state.borrow_mut();
state.take::<DomState>().dom
}
pub fn get_storage(
&mut self,
) -> std::collections::HashMap<String, std::collections::HashMap<String, String>> {
let state = self.inner.op_state();
let state = state.borrow();
state.borrow::<DomState>().storage.clone()
}
pub fn inner(&mut self) -> &mut JsRuntime {
&mut self.inner
}
pub fn op_state(&mut self) -> std::rc::Rc<std::cell::RefCell<deno_core::OpState>> {
self.inner.op_state()
}
pub fn record_resource_timing(&mut self, timings: crate::net::TimingStats) {
let op_state = self.inner.op_state();
let mut state = op_state.borrow_mut();
extensions::fetch_ext::record_resource_timing(&mut state, timings);
}
}