Giter Site home page Giter Site logo

redfire75369 / spiderfire Goto Github PK

View Code? Open in Web Editor NEW
370.0 10.0 21.0 1.68 MB

JavaScript Runtime built with Mozilla's SpiderMonkey Engine

License: Mozilla Public License 2.0

Batchfile 0.02% Rust 98.77% JavaScript 1.10% Just 0.10% TypeScript 0.02%
spiderfire javascript js runtime javascript-runtime mozilla spidermonkey

spiderfire's Introduction

Discord Server License: MPL 2.0

Spiderfire

Spiderfire is a Javascript runtime built with Mozilla's SpiderMonkey engine.

Spiderfire aims to disrupt the server-side Javascript runtime environment.

Licensing

This project is licensed under the Mozilla Public License 2.0. The full text of the license can be found here.

Build

For instructions on building Spiderfire, refer here.

Quick Start

For a quick guide to get started, refer here.

spiderfire's People

Contributors

arshia001 avatar gers2017 avatar lino-levan avatar redfire75369 avatar sagudev avatar vaimer9 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

spiderfire's Issues

get_private and get_mut_private don't work with subclasses

Having this:

#[js_class]
struct Base {
  reflector: Reflector
}

#[js_class]
struct Derived {
  base: Base
}

#[js_fn]
fn foo(bar: Base) { ... }

this JS code won't work:

foo(new Derived())

Because the check in get_private/get_mut_private is eventually using JS_InstanceOf which only checks for the exact class provided. The fix is to have this in ClassDefinition:

	fn instance_or_subtype_of(cx: &Context, object: &Object) -> Result<bool> {
		let constructor: Function = cx.root(Self::class_info(cx).constructor).into();
		let constructor_obj = constructor.to_object(cx);
		let mut result = false;
		unsafe {
			if !JS_HasInstance(
				cx.as_ptr(),
				constructor_obj.handle().into(),
				Value::object(cx, object).handle().into(),
				&mut result as *mut bool,
			) {
				return Err(Error::none());
			}
		}
		Ok(result)
	}

and change the code in get_(mut)_private to:

	fn get_private<'a>(cx: &Context, object: &Object<'a>) -> Result<&'a Self> {
		if Self::instance_of(cx, object) // Fast path, most native objects don't have base classes and this check is way faster
		|| Self::instance_or_subtype_of(cx, object)?
		{
			Ok(unsafe { Self::get_private_unchecked(object) })
		} else {
			Err(private_error(Self::class()))
		}
	}

Simply casting the pointer of a derived native type (Derived in the example) to a less-derived native type (Base in the example) works because the base is always the first member and there's #[repr(C)] on js_classes which I assume was put in to support this scenario.

Multiple Tests in One File

Issue

Currently, there is a limitation to the usage of tests as only 1 test per file can be created to test actions within the runtime. This is due to the following. Hence, only 1 runtime can be initialised per thread. Due to how cargo tests are run serially(with --test-threads=1) and in parallel, this means only 1 runtime can be initialised per executable, which requires 1 file per test.

It is currently not possible to initialize SpiderMonkey multiple times (that is, calling JS_Init/JSAPI methods/JS_ShutDown in that order, then doing so again). This restriction may eventually be lifted.

Finding a solution to this will improve flexibility for future tests with more complex modules, and JSAPI wrappers.
Currently, I have a draft of tests for the fs module, but I am not satisfied with them, as putting them all in a singular function partially defeats the purpose of such tests, as they are not targeted. With this improvement, each portion of the fs module can then be tested independently, and properly utilise cargo's test system, with many tests and test cases.

Requirements

  • Advanced Understanding of Rust, static values and cargo tests
  • Intermediate Understanding of JavaScript
  • Understanding of Spidermonkey Runtime and Engine initialisation

Possible Solution

thread_local or Mutex could be used to store the ParentRuntime and Runtime::create_with_parent could be used to create the appropriate runtimes.

Panic when printing a prototype

From the latest commit on master:

> new File(['a'], 'b').__proto__
{
thread 'main' panicked at /Users/arshia/.cargo/git/checkouts/mozjs-fa11ffc7d4f1cc2d/0a2efb9/mozjs-sys/src/jsval.rs:503:9:
assertion failed: self.is_double()
  "name": undefined,
thread 'main' panicked at /Users/arshia/.cargo/git/checkouts/mozjs-fa11ffc7d4f1cc2d/0a2efb9/mozjs-sys/src/jsval.rs:503:9:
assertion failed: self.is_double()
  "lastModified": undefined,
}

It looks like the pretty-printing code tries to access the properties of the prototype object like a normal constructed object, which causes a panic.

Dropping a context segfaults when native async functions have run

As per the title, when a context has had native async functions running on it, dropping the context sometimes causes a segfault. This behavior can be observed in the CLI's REPL mode:

> let p = http.get("https://www.google.com").then(p => p.text())
undefined
> p
Promise {
  <fulfilled> ""
}
>                     # pressed Ctrl+D to exit here
zsh: segmentation fault  cargo run

I previously traced to issue to the general area of rooted traceables, but have since changed my code a bit and it's not running now. I'll add more context when I have a repro running locally.

I'm running the latest code in the main branch, on an Apple Silicon mac. I don't know if this issue is specific to macOS.


EDIT: Here's a stack trace:

