diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index 51f2b26dee7..e26b99b85f6 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -669,15 +669,12 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter } else if (parameter.type->name() == "Promise") { // https://webidl.spec.whatwg.org/#js-promise scoped_generator.append(R"~~~( - if (!@js_name@@js_suffix@.is_cell() || !is(@js_name@@js_suffix@.as_cell())) { - // 1. Let promiseCapability be ? NewPromiseCapability(%Promise%). - auto promise_capability = TRY(JS::new_promise_capability(vm, realm.intrinsics().promise_constructor())); - // 2. Perform ? Call(promiseCapability.[[Resolve]], undefined, « V »). - TRY(JS::call(vm, *promise_capability->resolve(), JS::js_undefined(), @js_name@@js_suffix@)); - // 3. Return promiseCapability. - @js_name@@js_suffix@ = promise_capability; - } - auto @cpp_name@ = JS::make_handle(static_cast(@js_name@@js_suffix@.as_cell())); + // 1. Let promiseCapability be ? NewPromiseCapability(%Promise%). + auto promise_capability = TRY(JS::new_promise_capability(vm, realm.intrinsics().promise_constructor())); + // 2. Perform ? Call(promiseCapability.[[Resolve]], undefined, « V »). + TRY(JS::call(vm, *promise_capability->resolve(), JS::js_undefined(), @js_name@@js_suffix@)); + // 3. Return promiseCapability. + auto @cpp_name@ = JS::make_handle(promise_capability); )~~~"); } else if (parameter.type->name() == "object") { if (parameter.type->is_nullable()) { diff --git a/Tests/LibWeb/Text/data/greeter.wasm b/Tests/LibWeb/Text/data/greeter.wasm new file mode 100644 index 00000000000..bda0b465fa9 Binary files /dev/null and b/Tests/LibWeb/Text/data/greeter.wasm differ diff --git a/Tests/LibWeb/Text/expected/Wasm/WebAssembly-instantiate-streaming.txt b/Tests/LibWeb/Text/expected/Wasm/WebAssembly-instantiate-streaming.txt new file mode 100644 index 00000000000..994f4b6a896 --- /dev/null +++ b/Tests/LibWeb/Text/expected/Wasm/WebAssembly-instantiate-streaming.txt @@ -0,0 +1,5 @@ +WebAssembly.instantiateStreaming +Hello from wasm!!!!!! +WebAssembly.compileStreaming +Hello from wasm!!!!!! +Sanity check diff --git a/Tests/LibWeb/Text/input/Wasm/WebAssembly-instantiate-streaming.html b/Tests/LibWeb/Text/input/Wasm/WebAssembly-instantiate-streaming.html new file mode 100644 index 00000000000..d7567e287ae --- /dev/null +++ b/Tests/LibWeb/Text/input/Wasm/WebAssembly-instantiate-streaming.html @@ -0,0 +1,102 @@ + + + diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.cpp b/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.cpp index ea73bda19a0..bf0334880bc 100644 --- a/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.cpp +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.cpp @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #include #include @@ -35,6 +37,7 @@ namespace Web::WebAssembly { static JS::NonnullGCPtr asynchronously_compile_webassembly_module(JS::VM&, ByteBuffer, HTML::Task::Source = HTML::Task::Source::Unspecified); static JS::NonnullGCPtr instantiate_promise_of_module(JS::VM&, JS::NonnullGCPtr, JS::GCPtr import_object); static JS::NonnullGCPtr asynchronously_instantiate_webassembly_module(JS::VM&, JS::NonnullGCPtr, JS::GCPtr import_object); +static JS::NonnullGCPtr compile_potential_webassembly_response(JS::VM&, JS::NonnullGCPtr); namespace Detail { @@ -101,6 +104,13 @@ WebIDL::ExceptionOr> compile(JS::VM& vm, JS::H return asynchronously_compile_webassembly_module(vm, stable_bytes.release_value()); } +// https://webassembly.github.io/spec/web-api/index.html#dom-webassembly-compilestreaming +WebIDL::ExceptionOr> compile_streaming(JS::VM& vm, JS::Handle source) +{ + // The compileStreaming(source) method, when invoked, returns the result of compiling a potential WebAssembly response with source. + return compile_potential_webassembly_response(vm, *source); +} + // https://webassembly.github.io/spec/js-api/#dom-webassembly-instantiate WebIDL::ExceptionOr> instantiate(JS::VM& vm, JS::Handle& bytes, Optional>& import_object_handle) { @@ -130,6 +140,19 @@ WebIDL::ExceptionOr> instantiate(JS::VM& vm, M return asynchronously_instantiate_webassembly_module(vm, module, imports); } +// https://webassembly.github.io/spec/web-api/index.html#dom-webassembly-instantiatestreaming +WebIDL::ExceptionOr> instantiate_streaming(JS::VM& vm, JS::Handle source, Optional>& import_object) +{ + // The instantiateStreaming(source, importObject) method, when invoked, performs the following steps: + + // 1. Let promiseOfModule be the result of compiling a potential WebAssembly response with source. + auto promise_of_module = compile_potential_webassembly_response(vm, *source); + + // 2. Return the result of instantiating the promise of a module promiseOfModule with imports importObject. + auto imports = JS::GCPtr { import_object.has_value() ? import_object.value().ptr() : nullptr }; + return instantiate_promise_of_module(vm, promise_of_module, imports); +} + namespace Detail { JS::ThrowCompletionOr> instantiate_module(JS::VM& vm, Wasm::Module const& module, JS::GCPtr import_object) @@ -624,4 +647,110 @@ JS::NonnullGCPtr instantiate_promise_of_module(JS::VM& vm, JS:: return promise; } +// https://webassembly.github.io/spec/web-api/index.html#compile-a-potential-webassembly-response +JS::NonnullGCPtr compile_potential_webassembly_response(JS::VM& vm, JS::NonnullGCPtr source) +{ + auto& realm = *vm.current_realm(); + + // Note: This algorithm accepts a Response object, or a promise for one, and compiles and instantiates the resulting bytes of the response. + // This compilation can be performed in the background and in a streaming manner. + // If the Response is not CORS-same-origin, does not represent an ok status, or does not match the `application/wasm` MIME type, + // the returned promise will be rejected with a TypeError; if compilation or instantiation fails, + // the returned promise will be rejected with a CompileError or other relevant error type, depending on the cause of failure. + + // 1. Let returnValue be a new promise + auto return_value = WebIDL::create_promise(realm); + + // 2. Upon fulfillment of source with value unwrappedSource: + auto fulfillment_steps = JS::create_heap_function(vm.heap(), [&vm, return_value](JS::Value unwrapped_source) -> WebIDL::ExceptionOr { + auto& realm = HTML::relevant_realm(*return_value->promise()); + + // 1. Let response be unwrappedSource’s response. + if (!unwrapped_source.is_object() || !is(unwrapped_source.as_object())) { + WebIDL::reject_promise(realm, return_value, *vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "Response").value()); + return JS::js_undefined(); + } + auto& response_object = static_cast(unwrapped_source.as_object()); + auto response = response_object.response(); + + // 2. Let mimeType be the result of getting `Content-Type` from response’s header list. + // 3. If mimeType is null, reject returnValue with a TypeError and abort these substeps. + // 4. Remove all HTTP tab or space byte from the start and end of mimeType. + // 5. If mimeType is not a byte-case-insensitive match for `application/wasm`, reject returnValue with a TypeError and abort these substeps. + // Note: extra parameters are not allowed, including the empty `application/wasm;`. + // FIXME: Validate these extra constraints that are not checked by extract_mime_type() + if (auto mime = response->header_list()->extract_mime_type(); !mime.has_value() || mime.value().essence() != "application/wasm"sv) { + WebIDL::reject_promise(realm, return_value, *vm.throw_completion("Response does not match the application/wasm MIME type"sv).value()); + return JS::js_undefined(); + } + + // 6. If response is not CORS-same-origin, reject returnValue with a TypeError and abort these substeps. + // https://html.spec.whatwg.org/#cors-same-origin + auto type = response_object.type(); + if (type != Bindings::ResponseType::Basic && type != Bindings::ResponseType::Cors && type != Bindings::ResponseType::Default) { + WebIDL::reject_promise(realm, return_value, *vm.throw_completion("Response is not CORS-same-origin"sv).value()); + return JS::js_undefined(); + } + + // 7. If response’s status is not an ok status, reject returnValue with a TypeError and abort these substeps. + if (!response_object.ok()) { + WebIDL::reject_promise(realm, return_value, *vm.throw_completion("Response does not represent an ok status"sv).value()); + return JS::js_undefined(); + } + + // 8. Consume response’s body as an ArrayBuffer, and let bodyPromise be the result. + auto body_promise_or_error = response_object.array_buffer(); + if (body_promise_or_error.is_error()) { + auto throw_completion = Bindings::dom_exception_to_throw_completion(realm.vm(), body_promise_or_error.release_error()); + WebIDL::reject_promise(realm, return_value, *throw_completion.value()); + return JS::js_undefined(); + } + auto body_promise = body_promise_or_error.release_value(); + + // 9. Upon fulfillment of bodyPromise with value bodyArrayBuffer: + auto body_fulfillment_steps = JS::create_heap_function(vm.heap(), [&vm, return_value](JS::Value body_array_buffer) -> WebIDL::ExceptionOr { + // 1. Let stableBytes be a copy of the bytes held by the buffer bodyArrayBuffer. + VERIFY(body_array_buffer.is_object()); + auto stable_bytes = WebIDL::get_buffer_source_copy(body_array_buffer.as_object()); + if (stable_bytes.is_error()) { + VERIFY(stable_bytes.error().code() == ENOMEM); + WebIDL::reject_promise(HTML::relevant_realm(*return_value->promise()), return_value, *vm.throw_completion(vm.error_message(JS::VM::ErrorMessage::OutOfMemory)).value()); + return JS::js_undefined(); + } + + // 2. Asynchronously compile the WebAssembly module stableBytes using the networking task source and resolve returnValue with the result. + auto result = asynchronously_compile_webassembly_module(vm, stable_bytes.release_value(), HTML::Task::Source::Networking); + + // Need to manually convert WebIDL promise to an ECMAScript value here to resolve + WebIDL::resolve_promise(HTML::relevant_realm(*return_value->promise()), return_value, result->promise()); + + return JS::js_undefined(); + }); + + // 10. Upon rejection of bodyPromise with reason reason: + auto body_rejection_steps = JS::create_heap_function(vm.heap(), [return_value](JS::Value reason) -> WebIDL::ExceptionOr { + // 1. Reject returnValue with reason. + WebIDL::reject_promise(HTML::relevant_realm(*return_value->promise()), return_value, reason); + return JS::js_undefined(); + }); + + WebIDL::react_to_promise(body_promise, body_fulfillment_steps, body_rejection_steps); + + return JS::js_undefined(); + }); + + // 3. Upon rejection of source with reason reason: + auto rejection_steps = JS::create_heap_function(vm.heap(), [return_value](JS::Value reason) -> WebIDL::ExceptionOr { + // 1. Reject returnValue with reason. + WebIDL::reject_promise(HTML::relevant_realm(*return_value->promise()), return_value, reason); + + return JS::js_undefined(); + }); + + WebIDL::react_to_promise(source, fulfillment_steps, rejection_steps); + + // 4. Return returnValue. + return return_value; +} + } diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.h b/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.h index 9783d9d69c9..85a3a706c70 100644 --- a/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.h +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.h @@ -23,9 +23,11 @@ void finalize(JS::Object&); bool validate(JS::VM&, JS::Handle& bytes); WebIDL::ExceptionOr> compile(JS::VM&, JS::Handle& bytes); +WebIDL::ExceptionOr> compile_streaming(JS::VM&, JS::Handle source); WebIDL::ExceptionOr> instantiate(JS::VM&, JS::Handle& bytes, Optional>& import_object); WebIDL::ExceptionOr> instantiate(JS::VM&, Module const& module_object, Optional>& import_object); +WebIDL::ExceptionOr> instantiate_streaming(JS::VM&, JS::Handle source, Optional>& import_object); namespace Detail { struct CompiledWebAssemblyModule : public RefCounted { diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.idl b/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.idl index ca421a4df7f..ab45651ffad 100644 --- a/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.idl +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.idl @@ -1,5 +1,6 @@ #import #import +#import dictionary WebAssemblyInstantiatedSource { required Module module; @@ -7,11 +8,16 @@ dictionary WebAssemblyInstantiatedSource { }; // https://webassembly.github.io/spec/js-api/#webassembly-namespace +// https://webassembly.github.io/spec/web-api/index.html#streaming-modules [Exposed=*, WithGCVisitor, WithFinalizer] namespace WebAssembly { + // FIXME: Streaming APIs are supposed to be only exposed to Window, Worker + boolean validate(BufferSource bytes); Promise compile(BufferSource bytes); + Promise compileStreaming(Promise source); Promise instantiate(BufferSource bytes, optional object importObject); + Promise instantiateStreaming(Promise source, optional object importObject); Promise instantiate(Module moduleObject, optional object importObject); };