redfire75369 / spiderfire Goto Github PK
View Code? Open in Web Editor NEWJavaScript Runtime built with Mozilla's SpiderMonkey Engine
License: Mozilla Public License 2.0
JavaScript Runtime built with Mozilla's SpiderMonkey Engine
License: Mozilla Public License 2.0
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
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:
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)
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)
}
}
}
}
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)
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.
In the implementation of ion::Object::get
:
spiderfire/ion/src/object/object.rs
Lines 83 to 99 in 74a68bb
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.
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
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
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.
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.
As per the issue title, I'm trying to figure out if it's possible to create child classes using #[js_class]
, for example to create a File
class that inherits from Blob
?
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
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
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.
If this object:
{
toString() { return {}; }
}
is fed into String::from_value
, the entire process crashes with a segfault. This is because mozjs's ToString
returns null when the conversion fails, but this is not checked so an ion::String
with a null pointer is created.
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)
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>
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.
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
ion
): Contains wrappers around the native JSAPI.
IonObject
, IonArray
, IonDate
, IonFunction
)assert
)fs
)cli
): Contains the application code for the command-line interface.
eval
)repl
)run
)Building docker images is an important part of deploying server-side applications. It would benefit this project if a docker build is maintained.
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.
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.
(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:
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:
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.Runtime::step_event_loop
. You can see how this is useful here: https://github.com/wasmerio/winterjs/blob/main/src/runners/event_loop_stream.rsExtension 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(())
}
}
From the latest commit on master:
> new File(['a'], 'b').__proto__.__proto__
{}
The second __proto__
should be Blob
, but it's Object
instead. I'm investigating this myself, but I thought it was best to create an issue anyway.
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.
thread_local
or Mutex
could be used to store the ParentRuntime
and Runtime::create_with_parent
could be used to create the appropriate runtimes.
Compilation of spiderfire fails inconsistently on GH Actions during build workflow and when cloning from source.
MacOS build completes successfully
Windows and Ubuntu do not successfully build.
Experience similar issue to the issue in the Ubuntu CI build.
Run cargo update
to update Cargo.lock
's version of mozjs
and mozjs_sys
Refer to issue on rust-mozjs repository for updates
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.
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.
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.
hyper or reqwest can be used to provide HTTP networking APIs in a module.
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_class
es which I assume was put in to support this scenario.
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.
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:
spiderfire/runtime/src/event_loop/macrotasks.rs
Lines 142 to 151 in 403dd73
I'd fix this, but I'm not sure what approach would be best here. I can think of two:
Macrotask::run
to take self by reference, and remove afterwards, andclearInterval
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.
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.
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.
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.