mozjs_sys::jsgc::Heap<T>::get (/Users/arshia/.cargo/git/checkouts/mozjs-e1c76167ba7c548f/b7207a8/mozjs-sys/src/jsgc.rs:235)
<mozjs_sys::jsgc::Heap<*mut mozjs_sys::generated::root::JSObject> as mozjs::gc::trace::Traceable>::trace (/Users/arshia/.cargo/git/checkouts/mozjs-e1c76167ba7c548f/b7207a8/mozjs/src/gc/trace.rs:57)
<alloc::boxed::Box<T> as mozjs::gc::trace::Traceable>::trace (/Users/arshia/.cargo/git/checkouts/mozjs-e1c76167ba7c548f/b7207a8/mozjs/src/gc/trace.rs:133)
mozjs::gc::trace::RootedTraceableSet::trace (/Users/arshia/.cargo/git/checkouts/mozjs-e1c76167ba7c548f/b7207a8/mozjs/src/gc/trace.rs:440)
mozjs::gc::trace::trace_traceables::{{closure}} (/Users/arshia/.cargo/git/checkouts/mozjs-e1c76167ba7c548f/b7207a8/mozjs/src/gc/trace.rs:447)
std::thread::local::LocalKey<T>::try_with (@std::thread::local::LocalKey<T>::try_with:78)
std::thread::local::LocalKey<T>::with (@std::thread::local::LocalKey<T>::with:11)
mozjs::gc::trace::trace_traceables (/Users/arshia/.cargo/git/checkouts/mozjs-e1c76167ba7c548f/b7207a8/mozjs/src/gc/trace.rs:446)
js::gc::GCRuntime::traceEmbeddingBlackRoots(JSTracer*) (/Users/arshia/.cargo/git/checkouts/mozjs-e1c76167ba7c548f/b7207a8/mozjs-sys/mozjs/js/src/gc/RootMarking.cpp:384)
js::gc::GCRuntime::finishRoots() (/Users/arshia/.cargo/git/checkouts/mozjs-e1c76167ba7c548f/b7207a8/mozjs-sys/mozjs/js/src/gc/RootMarking.cpp:446)
JSRuntime::destroyRuntime() (/Users/arshia/.cargo/git/checkouts/mozjs-e1c76167ba7c548f/b7207a8/mozjs-sys/mozjs/js/src/vm/Runtime.cpp:260)
js::DestroyContext(JSContext*) (/Users/arshia/.cargo/git/checkouts/mozjs-e1c76167ba7c548f/b7207a8/mozjs-sys/mozjs/js/src/vm/JSContext.cpp:225)
<mozjs::rust::Runtime as core::ops::drop::Drop>::drop (/Users/arshia/.cargo/git/checkouts/mozjs-e1c76167ba7c548f/b7207a8/mozjs/src/rust.rs:430)
core::ptr::drop_in_place<mozjs::rust::Runtime> (@core::ptr::drop_in_place<mozjs::rust::Runtime>:9)
cli::commands::repl::start_repl::{{closure}} (/Users/arshia/repos/spiderfire/cli/src/commands/repl.rs:65)
cli::commands::handle_command::{{closure}} (/Users/arshia/repos/spiderfire/cli/src/commands/mod.rs:54)
<tokio::task::local::RunUntil<T> as core::future::future::Future>::poll::{{closure}} (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.32.0/src/task/local.rs:949)
tokio::task::local::LocalSet::with::{{closure}} (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.32.0/src/task/local.rs:686)
std::thread::local::LocalKey<T>::try_with (@std::thread::local::LocalKey<T>::try_with:82)
std::thread::local::LocalKey<T>::with (@std::thread::local::LocalKey<T>::with:9)
tokio::task::local::LocalSet::with (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.32.0/src/task/local.rs:669)
<tokio::task::local::RunUntil<T> as core::future::future::Future>::poll (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.32.0/src/task/local.rs:939)
tokio::task::local::LocalSet::run_until::{{closure}} (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.32.0/src/task/local.rs:575)
cli::main::{{closure}} (/Users/arshia/repos/spiderfire/cli/src/main.rs:66)
<core::pin::Pin<P> as core::future::future::Future>::poll (@<core::pin::Pin<P> as core::future::future::Future>::poll:18)
tokio::runtime::scheduler::current_thread::CoreGuard::block_on::{{closure}}::{{closure}}::{{closure}} (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.32.0/src/runtime/scheduler/current_thread/mod.rs:665)
tokio::runtime::coop::with_budget (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.32.0/src/runtime/coop.rs:107)
tokio::runtime::coop::budget (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.32.0/src/runtime/coop.rs:73)
tokio::runtime::scheduler::current_thread::CoreGuard::block_on::{{closure}}::{{closure}} (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.32.0/src/runtime/scheduler/current_thread/mod.rs:665)
tokio::runtime::scheduler::current_thread::Context::enter (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.32.0/src/runtime/scheduler/current_thread/mod.rs:410)
tokio::runtime::scheduler::current_thread::CoreGuard::block_on::{{closure}} (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.32.0/src/runtime/scheduler/current_thread/mod.rs:664)
tokio::runtime::scheduler::current_thread::CoreGuard::enter::{{closure}} (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.32.0/src/runtime/scheduler/current_thread/mod.rs:743)
tokio::runtime::context::scoped::Scoped<T>::set (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.32.0/src/runtime/context/scoped.rs:40)
tokio::runtime::context::set_scheduler::{{closure}} (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.32.0/src/runtime/context.rs:176)
std::thread::local::LocalKey<T>::try_with (@std::thread::local::LocalKey<T>::try_with:86)
std::thread::local::LocalKey<T>::with (@std::thread::local::LocalKey<T>::with:9)
tokio::runtime::context::set_scheduler (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.32.0/src/runtime/context.rs:176)
tokio::runtime::scheduler::current_thread::CoreGuard::enter (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.32.0/src/runtime/scheduler/current_thread/mod.rs:743)
tokio::runtime::scheduler::current_thread::CoreGuard::block_on (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.32.0/src/runtime/scheduler/current_thread/mod.rs:652)
tokio::runtime::scheduler::current_thread::CurrentThread::block_on::{{closure}} (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.32.0/src/runtime/scheduler/current_thread/mod.rs:175)

