use std::cell::RefCell; use std::future::Future; use std::rc::Rc; use std::sync::Arc; use std::sync::atomic::Ordering; use std::task::Poll; use std::time::Duration; use avril_caps::Capabilities; use avril_core::{Extension, ModuleLoader, Runtime, RuntimeOptions, Snapshot}; use avril_ext_workers::{Control, WorkerScope}; use avril_host::environment::Environment; use avril_host::messaging::{Broker, Endpoint}; use futures::future::{Either, select}; use tokio_util::sync::CancellationToken; use crate::modules::ModuleGraph; use crate::registry::PackageStore; mod snapshots; pub use snapshots::create as create_snapshot; #[cfg(test)] mod tests; #[derive(Clone)] pub struct Settings { pub snapshot: Option>, pub storage: avril_webstorage::Persistent, pub caps: Arc, pub environment: Environment, pub args: Vec, pub store: Arc, pub runtime: tokio::runtime::Handle, pub broker: Arc, pub process_signal: avril_ext_process::SignalMode, } pub fn runtime( graph: Rc, settings: Settings, stdio: Rc, listener: Option, test: Option, ) -> Result> { create_runtime(graph, settings, stdio, None, listener, test) } fn create_runtime( graph: Rc, settings: Settings, stdio: Rc, worker: Option, listener: Option, test: Option, ) -> Result> { let snapshot = settings.snapshot.clone(); let extensions = extensions(graph.clone(), settings, stdio, worker, listener, test)?; let options = RuntimeOptions { extensions, module_loader: Some(graph), }; let mut runtime = match snapshot.as_deref() { Some(snapshot) => Runtime::from_snapshot(options, snapshot)?, None => Runtime::new(options)?, }; crate::stdlib::register(&mut runtime)?; Ok(runtime) } fn extensions( graph: Rc, settings: Settings, stdio: Rc, worker: Option, listener: Option, test: Option, ) -> Result, Box> { let environment = Rc::new(RefCell::new(settings.environment.clone())); // Outside `avril test`, `test()` registers harmlessly and never runs. let test = test.unwrap_or_else(|| avril_ext_test::idle(&stdio)); let (websocket, sockets) = avril_ext_websocket::init(settings.runtime.clone(), settings.caps.clone())?; let (file, files) = avril_ext_file::init( settings.runtime.clone(), settings.caps.clone(), stdio.interrupt_flag(), ); let top_level = worker.is_none(); let mut extensions = vec![ avril_ext_web::with_broker(settings.broker.clone()), avril_ext_timers::init()?, avril_ext_url::init(), avril_ext_urlpattern::init(), avril_ext_webstreams::init(), avril_compression::init(), avril_webcrypto::init(), avril_ext_fjson::init(), avril_ext_kv::init(settings.runtime.clone(), settings.caps.clone()), avril_ext_task::init(), avril_ffi::init(settings.caps.clone()), avril_ext_encoding::init(), avril_ext_blob::init(), avril_ext_form_data::init(), avril_ext_http_types::init(), avril_ext_fetch::init( settings.runtime.clone(), settings.caps.clone(), graph.entry_url(), ), avril_ext_net::init(settings.runtime.clone(), settings.caps.clone()), avril_ext_http::init( settings.runtime.clone(), settings.caps.clone(), sockets, files.clone(), listener, ), websocket, avril_ext_wasmstreaming::init(), avril_ext_structured_clone::init(), file, avril_ext_process::init( settings.runtime.clone(), settings.caps.clone(), environment.clone(), files, worker .as_ref() .map_or_else(CancellationToken::new, |worker| { worker.control.process_cancellation() }), worker.is_some(), settings.process_signal, ), stdio.extension(), avril_ext_test::init(test), avril_ext_base::init( settings.caps.clone(), environment.clone(), settings.args.clone(), ), avril_ext_console::init(stdio), avril_ext_path::init(), avril_ext_workers::init( move || graph.entry_url(), settings.caps.clone(), settings.broker.clone(), Arc::new(settings.clone()), environment, worker, ), ]; // Web Storage is a top-level-only API; worker runtimes omit it entirely. if top_level { extensions.push(avril_webstorage::init(settings.storage.clone())); } Ok(extensions) } impl avril_ext_workers::Launch for Settings { fn run( &self, request: avril_ext_workers::Request, control: Arc, inbox: Arc, ) -> Result<(), Box> { let mut settings = self.clone(); settings.caps = request.caps; settings.environment = request.environment; let specifier = request.specifier; let referrer = request.referrer; let name = request.name; let authority = avril_runtime::authority::SourceAuthority::execute(settings.caps.clone()); let graph = Rc::new(ModuleGraph::open_shared(authority, settings.store.clone())); let host = settings.runtime.clone(); let entry = host.block_on(async { match select( Box::pin(graph.prepare(specifier, referrer)), Box::pin(control.cancelled()), ) .await { Either::Left((entry, _)) => entry.map(Some), Either::Right(_) => Ok(None), } })?; let Some(entry) = entry else { return Ok(()); }; graph.set_entry(&entry); let url = avril_core::ModuleLoader::module_url(&*graph, &entry).unwrap_or_else(|| entry.clone()); let terminated = control.termination_flag(); let stdio = avril_ext_stdio::Stdio::new_for_worker(terminated.clone())?; let mut runtime = create_runtime( graph, settings, stdio.clone(), Some(WorkerScope { control: control.clone(), inbox, url, name, }), None, None, )?; control.set_isolate(Some(runtime.isolate_handle())); let result = runtime.start_module(&entry).and_then(|()| { let mut background_delay = None; host.block_on(futures::future::poll_fn(|cx| { control.register_waker(cx.waker()); if control.is_stopped() { return Poll::Ready(Ok(())); } let background_pending = runtime.has_pending_background_tasks(); if let Err(error) = runtime.poll_event_loop(cx) { return Poll::Ready(Err(error)); } // The default V8 platform does not notify the host about foreground tasks. if background_pending || runtime.has_pending_background_tasks() { let delay = background_delay.get_or_insert_with(|| { Box::pin(tokio::time::sleep(Duration::from_millis(20))) }); if delay.as_mut().poll(cx).is_ready() { background_delay = None; cx.waker().wake_by_ref(); } } else { background_delay = None; } Poll::Pending })) }); let flushed = stdio.flush_interruptible(|| terminated.load(Ordering::SeqCst)); control.set_isolate(None); result?; flushed?; Ok(()) } }