1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-30 16:40:57 -05:00
denoland-deno/core/extensions.rs
Aaron O'Mullan 4154e95ae5 feat(core): disableable extensions & ops (#14063)
Streamlines a common middleware pattern and provides foundations for avoiding variably sized v8::ExternalReferences & enabling fully monomorphic op callpaths
2022-04-07 09:42:33 -04:00

198 lines
5.2 KiB
Rust

// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use crate::OpState;
use anyhow::Error;
use std::task::Context;
pub type SourcePair = (&'static str, Box<SourceLoadFn>);
pub type SourceLoadFn = dyn Fn() -> Result<String, Error>;
pub type OpFnRef = v8::FunctionCallback;
pub type OpMiddlewareFn = dyn Fn(OpDecl) -> OpDecl;
pub type OpStateFn = dyn Fn(&mut OpState) -> Result<(), Error>;
pub type OpEventLoopFn = dyn Fn(&mut OpState, &mut Context) -> bool;
#[derive(Clone, Copy)]
pub struct OpDecl {
pub name: &'static str,
pub v8_fn_ptr: OpFnRef,
pub enabled: bool,
pub is_async: bool, // TODO(@AaronO): enum sync/async/fast ?
}
impl OpDecl {
pub fn enabled(self, enabled: bool) -> Self {
Self { enabled, ..self }
}
pub fn disable(self) -> Self {
self.enabled(false)
}
}
#[derive(Default)]
pub struct Extension {
js_files: Option<Vec<SourcePair>>,
ops: Option<Vec<OpDecl>>,
opstate_fn: Option<Box<OpStateFn>>,
middleware_fn: Option<Box<OpMiddlewareFn>>,
event_loop_middleware: Option<Box<OpEventLoopFn>>,
initialized: bool,
enabled: bool,
}
// Note: this used to be a trait, but we "downgraded" it to a single concrete type
// for the initial iteration, it will likely become a trait in the future
impl Extension {
pub fn builder() -> ExtensionBuilder {
Default::default()
}
/// returns JS source code to be loaded into the isolate (either at snapshotting,
/// or at startup). as a vector of a tuple of the file name, and the source code.
pub fn init_js(&self) -> &[SourcePair] {
match &self.js_files {
Some(files) => files,
None => &[],
}
}
/// Called at JsRuntime startup to initialize ops in the isolate.
pub fn init_ops(&mut self) -> Option<Vec<OpDecl>> {
// TODO(@AaronO): maybe make op registration idempotent
if self.initialized {
panic!("init_ops called twice: not idempotent or correct");
}
self.initialized = true;
let mut ops = self.ops.take()?;
for op in ops.iter_mut() {
op.enabled = self.enabled && op.enabled;
}
Some(ops)
}
/// Allows setting up the initial op-state of an isolate at startup.
pub fn init_state(&self, state: &mut OpState) -> Result<(), Error> {
match &self.opstate_fn {
Some(ofn) => ofn(state),
None => Ok(()),
}
}
/// init_middleware lets us middleware op registrations, it's called before init_ops
pub fn init_middleware(&mut self) -> Option<Box<OpMiddlewareFn>> {
self.middleware_fn.take()
}
pub fn init_event_loop_middleware(&mut self) -> Option<Box<OpEventLoopFn>> {
self.event_loop_middleware.take()
}
pub fn run_event_loop_middleware(
&self,
op_state: &mut OpState,
cx: &mut Context,
) -> bool {
self
.event_loop_middleware
.as_ref()
.map(|f| f(op_state, cx))
.unwrap_or(false)
}
pub fn enabled(self, enabled: bool) -> Self {
Self { enabled, ..self }
}
pub fn disable(self) -> Self {
self.enabled(false)
}
}
// Provides a convenient builder pattern to declare Extensions
#[derive(Default)]
pub struct ExtensionBuilder {
js: Vec<SourcePair>,
ops: Vec<OpDecl>,
state: Option<Box<OpStateFn>>,
middleware: Option<Box<OpMiddlewareFn>>,
event_loop_middleware: Option<Box<OpEventLoopFn>>,
}
impl ExtensionBuilder {
pub fn js(&mut self, js_files: Vec<SourcePair>) -> &mut Self {
self.js.extend(js_files);
self
}
pub fn ops(&mut self, ops: Vec<OpDecl>) -> &mut Self {
self.ops.extend(ops);
self
}
pub fn state<F>(&mut self, opstate_fn: F) -> &mut Self
where
F: Fn(&mut OpState) -> Result<(), Error> + 'static,
{
self.state = Some(Box::new(opstate_fn));
self
}
pub fn middleware<F>(&mut self, middleware_fn: F) -> &mut Self
where
F: Fn(OpDecl) -> OpDecl + 'static,
{
self.middleware = Some(Box::new(middleware_fn));
self
}
pub fn event_loop_middleware<F>(&mut self, middleware_fn: F) -> &mut Self
where
F: Fn(&mut OpState, &mut Context) -> bool + 'static,
{
self.event_loop_middleware = Some(Box::new(middleware_fn));
self
}
pub fn build(&mut self) -> Extension {
let js_files = Some(std::mem::take(&mut self.js));
let ops = Some(std::mem::take(&mut self.ops));
Extension {
js_files,
ops,
opstate_fn: self.state.take(),
middleware_fn: self.middleware.take(),
event_loop_middleware: self.event_loop_middleware.take(),
initialized: false,
enabled: true,
}
}
}
/// Helps embed JS files in an extension. Returns Vec<(&'static str, Box<SourceLoadFn>)>
/// representing the filename and source code. This is only meant for extensions
/// that will be snapshotted, as code will be loaded at runtime.
///
/// Example:
/// ```ignore
/// include_js_files!(
/// prefix "deno:extensions/hello",
/// "01_hello.js",
/// "02_goodbye.js",
/// )
/// ```
#[macro_export]
macro_rules! include_js_files {
(prefix $prefix:literal, $($file:literal,)+) => {
vec![
$((
concat!($prefix, "/", $file),
Box::new(|| {
let c = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let path = c.join($file);
println!("cargo:rerun-if-changed={}", path.display());
let src = std::fs::read_to_string(path)?;
Ok(src)
}),
),)+
]
};
}