Idea: Easier writing of native functions for SM

Now you are writing all functions naked for SpiderMonkey. Even Servo does not do that. It uses procedural macro (and WebIDL). Well WebIDL is probably overkill but proc macros...

This is how it could look like:

#[jsfn]
pub fn print(s: String) {
      println!("rust said: {}", s)
}

and it would expend to:

unsafe extern "C" fn print(context: *mut JSContext, argc: u32, vp: *mut Value) -> bool {
    let args = Arguments::new(argc, vp);
    args.rval().set(UndefinedValue());

    if args.len != 1 {
        JS_ReportErrorASCII(context, b"print() requires exactly 1 argument\0".as_ptr() as *const libc::c_char);
        return false;
    }

    println!("{}", to_string(cx, args.value(0).unwrap()));

    return true;
}

It could also be applied for methods (like the one you uses in fs.rs). Something like this is also used in https://github.com/rjs-team/rjs look at js_fn! macro.

Add Typescript Support

I know, I know this is a deno thing but I really think it would improve the project by attracting all the typescript folks.
Personally is something node.js needs and I'd use it without doubt. I'm not suggesting get rid of javascript, not at all, what I'm suggesting is let the users use typescript or javascript without too much configuration or boilerplate.
I'm still learning rust but I'd like to work/collaborate on this feature.

Getting this function takes 3 arguments but 2 arguments were supplied From ModuleEvaluate function

