Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 112 additions & 41 deletions core/runtime/src/process/mod.rs
Original file line number Diff line number Diff line change
@@ -1,70 +1,123 @@
//! Boa's implementation of Node.js' `process` object.
//!
//! The `process` object can be accessed from any global object.
//!
//! Boa's implementation of Node.js' `process` object.
//!
//! The `process` object can be accessed from any global object.
//!
//! More information:
//! - [Node.js documentation][node]
//! - [Node.js documentation][node]
//!
//! [node]: https://nodejs.org/api/process.html
//! [node]: https://nodejs.org/api/process.html

#[cfg(test)]
pub(crate) mod tests;

use boa_engine::{
Context, JsObject, JsResult, JsString, JsValue, js_string, property::Attribute,
property::PropertyDescriptor,
Context, JsData, JsObject, JsResult, JsString, JsSymbol, JsValue, js_error, js_string,
native_function::NativeFunction, object::ObjectInitializer, property::Attribute,
};
use boa_gc::{Finalize, Trace};
use std::rc::Rc;

/// A trait that can be used to forward process provider to an implementation.
pub trait ProcessProvider: Trace {
/// Get current working directory (`process.cwd()`)
///
/// # Errors
/// Returns an error if the current directory cannot be obtained.
fn cwd(&self) -> JsResult<JsString>;

/// Boa's implementation of Node.js' `process` object.
/// Get environment variables so as to allow env property (`process.env`)
fn env(&self) -> impl IntoIterator<Item = (JsString, JsString)>;
}

/// The default std implementation of the process provider.
///
/// Implements the [`ProcessProvider`] trait. Outputs the process properties'
/// values on the basis of std.
#[derive(Debug, Trace, Finalize)]
pub struct StdProcessProvider;

impl ProcessProvider for StdProcessProvider {
fn cwd(&self) -> JsResult<JsString> {
let path = std::env::current_dir().map_err(
|e| js_error!(TypeError: "failed to get current working directory: {}", e.to_string()),
)?;
Ok(js_string!(path.to_string_lossy()))
}

fn env(&self) -> impl IntoIterator<Item = (JsString, JsString)> {
std::env::vars().map(|(k, v)| (js_string!(k), js_string!(v)))
}
}

/// Boa's implementation of Node.js' `process` object.
#[derive(Debug, Trace, Finalize, JsData)]
pub struct Process;

impl Process {
/// Name of the built-in `process` property.
/// Name of the built-in `process` property.
pub const NAME: JsString = js_string!("process");

/// Initializes the `process` built-in object.
/// Initializes the `process` built-in object with a custom provider.
///
/// # Errors
///
/// Returns a `JsError` if:
/// - Setting environment variables on the `env` object fails
/// - Defining the `env` property on the `process` object fails
pub fn init(context: &mut Context) -> JsResult<JsObject> {
let process = JsObject::default(context.intrinsics());
/// - Custom process provider returns an error
/// - Defining the `cwd` and `env` properties on the `process` object fails
pub fn init_with_provider<P>(context: &mut Context, provider: P) -> JsResult<JsObject>
where
P: ProcessProvider + 'static,
{
fn process_method<P: ProcessProvider + 'static>(
f: fn(&JsValue, &[JsValue], &P, &mut Context) -> JsResult<JsValue>,
provider: Rc<P>,
) -> NativeFunction {
// SAFETY: `Process` doesn't contain types that need tracing.
unsafe {
NativeFunction::from_closure(move |this, args, context| {
f(this, args, &provider, context)
})
}
}

let provider = Rc::new(provider);

// Create env object with current environment variables
let env = JsObject::default(context.intrinsics());
for (key, value) in std::env::vars() {
env.set(
js_string!(key),
JsValue::from(js_string!(value)),
false,
context,
)?;
for (key, value) in provider.env() {
env.set(key, JsValue::from(value), false, context)?;
}

process.define_property_or_throw(
js_string!("env"),
PropertyDescriptor::builder()
.value(env)
.writable(true)
.enumerable(false)
.configurable(true)
.build(),
context,
)?;

Ok(process)
Ok(ObjectInitializer::new(context)
.property(
JsSymbol::to_string_tag(),
Self::NAME,
Attribute::CONFIGURABLE,
)
.property(
js_string!("env"),
env,
Attribute::WRITABLE | Attribute::CONFIGURABLE,
)
.function(
process_method(
|_, _, provider, _| provider.cwd().map(JsValue::from),
provider.clone(),
),
js_string!("cwd"),
0,
)
.build())
}

/// Register the `process` object globally.
///
/// # Errors
/// This function will return an error if the property cannot be defined on the global object.
pub fn register(context: &mut Context) -> JsResult<()> {
let process_object = Self::init(context)?;
/// Register the `process` object globally by a custom provider.
///
/// # Errors
/// This function will return an error if the property cannot be defined on the global object.
pub fn register_with_provider<P>(context: &mut Context, provider: P) -> JsResult<()>
where
P: ProcessProvider + 'static,
{
let process_object = Self::init_with_provider(context, provider)?;

context.register_global_property(
js_string!(Self::NAME),
Expand All @@ -74,4 +127,22 @@ impl Process {

Ok(())
}

/// Initializes the `process` built-in object with the default std provider.
///
/// # Errors
///
/// Returns a `JsError` if:
/// - Defining the `cwd` and `env` properties on the `process` object fails
pub fn init(context: &mut Context) -> JsResult<JsObject> {
Self::init_with_provider(context, StdProcessProvider)
}

/// Register the `process` object globally by the default std provider.
///
/// # Errors
/// This function will return an error if the property cannot be defined on the global object.
pub fn register(context: &mut Context) -> JsResult<()> {
Self::register_with_provider(context, StdProcessProvider)
}
}
26 changes: 26 additions & 0 deletions core/runtime/src/process/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,32 @@ fn process_property_descriptors() {
);
}

#[test]
fn process_cwd_method() {
let mut context = Context::default();
crate::process::Process::register(&mut context).unwrap();

run_test_actions_with(
[
TestAction::run(TEST_HARNESS),
TestAction::run(indoc! {r#"
assert_own_property(process, "cwd");
assert_equals(typeof process.cwd, "function");

const cwd = process.cwd();
assert_equals(typeof cwd, "string");
assert_true(cwd.length > 0, "cwd should not be empty");

const cwdDesc = Object.getOwnPropertyDescriptor(process, "cwd");
assert_equals(cwdDesc.writable, true, "cwd must be writable");
assert_equals(cwdDesc.enumerable, false, "cwd must not be enumerable");
assert_equals(cwdDesc.configurable, true, "cwd must be configurable");
"#}),
],
&mut context,
);
}

#[test]
#[ignore = "Unsafe under parallel test execution as it tempers with env."]
fn process_env_contains_variables() {
Expand Down
Loading