rw_deno_core/
extensions.rs

1// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2use crate::modules::IntoModuleCodeString;
3use crate::modules::ModuleCodeString;
4use crate::ops::OpMetadata;
5use crate::runtime::bindings;
6use crate::FastStaticString;
7use crate::OpState;
8use anyhow::Context as _;
9use anyhow::Error;
10use std::borrow::Cow;
11use std::marker::PhantomData;
12use std::sync::Arc;
13use v8::fast_api::FastFunction;
14use v8::MapFnTo;
15
16#[derive(Clone)]
17pub enum ExtensionFileSourceCode {
18  /// Source code is included in the binary produced. Either by being defined
19  /// inline, or included using `include_str!()`. If you are snapshotting, this
20  /// will result in two copies of the source code being included - one in the
21  /// snapshot, the other the static string in the `Extension`.
22  #[deprecated = "Use ExtensionFileSource::new"]
23  IncludedInBinary(FastStaticString),
24
25  // Source code is loaded from a file on disk. It's meant to be used if the
26  // embedder is creating snapshots. Files will be loaded from the filesystem
27  // during the build time and they will only be present in the V8 snapshot.
28  LoadedFromFsDuringSnapshot(&'static str), // <- Path
29
30  // Source code was loaded from memory. It's meant to be used if the
31  // embedder is creating snapshots. Files will be loaded from memory
32  // during the build time and they will only be present in the V8 snapshot.
33  LoadedFromMemoryDuringSnapshot(FastStaticString),
34
35  /// Source code may be computed at runtime.
36  Computed(Arc<str>),
37}
38
39#[allow(deprecated)]
40impl std::fmt::Debug for ExtensionFileSourceCode {
41  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42    match *self {
43      Self::IncludedInBinary(..) => write!(f, "IncludedInBinary(..)"),
44      Self::LoadedFromFsDuringSnapshot(path) => {
45        write!(f, "LoadedFromFsDuringSnapshot({path})")
46      }
47      Self::LoadedFromMemoryDuringSnapshot(..) => {
48        write!(f, "LoadedFromMemoryDuringSnapshot(..)")
49      }
50      Self::Computed(..) => write!(f, "Computed(..)"),
51    }
52  }
53}
54
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub enum ExtensionSourceType {
57  LazyEsm,
58  Js,
59  Esm,
60}
61
62#[derive(Clone, Debug)]
63pub struct ExtensionFileSource {
64  pub specifier: &'static str,
65  pub code: ExtensionFileSourceCode,
66  _unconstructable_use_new: PhantomData<()>,
67}
68
69impl ExtensionFileSource {
70  pub const fn new(specifier: &'static str, code: FastStaticString) -> Self {
71    #[allow(deprecated)]
72    Self {
73      specifier,
74      code: ExtensionFileSourceCode::IncludedInBinary(code),
75      _unconstructable_use_new: PhantomData,
76    }
77  }
78
79  pub const fn new_computed(specifier: &'static str, code: Arc<str>) -> Self {
80    #[allow(deprecated)]
81    Self {
82      specifier,
83      code: ExtensionFileSourceCode::Computed(code),
84      _unconstructable_use_new: PhantomData,
85    }
86  }
87
88  pub const fn loaded_during_snapshot(
89    specifier: &'static str,
90    path: &'static str,
91  ) -> Self {
92    #[allow(deprecated)]
93    Self {
94      specifier,
95      code: ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path),
96      _unconstructable_use_new: PhantomData,
97    }
98  }
99
100  pub const fn loaded_from_memory_during_snapshot(
101    specifier: &'static str,
102    code: FastStaticString,
103  ) -> Self {
104    #[allow(deprecated)]
105    Self {
106      specifier,
107      code: ExtensionFileSourceCode::LoadedFromMemoryDuringSnapshot(code),
108      _unconstructable_use_new: PhantomData,
109    }
110  }
111
112  fn find_non_ascii(s: &str) -> String {
113    s.chars().filter(|c| !c.is_ascii()).collect::<String>()
114  }
115
116  #[allow(deprecated)]
117  pub fn load(&self) -> Result<ModuleCodeString, Error> {
118    match &self.code {
119      ExtensionFileSourceCode::LoadedFromMemoryDuringSnapshot(code)
120      | ExtensionFileSourceCode::IncludedInBinary(code) => {
121        debug_assert!(
122          code.is_ascii(),
123          "Extension code must be 7-bit ASCII: {} (found {})",
124          self.specifier,
125          Self::find_non_ascii(code)
126        );
127        Ok(IntoModuleCodeString::into_module_code(*code))
128      }
129      ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path) => {
130        let msg = || format!("Failed to read \"{}\"", path);
131        let s = std::fs::read_to_string(path).with_context(msg)?;
132        debug_assert!(
133          s.is_ascii(),
134          "Extension code must be 7-bit ASCII: {} (found {})",
135          self.specifier,
136          Self::find_non_ascii(&s)
137        );
138        Ok(s.into())
139      }
140      ExtensionFileSourceCode::Computed(code) => {
141        debug_assert!(
142          code.is_ascii(),
143          "Extension code must be 7-bit ASCII: {} (found {})",
144          self.specifier,
145          Self::find_non_ascii(code)
146        );
147        Ok(ModuleCodeString::from(code.clone()))
148      }
149    }
150  }
151}
152
153pub type OpFnRef = v8::FunctionCallback;
154pub type OpMiddlewareFn = dyn Fn(OpDecl) -> OpDecl;
155pub type OpStateFn = dyn FnOnce(&mut OpState);
156/// Trait implemented by all generated ops.
157pub trait Op {
158  const NAME: &'static str;
159  const DECL: OpDecl;
160}
161pub type GlobalTemplateMiddlewareFn =
162  for<'s> fn(
163    &mut v8::HandleScope<'s, ()>,
164    v8::Local<'s, v8::ObjectTemplate>,
165  ) -> v8::Local<'s, v8::ObjectTemplate>;
166pub type GlobalObjectMiddlewareFn =
167  for<'s> fn(&mut v8::HandleScope<'s>, v8::Local<'s, v8::Object>);
168
169extern "C" fn noop() {}
170
171#[derive(Clone, Copy)]
172pub struct OpDecl {
173  pub name: &'static str,
174  pub(crate) name_fast: FastStaticString,
175  pub is_async: bool,
176  pub is_reentrant: bool,
177  pub arg_count: u8,
178  /// The slow dispatch call. If metrics are disabled, the `v8::Function` is created with this callback.
179  pub(crate) slow_fn: OpFnRef,
180  /// The slow dispatch call with metrics enabled. If metrics are enabled, the `v8::Function` is created with this callback.
181  pub(crate) slow_fn_with_metrics: OpFnRef,
182  /// The fast dispatch call. If metrics are disabled, the `v8::Function`'s fastcall is created with this callback.
183  pub(crate) fast_fn: Option<FastFunction>,
184  /// The fast dispatch call with metrics enabled. If metrics are enabled, the `v8::Function`'s fastcall is created with this callback.
185  pub(crate) fast_fn_with_metrics: Option<FastFunction>,
186  /// Any metadata associated with this op.
187  pub metadata: OpMetadata,
188}
189
190impl OpDecl {
191  /// For use by internal op implementation only.
192  #[doc(hidden)]
193  #[allow(clippy::too_many_arguments)]
194  pub const fn new_internal_op2(
195    name: (&'static str, FastStaticString),
196    is_async: bool,
197    is_reentrant: bool,
198    arg_count: u8,
199    slow_fn: OpFnRef,
200    slow_fn_with_metrics: OpFnRef,
201    fast_fn: Option<FastFunction>,
202    fast_fn_with_metrics: Option<FastFunction>,
203    metadata: OpMetadata,
204  ) -> Self {
205    #[allow(deprecated)]
206    Self {
207      name: name.0,
208      name_fast: name.1,
209      is_async,
210      is_reentrant,
211      arg_count,
212      slow_fn,
213      slow_fn_with_metrics,
214      fast_fn,
215      fast_fn_with_metrics,
216      metadata,
217    }
218  }
219
220  /// Returns a copy of this `OpDecl` that replaces underlying functions
221  /// with noops.
222  pub fn disable(self) -> Self {
223    Self {
224      slow_fn: bindings::op_disabled_fn.map_fn_to(),
225      slow_fn_with_metrics: bindings::op_disabled_fn.map_fn_to(),
226      // TODO(bartlomieju): Currently this fast fn won't throw like `op_disabled_fn`;
227      // ideally we would add a fallback that would throw, but it's unclear
228      // if disabled op (that throws in JS) would ever get optimized to become
229      // a fast function.
230      fast_fn: if self.fast_fn.is_some() {
231        Some(FastFunction {
232          args: &[],
233          function: noop as _,
234          repr: v8::fast_api::Int64Representation::Number,
235          return_type: v8::fast_api::CType::Void,
236        })
237      } else {
238        None
239      },
240      fast_fn_with_metrics: if self.fast_fn_with_metrics.is_some() {
241        Some(FastFunction {
242          args: &[],
243          function: noop as _,
244          repr: v8::fast_api::Int64Representation::Number,
245          return_type: v8::fast_api::CType::Void,
246        })
247      } else {
248        None
249      },
250      ..self
251    }
252  }
253
254  /// Returns a copy of this `OpDecl` with the implementation function set to the function from another
255  /// `OpDecl`.
256  pub const fn with_implementation_from(mut self, from: &Self) -> Self {
257    self.slow_fn = from.slow_fn;
258    self.slow_fn_with_metrics = from.slow_fn_with_metrics;
259    self.fast_fn = from.fast_fn;
260    self.fast_fn_with_metrics = from.fast_fn_with_metrics;
261    self
262  }
263
264  #[doc(hidden)]
265  pub const fn fast_fn(&self) -> FastFunction {
266    let Some(f) = self.fast_fn else {
267      panic!("Not a fast function");
268    };
269    f
270  }
271
272  #[doc(hidden)]
273  pub const fn fast_fn_with_metrics(&self) -> FastFunction {
274    let Some(f) = self.fast_fn_with_metrics else {
275      panic!("Not a fast function");
276    };
277    f
278  }
279}
280
281/// Declares a block of Deno `#[op]`s. The first parameter determines the name of the
282/// op declaration block, and is usually `deno_ops`. This block generates a function that
283/// returns a [`Vec<OpDecl>`].
284///
285/// This can be either a compact form like:
286///
287/// ```no_compile
288/// # use deno_core::*;
289/// #[op]
290/// fn op_xyz() {}
291///
292/// deno_core::ops!(deno_ops, [
293///   op_xyz
294/// ]);
295///
296/// // Use the ops:
297/// deno_ops()
298/// ```
299///
300/// ... or a parameterized form like so that allows passing a number of type parameters
301/// to each `#[op]`:
302///
303/// ```no_compile
304/// # use deno_core::*;
305/// #[op]
306/// fn op_xyz<P>() where P: Clone {}
307///
308/// deno_core::ops!(deno_ops,
309///   parameters = [P: Clone],
310///   ops = [
311///     op_xyz<P>
312///   ]
313/// );
314///
315/// // Use the ops, with `String` as the parameter `P`:
316/// deno_ops::<String>()
317/// ```
318#[macro_export]
319macro_rules! ops {
320  ($name:ident, parameters = [ $( $param:ident : $type:ident ),+ ], ops = [ $( $(#[$m:meta])* $( $op:ident )::+ $( < $op_param:ident > )?  ),+ $(,)? ]) => {
321    pub(crate) fn $name < $( $param : $type + 'static ),+ > () -> ::std::vec::Vec<$crate::OpDecl> {
322      vec![
323      $(
324        $( #[ $m ] )*
325        $( $op )::+ :: decl $( :: <$op_param> )? () ,
326      )+
327      ]
328    }
329  };
330  ($name:ident, [ $( $(#[$m:meta])* $( $op:ident )::+ ),+ $(,)? ] ) => {
331    pub(crate) fn $name() -> ::std::Vec<$crate::OpDecl> {
332      use $crate::Op;
333      vec![
334        $( $( #[ $m ] )* $( $op )::+ :: DECL, )+
335      ]
336    }
337  }
338}
339
340/// Return the first argument if not empty, otherwise the second.
341#[macro_export]
342macro_rules! or {
343  ($e:expr, $fallback:expr) => {
344    $e
345  };
346  (, $fallback:expr) => {
347    $fallback
348  };
349}
350
351/// Defines a Deno extension. The first parameter is the name of the extension symbol namespace to create. This is the symbol you
352/// will use to refer to the extension.
353///
354/// Most extensions will define a combination of ops and ESM files, like so:
355///
356/// ```no_compile
357/// #[op]
358/// fn op_xyz() {
359/// }
360///
361/// deno_core::extension!(
362///   my_extension,
363///   ops = [ op_xyz ],
364///   esm = [ "my_script.js" ],
365///   docs = "A small sample extension"
366/// );
367/// ```
368///
369/// The following options are available for the [`extension`] macro:
370///
371///  * deps: a comma-separated list of module dependencies, eg: `deps = [ my_other_extension ]`
372///  * parameters: a comma-separated list of parameters and base traits, eg: `parameters = [ P: MyTrait ]`
373///  * bounds: a comma-separated list of additional type bounds, eg: `bounds = [ P::MyAssociatedType: MyTrait ]`
374///  * ops: a comma-separated list of [`OpDecl`]s to provide, eg: `ops = [ op_foo, op_bar ]`
375///  * esm: a comma-separated list of ESM module filenames (see [`include_js_files`]), eg: `esm = [ dir "dir", "my_file.js" ]`
376///  * lazy_loaded_esm: a comma-separated list of ESM module filenames (see [`include_js_files`]), that will be included in
377///     the produced binary, but not automatically evaluated. Eg: `lazy_loaded_esm = [ dir "dir", "my_file.js" ]`
378///  * js: a comma-separated list of JS filenames (see [`include_js_files`]), eg: `js = [ dir "dir", "my_file.js" ]`
379///  * config: a structure-like definition for configuration parameters which will be required when initializing this extension, eg: `config = { my_param: Option<usize> }`
380///  * middleware: an [`OpDecl`] middleware function with the signature `fn (OpDecl) -> OpDecl`
381///  * state: a state initialization function, with the signature `fn (&mut OpState, ...) -> ()`, where `...` are parameters matching the fields of the config struct
382///  * global_template_middleware: a global template middleware function (see [`Extension::global_template_middleware`])
383///  * global_object_middleware: a global object middleware function (see [`Extension::global_object_middleware`])
384///  * docs: comma separated list of toplevel #[doc=...] tags to be applied to the extension's resulting struct
385#[macro_export]
386macro_rules! extension {
387  (
388    $name:ident
389    $(, deps = [ $( $dep:ident ),* ] )?
390    $(, parameters = [ $( $param:ident : $type:ident ),+ ] )?
391    $(, bounds = [ $( $bound:path : $bound_type:ident ),+ ] )?
392    $(, ops_fn = $ops_symbol:ident $( < $ops_param:ident > )? )?
393    $(, ops = [ $( $(#[$m:meta])* $( $op:ident )::+ $( < $( $op_param:ident ),* > )?  ),+ $(,)? ] )?
394    $(, esm_entry_point = $esm_entry_point:expr )?
395    $(, esm = [ $($esm:tt)* ] )?
396    $(, lazy_loaded_esm = [ $($lazy_loaded_esm:tt)* ] )?
397    $(, js = [ $($js:tt)* ] )?
398    $(, options = { $( $options_id:ident : $options_type:ty ),* $(,)? } )?
399    $(, middleware = $middleware_fn:expr )?
400    $(, state = $state_fn:expr )?
401    $(, global_template_middleware = $global_template_middleware_fn:expr )?
402    $(, global_object_middleware = $global_object_middleware_fn:expr )?
403    $(, external_references = [ $( $external_reference:expr ),* $(,)? ] )?
404    $(, customizer = $customizer_fn:expr )?
405    $(, docs = $($docblocks:expr),+)?
406    $(,)?
407  ) => {
408    $( $(#[doc = $docblocks])+ )?
409    ///
410    /// An extension for use with the Deno JS runtime.
411    /// To use it, provide it as an argument when instantiating your runtime:
412    ///
413    /// ```rust,ignore
414    /// use deno_core::{ JsRuntime, RuntimeOptions };
415    ///
416    #[doc = concat!("let mut extensions = vec![", stringify!($name), "::init_ops_and_esm()];")]
417    /// let mut js_runtime = JsRuntime::new(RuntimeOptions {
418    ///   extensions,
419    ///   ..Default::default()
420    /// });
421    /// ```
422    ///
423    #[allow(non_camel_case_types)]
424    pub struct $name {
425    }
426
427    impl $name {
428      fn ext $( <  $( $param : $type + 'static ),+ > )?() -> $crate::Extension {
429        #[allow(unused_imports)]
430        use $crate::Op;
431        $crate::Extension {
432          // Computed at compile-time, may be modified at runtime with `Cow`:
433          name: ::std::stringify!($name),
434          deps: &[ $( $( ::std::stringify!($dep) ),* )? ],
435          // Use intermediary `const`s here to disable user expressions which
436          // can't be evaluated at compile-time.
437          js_files: {
438            const JS: &'static [$crate::ExtensionFileSource] = &$crate::include_js_files!( $name $($($js)*)? );
439            ::std::borrow::Cow::Borrowed(JS)
440          },
441          esm_files: {
442            const JS: &'static [$crate::ExtensionFileSource] = &$crate::include_js_files!( $name $($($esm)*)? );
443            ::std::borrow::Cow::Borrowed(JS)
444          },
445          lazy_loaded_esm_files: {
446            const JS: &'static [$crate::ExtensionFileSource] = &$crate::include_lazy_loaded_js_files!( $name $($($lazy_loaded_esm)*)? );
447            ::std::borrow::Cow::Borrowed(JS)
448          },
449          esm_entry_point: {
450            const V: ::std::option::Option<&'static ::std::primitive::str> = $crate::or!($(::std::option::Option::Some($esm_entry_point))?, ::std::option::Option::None);
451            V
452          },
453          ops: ::std::borrow::Cow::Borrowed(&[$($(
454            $( #[ $m ] )*
455            $( $op )::+ $( :: < $($op_param),* > )? :: DECL
456          ),+)?]),
457          external_references: ::std::borrow::Cow::Borrowed(&[ $( $external_reference ),* ]),
458          global_template_middleware: ::std::option::Option::None,
459          global_object_middleware: ::std::option::Option::None,
460          // Computed at runtime:
461          op_state_fn: ::std::option::Option::None,
462          middleware_fn: ::std::option::Option::None,
463          enabled: true,
464        }
465      }
466
467      // If ops were specified, add those ops to the extension.
468      #[inline(always)]
469      #[allow(unused_variables)]
470      fn with_ops_fn $( <  $( $param : $type + 'static ),+ > )?(ext: &mut $crate::Extension)
471      $( where $( $bound : $bound_type ),+ )?
472      {
473        // Use the ops_fn, if provided
474        $crate::extension!(! __ops__ ext $( $ops_symbol $( < $ops_param > )? )? __eot__);
475      }
476
477      // Includes the state and middleware functions, if defined.
478      #[inline(always)]
479      #[allow(unused_variables)]
480      fn with_state_and_middleware$( <  $( $param : $type + 'static ),+ > )?(ext: &mut $crate::Extension, $( $( $options_id : $options_type ),* )? )
481      $( where $( $bound : $bound_type ),+ )?
482      {
483        $crate::extension!(! __config__ ext $( parameters = [ $( $param : $type ),* ] )? $( config = { $( $options_id : $options_type ),* } )? $( state_fn = $state_fn )? );
484
485        $(
486          ext.global_template_middleware = ::std::option::Option::Some($global_template_middleware_fn);
487        )?
488
489        $(
490          ext.global_object_middleware = ::std::option::Option::Some($global_object_middleware_fn);
491        )?
492
493        $(
494          ext.middleware_fn = ::std::option::Option::Some(::std::boxed::Box::new($middleware_fn));
495        )?
496      }
497
498      #[inline(always)]
499      #[allow(unused_variables)]
500      #[allow(clippy::redundant_closure_call)]
501      fn with_customizer(ext: &mut $crate::Extension) {
502        $( ($customizer_fn)(ext); )?
503      }
504
505      #[allow(dead_code)]
506      /// Initialize this extension for runtime or snapshot creation. Use this
507      /// function if the runtime or snapshot is not created from a (separate)
508      /// snapshot, or that snapshot does not contain this extension. Otherwise
509      /// use `init_ops()` instead.
510      ///
511      /// # Returns
512      /// an Extension object that can be used during instantiation of a JsRuntime
513      pub fn init_ops_and_esm $( <  $( $param : $type + 'static ),+ > )? ( $( $( $options_id : $options_type ),* )? ) -> $crate::Extension
514      $( where $( $bound : $bound_type ),+ )?
515      {
516        let mut ext = Self::ext $( ::< $( $param ),+ > )?();
517        Self::with_ops_fn $( ::< $( $param ),+ > )?(&mut ext);
518        Self::with_state_and_middleware $( ::< $( $param ),+ > )?(&mut ext, $( $( $options_id , )* )? );
519        Self::with_customizer(&mut ext);
520        ext
521      }
522
523      #[allow(dead_code)]
524      /// Initialize this extension for runtime or snapshot creation, excluding
525      /// its JavaScript sources and evaluation. This is used when the runtime
526      /// or snapshot is created from a (separate) snapshot which includes this
527      /// extension in order to avoid evaluating the JavaScript twice.
528      ///
529      /// # Returns
530      /// an Extension object that can be used during instantiation of a JsRuntime
531      pub fn init_ops $( <  $( $param : $type + 'static ),+ > )? ( $( $( $options_id : $options_type ),* )? ) -> $crate::Extension
532      $( where $( $bound : $bound_type ),+ )?
533      {
534        let mut ext = Self::ext $( ::< $( $param ),+ > )?();
535        Self::with_ops_fn $( ::< $( $param ),+ > )?(&mut ext);
536        Self::with_state_and_middleware $( ::< $( $param ),+ > )?(&mut ext, $( $( $options_id , )* )? );
537        Self::with_customizer(&mut ext);
538        ext.js_files = ::std::borrow::Cow::Borrowed(&[]);
539        ext.esm_files = ::std::borrow::Cow::Borrowed(&[]);
540        ext.esm_entry_point = ::std::option::Option::None;
541        ext
542      }
543    }
544  };
545
546  // This branch of the macro generates a config object that calls the state function with itself.
547  (! __config__ $ext:ident $( parameters = [ $( $param:ident : $type:ident ),+ ] )? config = { $( $options_id:ident : $options_type:ty ),* } $( state_fn = $state_fn:expr )? ) => {
548    {
549      #[doc(hidden)]
550      struct Config $( <  $( $param : $type + 'static ),+ > )? {
551        $( pub $options_id : $options_type , )*
552        $( __phantom_data: ::std::marker::PhantomData<($( $param ),+)>, )?
553      }
554      let config = Config {
555        $( $options_id , )*
556        $( __phantom_data: ::std::marker::PhantomData::<($( $param ),+)>::default() )?
557      };
558
559      let state_fn: fn(&mut $crate::OpState, Config $( <  $( $param ),+ > )? ) = $(  $state_fn  )?;
560      $ext.op_state_fn = ::std::option::Option::Some(::std::boxed::Box::new(move |state: &mut $crate::OpState| {
561        state_fn(state, config);
562      }));
563    }
564  };
565
566  (! __config__ $ext:ident $( parameters = [ $( $param:ident : $type:ident ),+ ] )? $( state_fn = $state_fn:expr )? ) => {
567    $( $ext.op_state_fn = ::std::option::Option::Some(::std::boxed::Box::new($state_fn)); )?
568  };
569
570  (! __ops__ $ext:ident __eot__) => {
571  };
572
573  (! __ops__ $ext:ident $ops_symbol:ident __eot__) => {
574    $ext.ops.to_mut().extend($ops_symbol())
575  };
576
577  (! __ops__ $ext:ident $ops_symbol:ident < $ops_param:ident > __eot__) => {
578    $ext.ops.to_mut().extend($ops_symbol::<$ops_param>())
579  };
580}
581
582pub struct Extension {
583  pub name: &'static str,
584  pub deps: &'static [&'static str],
585  pub js_files: Cow<'static, [ExtensionFileSource]>,
586  pub esm_files: Cow<'static, [ExtensionFileSource]>,
587  pub lazy_loaded_esm_files: Cow<'static, [ExtensionFileSource]>,
588  pub esm_entry_point: Option<&'static str>,
589  pub ops: Cow<'static, [OpDecl]>,
590  pub external_references: Cow<'static, [v8::ExternalReference<'static>]>,
591  pub global_template_middleware: Option<GlobalTemplateMiddlewareFn>,
592  pub global_object_middleware: Option<GlobalObjectMiddlewareFn>,
593  pub op_state_fn: Option<Box<OpStateFn>>,
594  pub middleware_fn: Option<Box<OpMiddlewareFn>>,
595  pub enabled: bool,
596}
597
598impl Extension {
599  // Produces a new extension that is suitable for use during the warmup phase.
600  //
601  // JS sources are not included, and ops are include for external references only.
602  pub(crate) fn for_warmup(&self) -> Extension {
603    Self {
604      op_state_fn: None,
605      middleware_fn: None,
606      name: self.name,
607      deps: self.deps,
608      js_files: Cow::Borrowed(&[]),
609      esm_files: Cow::Borrowed(&[]),
610      lazy_loaded_esm_files: Cow::Borrowed(&[]),
611      esm_entry_point: None,
612      ops: self.ops.clone(),
613      external_references: self.external_references.clone(),
614      global_template_middleware: self.global_template_middleware,
615      global_object_middleware: self.global_object_middleware,
616      enabled: self.enabled,
617    }
618  }
619}
620
621impl Default for Extension {
622  fn default() -> Self {
623    Self {
624      name: "DEFAULT",
625      deps: &[],
626      js_files: Cow::Borrowed(&[]),
627      esm_files: Cow::Borrowed(&[]),
628      lazy_loaded_esm_files: Cow::Borrowed(&[]),
629      esm_entry_point: None,
630      ops: Cow::Borrowed(&[]),
631      external_references: Cow::Borrowed(&[]),
632      global_template_middleware: None,
633      global_object_middleware: None,
634      op_state_fn: None,
635      middleware_fn: None,
636      enabled: true,
637    }
638  }
639}
640
641// Note: this used to be a trait, but we "downgraded" it to a single concrete type
642// for the initial iteration, it will likely become a trait in the future
643impl Extension {
644  /// Check if dependencies have been loaded, and errors if either:
645  /// - The extension is depending on itself or an extension with the same name.
646  /// - A dependency hasn't been loaded yet.
647  pub fn check_dependencies(&self, previous_exts: &[Extension]) {
648    'dep_loop: for dep in self.deps {
649      if dep == &self.name {
650        panic!("Extension '{}' is either depending on itself or there is another extension with the same name", self.name);
651      }
652
653      for ext in previous_exts {
654        if dep == &ext.name {
655          continue 'dep_loop;
656        }
657      }
658
659      panic!("Extension '{}' is missing dependency '{dep}'", self.name);
660    }
661  }
662
663  /// returns JS source code to be loaded into the isolate (either at snapshotting,
664  /// or at startup).  as a vector of a tuple of the file name, and the source code.
665  pub fn get_js_sources(&self) -> &[ExtensionFileSource] {
666    &self.js_files
667  }
668
669  pub fn get_esm_sources(&self) -> &[ExtensionFileSource] {
670    &self.esm_files
671  }
672
673  pub fn get_lazy_loaded_esm_sources(&self) -> &[ExtensionFileSource] {
674    &self.lazy_loaded_esm_files
675  }
676
677  pub fn get_esm_entry_point(&self) -> Option<&'static str> {
678    self.esm_entry_point
679  }
680
681  pub fn op_count(&self) -> usize {
682    self.ops.len()
683  }
684
685  /// Called at JsRuntime startup to initialize ops in the isolate.
686  pub fn init_ops(&mut self) -> &[OpDecl] {
687    if !self.enabled {
688      for op in self.ops.to_mut() {
689        op.disable();
690      }
691    }
692    self.ops.as_ref()
693  }
694
695  /// Allows setting up the initial op-state of an isolate at startup.
696  pub fn take_state(&mut self, state: &mut OpState) {
697    if let Some(op_fn) = self.op_state_fn.take() {
698      op_fn(state);
699    }
700  }
701
702  /// Middleware should be called before init_ops
703  pub fn take_middleware(&mut self) -> Option<Box<OpMiddlewareFn>> {
704    self.middleware_fn.take()
705  }
706
707  pub fn get_global_template_middleware(
708    &mut self,
709  ) -> Option<GlobalTemplateMiddlewareFn> {
710    self.global_template_middleware
711  }
712
713  pub fn get_global_object_middleware(
714    &mut self,
715  ) -> Option<GlobalObjectMiddlewareFn> {
716    self.global_object_middleware
717  }
718
719  pub fn get_external_references(
720    &mut self,
721  ) -> &[v8::ExternalReference<'static>] {
722    self.external_references.as_ref()
723  }
724
725  pub fn enabled(self, enabled: bool) -> Self {
726    Self { enabled, ..self }
727  }
728
729  pub fn disable(self) -> Self {
730    self.enabled(false)
731  }
732}
733
734/// Helps embed JS files in an extension. Returns a vector of
735/// [`ExtensionFileSource`], that represents the filename and source code.
736///
737/// ```
738/// # use deno_core::include_js_files_doctest as include_js_files;
739/// // Example (for "my_extension"):
740/// let files = include_js_files!(
741///   my_extension
742///   "01_hello.js",
743///   "02_goodbye.js",
744/// );
745///
746/// // Produces following specifiers:
747/// // - "ext:my_extension/01_hello.js"
748/// // - "ext:my_extension/02_goodbye.js"
749/// ```
750///
751/// An optional "dir" option can be specified to prefix all files with a
752/// directory name.
753///
754/// ```
755/// # use deno_core::include_js_files_doctest as include_js_files;
756/// // Example with "dir" option (for "my_extension"):
757/// include_js_files!(
758///   my_extension
759///   dir "js",
760///   "01_hello.js",
761///   "02_goodbye.js",
762/// );
763/// // Produces following specifiers:
764/// // - "ext:my_extension/js/01_hello.js"
765/// // - "ext:my_extension/js/02_goodbye.js"
766/// ```
767///
768/// You may also override the specifiers for each file like so:
769///
770/// ```
771/// # use deno_core::include_js_files_doctest as include_js_files;
772/// // Example with "dir" option (for "my_extension"):
773/// include_js_files!(
774///   my_extension
775///   "module:hello" = "01_hello.js",
776///   "module:goodbye" = "02_goodbye.js",
777/// );
778/// // Produces following specifiers:
779/// // - "module:hello"
780/// // - "module:goodbye"
781/// ```
782#[macro_export]
783macro_rules! include_js_files {
784  // Valid inputs:
785  //  - "file"
786  //  - "file" with_specifier "specifier"
787  //  - "specifier" = "file"
788  //  - "specifier" = { source = "source" }
789  ($name:ident $( dir $dir:literal, )? $(
790    $s1:literal
791    $(with_specifier $s2:literal)?
792    $(= $config:tt)?
793  ),* $(,)?) => {
794    $crate::__extension_include_js_files_detect!(name=$name, dir=$crate::__extension_root_dir!($($dir)?), $([
795      // These entries will be parsed in __extension_include_js_files_inner
796      $s1 $(with_specifier $s2)? $(= $config)?
797    ]),*)
798  };
799}
800
801/// Helps embed JS files in an extension. Returns a vector of
802/// `ExtensionFileSource`, that represent the filename and source code. All
803/// specified files are rewritten into "ext:<extension_name>/<file_name>".
804///
805/// An optional "dir" option can be specified to prefix all files with a
806/// directory name.
807///
808/// See [`include_js_files!`] for details on available options.
809#[macro_export]
810macro_rules! include_lazy_loaded_js_files {
811  ($name:ident $( dir $dir:literal, )? $(
812    $s1:literal
813    $(with_specifier $s2:literal)?
814    $(= $config:tt)?
815  ),* $(,)?) => {
816    $crate::__extension_include_js_files_inner!(mode=included, name=$name, dir=$crate::__extension_root_dir!($($dir)?), $([
817      // These entries will be parsed in __extension_include_js_files_inner
818      $s1 $(with_specifier $s2)? $(= $config)?
819    ]),*)
820  };
821}
822
823/// Used for doctests only. Won't try to load anything from disk.
824#[doc(hidden)]
825#[macro_export]
826macro_rules! include_js_files_doctest {
827  ($name:ident $( dir $dir:literal, )? $(
828    $s1:literal
829    $(with_specifier $s2:literal)?
830    $(= $config:tt)?
831  ),* $(,)?) => {
832    $crate::__extension_include_js_files_inner!(mode=loaded, name=$name, dir=$crate::__extension_root_dir!($($dir)?), $([
833      $s1 $(with_specifier $s2)? $(= $config)?
834    ]),*)
835  };
836}
837
838/// When `#[cfg(not(feature = "include_js_files_for_snapshotting"))]` matches, ie: the `include_js_files_for_snapshotting`
839/// feature is not set, we want all JS files to be included.
840///
841/// Maps `(...)` to `(mode=included, ...)`
842#[cfg(not(feature = "include_js_files_for_snapshotting"))]
843#[doc(hidden)]
844#[macro_export]
845macro_rules! __extension_include_js_files_detect {
846  ($($rest:tt)*) => { $crate::__extension_include_js_files_inner!(mode=included, $($rest)*) };
847}
848
849/// When `#[cfg(feature = "include_js_files_for_snapshotting")]` matches, ie: the `include_js_files_for_snapshotting`
850/// feature is set, we want the pathnames for the JS files to be included and not the file contents.
851///
852/// Maps `(...)` to `(mode=loaded, ...)`
853#[cfg(feature = "include_js_files_for_snapshotting")]
854#[doc(hidden)]
855#[macro_export]
856macro_rules! __extension_include_js_files_detect {
857  ($($rest:tt)*) => { $crate::__extension_include_js_files_inner!(mode=loaded, $($rest)*) };
858}
859
860/// This is the core of the [`include_js_files!`] and [`include_lazy_loaded_js_files`] macros. The first
861/// rule is the entry point that receives a list of unparsed file entries. Each entry is extracted and
862/// then parsed with the `@parse_item` rules.
863#[doc(hidden)]
864#[macro_export]
865macro_rules! __extension_include_js_files_inner {
866  // Entry point: (mode=, name=, dir=, [... files])
867  (mode=$mode:ident, name=$name:ident, dir=$dir:expr, $([
868    $s1:literal
869    $(with_specifier $s2:literal)?
870    $(= $config:tt)?
871  ]),*) => {
872    [
873      $(
874        $crate::__extension_include_js_files_inner!(
875          @parse_item
876          mode=$mode,
877          name=$name,
878          dir=$dir,
879          $s1 $(with_specifier $s2)? $(= $config)?
880        )
881      ),*
882    ]
883  };
884
885  // @parse_item macros will parse a single file entry, and then call @item macros with the destructured data
886
887  // "file" -> Include a file, use the generated specifier
888  (@parse_item mode=$mode:ident, name=$name:ident, dir=$dir:expr, $file:literal) => {
889    $crate::__extension_include_js_files_inner!(@item mode=$mode, dir=$dir, specifier=concat!("ext:", stringify!($name), "/", $file), file=$file)
890  };
891  // "file" with_specifier "specifier" -> Include a file, use the provided specifier
892  (@parse_item mode=$mode:ident, name=$name:ident, dir=$dir:expr, $file:literal with_specifier $specifier:literal) => {
893    {
894      #[deprecated="When including JS files 'file with_specifier specifier' is deprecated: use 'specifier = file' instead"]
895      struct WithSpecifierIsDeprecated {}
896      _ = WithSpecifierIsDeprecated {};
897      $crate::__extension_include_js_files_inner!(@item mode=$mode, dir=$dir, specifier=$specifier, file=$file)
898    }
899  };
900  // "specifier" = "file" -> Include a file, use the provided specifier
901  (@parse_item mode=$mode:ident, name=$name:ident, dir=$dir:expr, $specifier:literal = $file:literal) => {
902    $crate::__extension_include_js_files_inner!(@item mode=$mode, dir=$dir, specifier=$specifier, file=$file)
903  };
904  // "specifier" = { source = "source" } -> Include a file, use the provided specifier
905  (@parse_item mode=$mode:ident, name=$name:ident, dir=$dir:expr, $specifier:literal = { source = $source:literal }) => {
906    $crate::__extension_include_js_files_inner!(@item mode=$mode, specifier=$specifier, source=$source)
907  };
908
909  // @item macros generate the final output
910
911  // loaded, source
912  (@item mode=loaded, specifier=$specifier:expr, source=$source:expr) => {
913    $crate::ExtensionFileSource::loaded_from_memory_during_snapshot($specifier, $crate::ascii_str!($source))
914  };
915  // loaded, file
916  (@item mode=loaded, dir=$dir:expr, specifier=$specifier:expr, file=$file:literal) => {
917    $crate::ExtensionFileSource::loaded_during_snapshot($specifier, concat!($dir, "/", $file))
918  };
919  // included, source
920  (@item mode=included, specifier=$specifier:expr, source=$source:expr) => {
921    $crate::ExtensionFileSource::new($specifier, $crate::ascii_str!($source))
922  };
923  // included, file
924  (@item mode=included, dir=$dir:expr, specifier=$specifier:expr, file=$file:literal) => {
925    $crate::ExtensionFileSource::new($specifier, $crate::ascii_str_include!(concat!($dir, "/", $file)))
926  };
927}
928
929/// Given an optional `$dir`, generates a crate-relative root directory.
930#[doc(hidden)]
931#[macro_export]
932macro_rules! __extension_root_dir {
933  () => {
934    env!("CARGO_MANIFEST_DIR")
935  };
936  ($dir:expr) => {
937    concat!(env!("CARGO_MANIFEST_DIR"), "/", $dir)
938  };
939}
940
941#[cfg(test)]
942mod tests {
943  #[test]
944  fn test_include_js() {
945    let files = include_js_files!(prefix "00_infra.js", "01_core.js",);
946    assert_eq!("ext:prefix/00_infra.js", files[0].specifier);
947    assert_eq!("ext:prefix/01_core.js", files[1].specifier);
948
949    let files = include_js_files!(prefix dir ".", "00_infra.js", "01_core.js",);
950    assert_eq!("ext:prefix/00_infra.js", files[0].specifier);
951    assert_eq!("ext:prefix/01_core.js", files[1].specifier);
952
953    let files = include_js_files!(prefix
954      "a" = { source = "b" }
955    );
956    assert_eq!("a", files[0].specifier);
957  }
958}