here is the logs
```
Fresh cc v1.0.67
Fresh version_check v0.9.3
Fresh glob v0.3.0
Fresh unicode-xid v0.2.1
Fresh autocfg v1.0.1
Fresh pkg-config v0.3.19
Fresh regex-syntax v0.6.23
Fresh rustc-hash v1.1.0
Fresh shlex v0.1.1
Fresh lazy_static v1.4.0
Fresh cfg-if v1.0.0
Fresh same-file v1.0.6
Fresh lazycell v1.3.0
Fresh cfg-if v0.1.10
Fresh peeking_take_while v0.1.2
Fresh unicode-width v0.1.8
Fresh ansi_term v0.11.0
Fresh strsim v0.8.0
Fresh vec_map v0.8.2
Fresh once_cell v1.7.2
Fresh termcolor v1.1.2
Fresh regex v1.4.5
Fresh walkdir v2.3.1
Fresh textwrap v0.11.0
Fresh libc v0.2.89
Fresh memchr v2.3.4
Fresh proc-macro2 v1.0.24
Fresh bitflags v1.2.1
Fresh encoding_rs v0.8.28
Fresh which v3.1.1
Fresh log v0.4.14
Fresh atty v0.2.14
Fresh time v0.1.44
Fresh nom v5.1.2
Fresh libloading v0.5.2
Fresh quote v1.0.9
Fresh num-traits v0.2.14
Fresh libz-sys v1.1.2
Fresh encoding_c v0.9.8
Fresh encoding_c_mem v0.2.6
Fresh clap v2.33.3
Fresh cexpr v0.4.0
Fresh clang-sys v0.29.3
Fresh num-integer v0.1.44
Fresh bindgen v0.53.3
Fresh chrono v0.4.19
Fresh mozjs_sys v0.68.2 (https://github.com/servo/mozjs?rev=82da136c53d7ca7b0b7fe1c5622627036ef31899#82da136c)
Fresh mozjs v0.14.1 (https://github.com/servo/rust-mozjs#fe104a26)
Compiling spiderfire v0.1.0 (/home/wolfpat/spiderfire)
Running rustc --crate-name spiderfire --edition=2018 src/main.rs --error-format=json --json=diagnostic-rendered-ansi --crate-type bin --emit=dep-info,link -Cembed-bitcode=no -C debuginfo=2 -C metadata=f681912fcf3e37c1 -C extra-filename=-f681912fcf3e37c1 --out-dir /home/wolfpat/spiderfire/target/debug/deps -C incremental=/home/wolfpat/spiderfire/target/debug/incremental -L dependency=/home/wolfpat/spiderfire/target/debug/deps --extern chrono=/home/wolfpat/spiderfire/target/debug/deps/libchrono-33f7dbbd138754ae.rlib --extern clap=/home/wolfpat/spiderfire/target/debug/deps/libclap-02d3490164b7a98c.rlib --extern libc=/home/wolfpat/spiderfire/target/debug/deps/liblibc-92a6ce5a991a67c9.rlib --extern mozjs=/home/wolfpat/spiderfire/target/debug/deps/libmozjs-b5ab7020a62ab304.rlib --extern once_cell=/home/wolfpat/spiderfire/target/debug/deps/libonce_cell-e9da1314ab8fbd82.rlib --extern termcolor=/home/wolfpat/spiderfire/target/debug/deps/libtermcolor-e268ff7bc76f4625.rlib -L native=/home/wolfpat/spiderfire/target/debug/build/mozjs-3ca1e6668fc070d0/out -L native=/home/wolfpat/spiderfire/target/debug/build/mozjs_sys-36dbba0e74ff8ac2/out/build/js/src/build -L native=/home/wolfpat/spiderfire/target/debug/build/mozjs_sys-36dbba0e74ff8ac2/out/build/glue
error[E0061]: this function takes 3 arguments but 2 arguments were supplied
--> src/runtime/jsapi_utils/eval.rs:103:8
|
103 | if !ModuleEvaluate(rt.cx(), module.handle().into()) {
| ^^^^^^^^^^^^^^ ------- ---------------------- supplied 2 arguments
| |
| expected 3 arguments

error: aborting due to previous error

For more information about this error, try rustc --explain E0061.
error: could not compile spiderfire.

Caused by:
process didn't exit successfully: rustc --crate-name spiderfire --edition=2018 src/main.rs --error-format=json --json=diagnostic-rendered-ansi --crate-type bin --emit=dep-info,link -Cembed-bitcode=no -C debuginfo=2 -C metadata=f681912fcf3e37c1 -C extra-filename=-f681912fcf3e37c1 --out-dir /home/wolfpat/spiderfire/target/debug/deps -C incremental=/home/wolfpat/spiderfire/target/debug/incremental -L dependency=/home/wolfpat/spiderfire/target/debug/deps --extern chrono=/home/wolfpat/spiderfire/target/debug/deps/libchrono-33f7dbbd138754ae.rlib --extern clap=/home/wolfpat/spiderfire/target/debug/deps/libclap-02d3490164b7a98c.rlib --extern libc=/home/wolfpat/spiderfire/target/debug/deps/liblibc-92a6ce5a991a67c9.rlib --extern mozjs=/home/wolfpat/spiderfire/target/debug/deps/libmozjs-b5ab7020a62ab304.rlib --extern once_cell=/home/wolfpat/spiderfire/target/debug/deps/libonce_cell-e9da1314ab8fbd82.rlib --extern termcolor=/home/wolfpat/spiderfire/target/debug/deps/libtermcolor-e268ff7bc76f4625.rlib -L native=/home/wolfpat/spiderfire/target/debug/build/mozjs-3ca1e6668fc070d0/out -L native=/home/wolfpat/spiderfire/target/debug/build/mozjs_sys-36dbba0e74ff8ac2/out/build/js/src/build -L native=/home/wolfpat/spiderfire/target/debug/build/mozjs_sys-36dbba0e74ff8ac2/out/build/glue (exit code: 1)


Macro parsing succeeds on invalid input

I used to have this code somewhere:

#[js_fn]
fn something(#[ion::this] this: Object) -> ...

and it was parsing OK, which it shouldn't have. Since the macros on the properties are being parsed out by ion-proc, I think it'd be better to fail with an error on such invalid syntax instead of ignoring it.

TextDecoder and TextEncoder APIs

Spiderfire currently lacks a way to convert bytes into strings correctly and vice versa. In order to combat this, the TextDecoder and TextEncoder APIs should be implemented.

This will require use of #[js_class] for the primary APIs, and use of encoding_rs to support the many different encodings supported by the standard.

Implementation of TextDecoderStream and TextEncoderStream is not needed for the time being as #8 is required for that first.

Resources

Using clearInterval from inside the setInterval callback does not clear the interval

Since timers are removed from the queue and added back in, if an interval is cleared from within the callback, it doesn't get cleared at all.

Failing JS code:

  let handle = setInterval(() => {
    clearInterval(handle);
    console.log(1);
  }, 300);

Relevant rust code:

let macrotask = { self.map.remove_entry(&next) };
if let Some((id, macrotask)) = macrotask {
let macrotask = macrotask.run(cx)?;
if let Some(Macrotask::Timer(mut timer)) = macrotask {
if timer.reset() {
self.map.insert(id, Macrotask::Timer(timer));
}
}
}

I'd fix this, but I'm not sure what approach would be best here. I can think of two:

  • refactor Macrotask::run to take self by reference, and remove afterwards, and
  • if a clearInterval call happens from inside the callback, install a tombstone value in the map, and clear that after the callback returns instead of adding the timer back in.

Both approaches would work, but could get complicated, so I'll leave it to you @Redfire75369.

Strings with invalid UTF-16 codepoints cause SpiderFire to crash

To reproduce, simply type '\uD800' into the REPL prompt:

> '\uD800'
thread 'main' panicked at ion/src/string/mod.rs:186:50:
called `Result::unwrap()` on an `Err` value: Utf16Error { valid_up_to: 0, error_len: None }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

HTTP Networking Module

Issue

Spiderfire currently lacks a networking module, making it impossible to use this as a serverside runtime, and serve webpages for example. It is also unable to fetch data from web APIs. Implementing a networking module will be essential for spiderfire's usage.

Possible Solution

hyper or reqwest can be used to provide HTTP networking APIs in a module.

Add Readable and Writeable Stream APIs

Come on, man. I'd love to just, create a stream, pipe that steam into streams, pipe streams into files, pipe streams into network ports, pipe streams into pipes, stream pips into files, pipe....

oh also, how about you make write return a promise for when the internal buffers fill up? That'd be neat, in fact, that's one thing I hate about NodeJS's streams, for some reason they don't mix with async functions well and you gotta make/use a wrapper with it and that makes me sad.

I'm sure with enough effort, spiderfire will be the key to ending all my sadness. Wouldn't that be cool?

Oh, and files and networks stuffs shouldn't be the only thing that's streams, stdin/stdout/stderr should also have streams! Streams everywhere! Just like UNIX intended.

Error when Values are concurrently rooted

I know this is a known issue:

	/// Returns the result of the [Promise].
	///
	/// ### Note
	/// Currently leads to a sefault.
	pub fn result<'cx>(&self, cx: &'cx Context) -> Value<'cx> {
		let mut value = Value::undefined(cx);
		unsafe { JS_GetPromiseResult(self.handle().into(), value.handle_mut().into()) }
		value
	}

However, I'm wondering why this happens and what has to happen to fix it? Our usecase depends heavily on promises and we've been running into the segfault.

Here's a stack trace just in case it proves useful:

thread '<unnamed>' panicked at /Users/arshia/.cargo/git/checkouts/mozjs-e1c76167ba7c548f/5caf17f/mozjs-sys/src/jsimpls.rs:420:9:
assertion failed: *self.stack == self as *mut _ as usize as _
stack backtrace:
   0: rust_begin_unwind
             at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/std/src/panicking.rs:597:5
   1: core::panicking::panic_fmt
             at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/core/src/panicking.rs:72:14
   2: core::panicking::panic
             at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/core/src/panicking.rs:127:5
   3: mozjs_sys::jsimpls::<impl mozjs_sys::jsgc::Rooted<T>>::remove_from_root_stack
             at /Users/arshia/.cargo/git/checkouts/mozjs-e1c76167ba7c548f/5caf17f/mozjs-sys/src/jsimpls.rs:420:9
   4: <ion::context::Context as core::ops::drop::Drop>::drop
             at /Users/arshia/repos/wasmer/spiderfire/ion/src/context.rs:222:3
   5: core::ptr::drop_in_place<ion::context::Context>
             at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/core/src/ptr/mod.rs:497:1
   6: wasmer_winter::ion_runner::performance::test_futures::wrapper::inner::{{closure}}
             at ./src/ion_runner/performance.rs:34:5
   7: runtime::promise::future_to_promise::{{closure}}
             at /Users/arshia/repos/wasmer/spiderfire/runtime/src/promise.rs:27:69
   8: tokio::runtime::task::core::CoreStage<T>::poll::{{closure}}
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/runtime/task/core.rs:184:17
   9: tokio::loom::std::unsafe_cell::UnsafeCell<T>::with_mut
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/loom/std/unsafe_cell.rs:14:9
  10: tokio::runtime::task::core::CoreStage<T>::poll
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/runtime/task/core.rs:174:13
  11: tokio::runtime::task::harness::poll_future::{{closure}}
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/runtime/task/harness.rs:480:19
  12: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once
             at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/core/src/panic/unwind_safe.rs:271:9
  13: std::panicking::try::do_call
             at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/std/src/panicking.rs:504:40
  14: ___rust_try
  15: std::panicking::try
             at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/std/src/panicking.rs:468:19
  16: std::panic::catch_unwind
             at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/std/src/panic.rs:142:14
  17: tokio::runtime::task::harness::poll_future
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/runtime/task/harness.rs:468:18
  18: tokio::runtime::task::harness::Harness<T,S>::poll_inner
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/runtime/task/harness.rs:104:27
  19: tokio::runtime::task::harness::Harness<T,S>::poll
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/runtime/task/harness.rs:57:15
  20: tokio::runtime::task::raw::poll
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/runtime/task/raw.rs:194:5
  21: tokio::runtime::task::raw::RawTask::poll
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/runtime/task/raw.rs:134:18
  22: tokio::runtime::task::LocalNotified<S>::run
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/runtime/task/mod.rs:385:9
  23: tokio::task::local::LocalSet::tick::{{closure}}
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/task/local.rs:578:54
  24: tokio::coop::with_budget::{{closure}}
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/coop.rs:102:9
  25: std::thread::local::LocalKey<T>::try_with
             at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/std/src/thread/local.rs:270:16
  26: std::thread::local::LocalKey<T>::with
             at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/std/src/thread/local.rs:246:9
  27: tokio::coop::with_budget
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/coop.rs:95:5
  28: tokio::coop::budget
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/coop.rs:72:5
  29: tokio::task::local::LocalSet::tick
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/task/local.rs:578:31
  30: <tokio::task::local::RunUntil<T> as core::future::future::Future>::poll::{{closure}}
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/task/local.rs:814:16
  31: tokio::task::local::LocalSet::with::{{closure}}
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/task/local.rs:633:13
  32: std::thread::local::LocalKey<T>::try_with
             at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/std/src/thread/local.rs:270:16
  33: std::thread::local::LocalKey<T>::with
             at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/std/src/thread/local.rs:246:9
  34: tokio::task::local::LocalSet::with
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/task/local.rs:616:9
  35: <tokio::task::local::RunUntil<T> as core::future::future::Future>::poll
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/task/local.rs:800:9
  36: tokio::task::local::LocalSet::run_until::{{closure}}
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/task/local.rs:536:19
  37: wasmer_winter::ion_runner::IonRunner::spawn_thread::{{closure}}::{{closure}}
             at ./src/ion_runner/mod.rs:343:73
  38: <core::pin::Pin<P> as core::future::future::Future>::poll
             at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/core/src/future/future.rs:125:9
  39: tokio::runtime::basic_scheduler::CoreGuard::block_on::{{closure}}::{{closure}}::{{closure}}
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/runtime/basic_scheduler.rs:543:48
  40: tokio::coop::with_budget::{{closure}}
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/coop.rs:102:9
  41: std::thread::local::LocalKey<T>::try_with
             at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/std/src/thread/local.rs:270:16
  42: std::thread::local::LocalKey<T>::with
             at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/std/src/thread/local.rs:246:9
  43: tokio::coop::with_budget
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/coop.rs:95:5
  44: tokio::coop::budget
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/coop.rs:72:5
  45: tokio::runtime::basic_scheduler::CoreGuard::block_on::{{closure}}::{{closure}}
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/runtime/basic_scheduler.rs:543:25
  46: tokio::runtime::basic_scheduler::Context::enter
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/runtime/basic_scheduler.rs:367:19
  47: tokio::runtime::basic_scheduler::CoreGuard::block_on::{{closure}}
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/runtime/basic_scheduler.rs:542:36
  48: tokio::runtime::basic_scheduler::CoreGuard::enter::{{closure}}
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/runtime/basic_scheduler.rs:613:57
  49: tokio::macros::scoped_tls::ScopedKey<T>::set
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/macros/scoped_tls.rs:61:9
  50: tokio::runtime::basic_scheduler::CoreGuard::enter
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/runtime/basic_scheduler.rs:613:27
  51: tokio::runtime::basic_scheduler::CoreGuard::block_on
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/runtime/basic_scheduler.rs:533:19
  52: tokio::runtime::basic_scheduler::BasicScheduler::block_on
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/runtime/basic_scheduler.rs:179:24
  53: tokio::runtime::Runtime::block_on
             at /Users/arshia/.cargo/git/checkouts/tokio-2eb71367495b4da5/2be2588/tokio/src/runtime/mod.rs:486:46
  54: wasmer_winter::ion_runner::IonRunner::spawn_thread::{{closure}}
             at ./src/ion_runner/mod.rs:337:13
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

and here's the code in test_futures:

#[js_fn]
fn test_futures<'cx>(cx: &'cx Context, promise: ion::Promise) -> Option<ion::Promise<'cx>> {
    let cx2 = unsafe { Context::new_unchecked(cx.as_ptr()) };
    let p = cx2
        .root_persistent_object(promise.get())
        .handle()
        .into_handle();

    future_to_promise::<_, _, ion::Exception>(cx, async move {
        let promise = unsafe { ion::Promise::from_unchecked(Local::from_raw_handle(p)) };

        println!("Waiting for promise");

        let v = promise.to_future::<String>(&cx2, false, ()).await?;

        println!("The wait is over, result: {v}");

        cx2.unroot_persistent_object(promise.get());

        Ok(v)
    })
}

where Promise::to_future is something I'm currently working on.

Documentation for Internal and External API

Lack of Documentation

Spiderfire is currently lacking documentation for most of its API. It would be nice to have the structs and functions documented so that contributors can more easily understand the codebase, and users can begin creation of native modules.

Requirements

This task requires basic understanding of Rust, to understand the codebase, and intermediate understanding of Javascript, to understand the interactions with SpiderMonkey.
Difficulty: Easy to Intermediate

Workspace Crates

  • Ion Procedural Macros ('ion-proc`): Contains procedural macros.
    • Wrappers around regular macros
  • Ion (ion): Contains wrappers around the native JSAPI.
    • Wrappers around native types. (IonObject, IonArray, IonDate, IonFunction)
    • Conversion and printing of native types.
    • Handling runtime exceptions.
  • Runtime ('runtime'): Contains functions related to runtime initialisation and operation.
    • Module loader.
    • Console global.
    • Logging level.
  • Modules ('modules'): Contains standard modules.
    • Assertion (assert)
    • File System (fs)
  • CLI (cli): Contains the application code for the command-line interface.
    • Evaluation (eval)
    • REPL (repl)
    • Running modules/scripts (run)

JavaScript Promise & Rust Future Interoperability

Issue

Although promises have been implemented, with the addition of the event loop, there is still an issue as native asynchronicity cannot be used. Every promise that is created requires its own native function to be made.

Requirements

  • Advanced Understanding of Rust and JavaScript Asynchronous Runtimes
  • Understanding of codebase to utilise and enhance APIs

Possible Solution

An API will need to be created that allows Rust Futures to be converted to JavaScript Promises. One possible plan of action is similar to wasm-bindgen, where the future is spawned in a seperate thread, and awaited.

However, this would require a more flexible API for creating promises. Promises currently each require their own native function. Each promise converted from a future would need to be created dynamically. The most effective solution for this would be to allow callbacks for the creation of Promises, but I am unsure of how this could be done.

Improve Console Implementation

The console implementation is currently quite bare bones, and does not follow the specification.

The specification specifies log levels for each of the functions on console. These levels should be honoured except for console.debug which will have LogLevel::Debug.

In addition, a formatter should be implemented to format strings according to the %s, %d, %i, %f, %o, %O specifiers. Do note that %% stands for % in the final string. All arguments passed to the various logging functions that take variable arguments (varargs) should pass them to the formatter function to obtain the string. If possible, the %c specifier can also be implemented, but extra work will be required.

The formatter should also be exposed from js, either as console.format or in some other form.

Requirements: Basic in JavaScript, Basic in Rust
Difficulty: Basic

Resources

[RFC] Macro for Native Classes

Extension of #4 for Classes

This is a low priority task, as procedural macros will most likely be required, and other aspects such as more standard modules and the event loop are more important to complete currently.

Proposed Syntax

#[js_class]
mod Name {
    struct Fields {
        str: String,
        num: u64
    }
    
    #[constructor]
    fn constructor() -> IonResult<Name> {
        Fields {
            str: "",
            num: 0
        }
    }
    
    fn method(#[this] this: IonObject, num: f64) -> IonResult<()> {
        println!("{}", this.str);
        this.set("num", num);
    }
    
    #[get]
    fn get(#[this] this: IonObject) -> IonResult<f64> {
        Ok(this.get("num"))
    }
    
    #[set]
    fn set(#[this] this: IonObject, String str) -> IonResult<()> {
        this.set("str", str);
        Ok(())
    }
}

Issue with Build on various platforms

Issue

Description

Compilation of spiderfire fails inconsistently on GH Actions during build workflow and when cloning from source.

Examples

GitHub Actions

MacOS build completes successfully
Windows and Ubuntu do not successfully build.

Clone & Build (Linux):

Experience similar issue to the issue in the Ubuntu CI build.

Temporary Solution:

Run cargo update to update Cargo.lock's version of mozjs and mozjs_sys

Notes

Refer to issue on rust-mozjs repository for updates

Race condition in the event loop?

This is an odd one, so I'll try my best to explain what I know.

When returning resolved promises from native code, sometimes the promise never actually resolves on the JS side. This happens right now in my implementation of TransformStream, here (but I've seen it happen elsewhere as well, unless I'm mistaken and the issue was something else there): https://github.com/wasmerio/spiderfire/blob/c87d647fcd27da7a962a0310f6d8ee5dc62cdbd4/runtime/src/globals/streams/transform_stream.rs#L246

Basically, on line 274, I'm constructing a resolved promise via this little convenience method:

pub fn new_resolved<'cx>(cx: &'cx Context, value: impl IntoValue<'cx>) -> Promise {
	let mut val = Value::undefined(cx);
	Box::new(value).into_value(cx, &mut val);

	Promise {
		promise: TracedHeap::from_local(&cx.root_object(unsafe { CallOriginalPromiseResolve(cx.as_ptr(), val.handle().into()) })),
	}
}

When I inspect the promise state on the native side, it is reported as being fulfilled. However, when returning this to the JS side, it never actually resolves (as in, the await xxx line never finished running). I tried adding reactions to the promise, and those aren't being run either. Please note I've tried using Promise::new and Promise::resolve instead of my new_resolved method and the result was the same.

Now, here's the interesting part: when I run the same code with a debugger, everything suddenly starts to work. It's the same if I add an await sleep(1000) between the construction of the promise and the await line. Both of these make me think this may be a race condition (although I don't know how, since the code is single-threaded) or otherwise has something to do with execution order or time. Unfortunately, I'm not very familiar with the event loop, so I haven't been able to investigate that area further.

Ctrl + C in repl mode results in Segmentation fault

From Ubuntu, zsh, have the current pre-release binary,
When i press Ctrl + C 2 times in repl mode i get the following:

> ^C
Press Ctrl+C again to exit.
^Cmozilla::detail::MutexImpl::~MutexImpl: pthread_mutex_destroy failed: Device or resource busy
[2]    12204 segmentation fault (core dumped)  /home/v9/Downloads/spidey/spiderfire repl

Adding Docker build step to Spiderfire.

Building docker images is an important part of deploying server-side applications. It would benefit this project if a docker build is maintained.

Why?

Using a spiderfire base image would make it easier for developers to deploy their applications. It also means that when a package manager is implemented, users can bundle their applications into docker images.

Proposed Implementation

A Dockerfile would initially be placed at the top of the repository. If there's demand for multiple distros/architectures then a Docker directory can be made with the respective builds.

A justfile directive will also be added that allows you to run just docker-build to build your image. A docker push directive may also be implemented as well.

Finally, the CI will be updated with a docker build directive.

[RFC] HTTP Module

I've been working on a HTTP module for a while now. I'm just stuck on a few of the API decisions, specifically on how to use the Response to get the data.
If you have any other feedback on it, that'd be nice too.

Here's the current API in http.d.ts.
<Removed as it is outdated, See below hidden comment>

JavaScript API Documentation

Currently, Spiderfire has 4 modules (assert, fs, path, url) and a bunch of globals Iconsole, setTimeout, etc.). However, it can be a bit confusing for the users who would like to use these since their API differs slightly from stuff like node or deno.

All of the above have typescript and flow declaration files. As such, it would be nice to add documentation in these files for those functions. All contributions are welcome.

Most of the functions should make sense on their own, but feel free to ask here if you need some help.

Requirements: Intermediate in JavaScript
Difficulty: Easy

Resources

Looping over response headers with `for... of` results in a segfault

Try running this code with the cli:

async function f() {
  const response = await fetch("http://api.quotable.io/random");
  for (const [key, value] of response.headers) {
    console.log(`${key} = ${value}`);
  }
  console.log(await response.text());
}

f();

For me, currently, it results in a segfault in:

let cookies: Vec<_> = self_.headers.as_ref().get_all(&SET_COOKIE).iter().map(HeaderValue::clone).collect();

However, I've seen the HeaderIterator fail randomly in other places too. Here's the stack trace:

http::header::map::Pos::is_none (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/http-0.2.9/src/header/map.rs:3164)
http::header::map::Pos::is_some (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/http-0.2.9/src/header/map.rs:3159)
http::header::map::Pos::resolve (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/http-0.2.9/src/header/map.rs:3169)
http::header::map::HeaderMap<T>::find (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/http-0.2.9/src/header/map.rs:1294)
<&http::header::name::HeaderName as http::header::map::as_header_name::Sealed>::find (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/http-0.2.9/src/header/map.rs:3405)
http::header::map::HeaderMap<T>::get_all (/Users/arshia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/http-0.2.9/src/header/map.rs:757)
runtime::globals::fetch::header::class::Headers::iterator (/Users/arshia/repos/wasmer/spiderfire/runtime/src/globals/fetch/header.rs:268)
runtime::globals::fetch::header::class::iterator::wrapper (/Users/arshia/repos/wasmer/spiderfire/runtime/src/globals/fetch/header.rs:170)
runtime::globals::fetch::header::class::iterator::{{closure}} (/Users/arshia/repos/wasmer/spiderfire/runtime/src/globals/fetch/header.rs:170)
core::ops::function::FnOnce::call_once (@core::ops::function::FnOnce::call_once:7)
<core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once (@<core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once:13)
std::panicking::try::do_call (@std::panicking::try::do_call:27)
__rust_try (@__rust_try:11)
std::panicking::try (@std::panicking::try:24)
std::panic::catch_unwind (@std::panic::catch_unwind:6)
runtime::globals::fetch::header::class::iterator (/Users/arshia/repos/wasmer/spiderfire/runtime/src/globals/fetch/header.rs:170)
CallJSNative(JSContext*, bool (*)(JSContext*, unsigned int, JS::Value*), js::CallReason, JS::CallArgs const&) (/Users/arshia/.cargo/git/checkouts/mozjs-fa11ffc7d4f1cc2d/2fa4b2b/mozjs-sys/mozjs/js/src/vm/Interpreter.cpp:486)
js::InternalCallOrConstruct(JSContext*, JS::CallArgs const&, js::MaybeConstruct, js::CallReason) (/Users/arshia/.cargo/git/checkouts/mozjs-fa11ffc7d4f1cc2d/2fa4b2b/mozjs-sys/mozjs/js/src/vm/Interpreter.cpp:580)
InternalCall(JSContext*, js::AnyInvokeArgs const&, js::CallReason) (/Users/arshia/.cargo/git/checkouts/mozjs-fa11ffc7d4f1cc2d/2fa4b2b/mozjs-sys/mozjs/js/src/vm/Interpreter.cpp:647)
js::CallFromStack(JSContext*, JS::CallArgs const&, js::CallReason) (/Users/arshia/.cargo/git/checkouts/mozjs-fa11ffc7d4f1cc2d/2fa4b2b/mozjs-sys/mozjs/js/src/vm/Interpreter.cpp:652)

The event loop doesn't play nice with other async code

(That's a bad issue title, but I can't think of a better one, sorry)

There are two main issues with the current implementation of the event loop:

  • As long as there is work to do, it keeps waking up the task, even if the work is a timer many seconds into the future, leading to busy-waiting and wasted CPU cycles.
  • It does't wake up the task when it receives new work to do (e.g. when a promise continuation or future is enqueued as a result of running some code).

The second point is especially painful. Let's assume there's a select! somewhere:

select! {
  _ = do_stuff() => { ... },
  _ = rt.run_event_loop(), if !rt.event_loop_is_empty() => { ... },
}

and do_stuff does:

fn do_stuff() {
  let foo = fetch_something().await;
  let promise = execute_some_js_with_foo(foo);
  let result = PromiseFuture::new(promise).await;

what happens is that do_stuff will run until it creates the PromiseFuture, then wait for the event loop to progress the promise. However, if the event loop was empty, it won't be polled (this is the only sensible thing to do when running JS alongside native code; otherwise, we're always busy-waiting), so now we're stuck waiting for something else to wake the task up (For context, we didn't discover this so far because the WinterJS event loop was a bit of a patchwork implementation, and relied on being interrupted every millisecond or so.)

Here's my commit that fixes this in our fork: e9e2ec5. I've added a few things:

  • the event loop stores a waker, and when something (future, microtask, macrotask) is enqueued, we wake the task up.
  • the macrotask queue now registers timers when it wants to be woken up later.
  • The event loop itself no longer has a concept of "running to completion". Instead, it has a step function that does as much as it can and register wake-up calls for its pending work. There's a new Future type named RunToEnd that keeps stepping the event loop until it's empty.
  • Runtime::run_event_loop now uses RunToEnd internally, but the semantics should be unchanged.
  • The stepping behavior is exposed through Runtime::step_event_loop. You can see how this is useful here: https://github.com/wasmerio/winterjs/blob/main/src/runners/event_loop_stream.rs

Opt<T> does not respect passing undefined explicitly

Since Opt<T> only checks for the absence of an argument, it does not respect passing undefined explicitly. This is probably the correct implementation:

impl<'cx, T: FromValue<'cx>> FromArgument<'_, 'cx> for Opt<T> {
	type Config = T::Config;

	fn from_argument(accessor: &mut Accessor<'_, 'cx>, config: Self::Config) -> Result<Opt<T>> {
		if accessor.is_empty() {
			Ok(Opt(None))
		} else {
			let val = accessor.value();
			if val.handle().is_undefined() {
				Ok(Opt(None))
			} else {
				T::from_value(accessor.cx(), &val, false, config).map(Some).map(Opt)
			}
		}
	}
}

Objects with getters that fail do not trigger an error

In the implementation of ion::Object::get:

pub fn get<'cx, K: ToPropertyKey<'cx>>(&self, cx: &'cx Context, key: K) -> Option<Value<'cx>> {
let key = key.to_key(cx).unwrap();
if self.has(cx, &key) {
let mut rval = Value::undefined(cx);
unsafe {
JS_GetPropertyById(
cx.as_ptr(),
self.handle().into(),
key.handle().into(),
rval.handle_mut().into(),
)
};
Some(rval)
} else {
None
}
}

The return value of JS_GetPropertyById is not checked, so an object with a getter that fails does not propagate the error. This may also be the case for other calls into SpiderMonkey, but I haven't checked for more.

For context, this is the WPT test that fails as a result of this:

    test(() => {
      assert_throws_js(
        () => new TextDecoderStream('utf-8', {
          get fatal() { throw new Error(); }
        }), 'the constructor should throw');
    }, 'a throwing fatal member should cause the constructor to throw');

Note, the TextDecoderStream implementation is local to WinterJS and I put the test here just to illustrate the problem.

URLSearchParams in URL Module

Although the URL Module is mostly complete, URLSearchParams is currently unimplemented.
This can be implemented with the #[js_class] macro, you can look at the definition of URL or ask here for more help.

Since the URLSearchParams should mutate the underlying URL object, it can probably store a url: &mut Url or the output of Url::query_pairs_mut. Url::query_pairs_mut should deal with most of the underlying implementation details, and the main task will be mapping the underlying functions to the JS API.

Requirements: Basic in JavaScript, Advanced in Rust
Difficulty: Advanced

Resources

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.