diff --git a/doc/api/errors.md b/doc/api/errors.md
index 3f7e8a4b165943..22295985020337 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -2890,6 +2890,17 @@ The WASI instance has already started.
The WASI instance has not been started.
+
+
+### `ERR_WEBASSEMBLY_RESPONSE`
+
+
+
+The `Response` that has been passed to `WebAssembly.compileStreaming` or to
+`WebAssembly.instantiateStreaming` is not a valid WebAssembly response.
+
### `ERR_WORKER_INIT_FAILED`
diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js
index b64dfaf980fd92..e1b882b6dbe744 100644
--- a/lib/internal/bootstrap/pre_execution.js
+++ b/lib/internal/bootstrap/pre_execution.js
@@ -5,6 +5,7 @@ const {
ObjectDefineProperties,
ObjectDefineProperty,
ObjectGetOwnPropertyDescriptor,
+ PromiseResolve,
SafeMap,
SafeWeakMap,
StringPrototypeStartsWith,
@@ -24,7 +25,11 @@ const {
} = require('internal/util');
const { Buffer } = require('buffer');
-const { ERR_MANIFEST_ASSERT_INTEGRITY } = require('internal/errors').codes;
+const {
+ ERR_INVALID_ARG_TYPE,
+ ERR_MANIFEST_ASSERT_INTEGRITY,
+ ERR_WEBASSEMBLY_RESPONSE,
+} = require('internal/errors').codes;
const assert = require('internal/assert');
function prepareMainThreadExecution(expandArgv1 = false,
@@ -215,6 +220,44 @@ function setupFetch() {
Request: lazyInterface('Request'),
Response: lazyInterface('Response'),
});
+
+ // The WebAssembly Web API: https://webassembly.github.io/spec/web-api
+ internalBinding('wasm_web_api').setImplementation((streamState, source) => {
+ (async () => {
+ const response = await PromiseResolve(source);
+ if (!(response instanceof lazyUndici().Response)) {
+ throw new ERR_INVALID_ARG_TYPE(
+ 'source', ['Response', 'Promise resolving to Response'], response);
+ }
+
+ const contentType = response.headers.get('Content-Type');
+ if (contentType !== 'application/wasm') {
+ throw new ERR_WEBASSEMBLY_RESPONSE(
+ `has unsupported MIME type '${contentType}'`);
+ }
+
+ if (!response.ok) {
+ throw new ERR_WEBASSEMBLY_RESPONSE(
+ `has status code ${response.status}`);
+ }
+
+ if (response.bodyUsed !== false) {
+ throw new ERR_WEBASSEMBLY_RESPONSE('body has already been used');
+ }
+
+ // Pass all data from the response body to the WebAssembly compiler.
+ for await (const chunk of response.body) {
+ streamState.push(chunk);
+ }
+ })().then(() => {
+ // No error occurred. Tell the implementation that the stream has ended.
+ streamState.finish();
+ }, (err) => {
+ // An error occurred, either because the given object was not a valid
+ // and usable Response or because a network error occurred.
+ streamState.abort(err);
+ });
+ });
}
// TODO(aduh95): move this to internal/bootstrap/browser when the CLI flag is
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 9c03a8cf530ee9..b89781aeb10bab 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -1650,6 +1650,7 @@ E('ERR_VM_MODULE_NOT_MODULE',
'Provided module is not an instance of Module', Error);
E('ERR_VM_MODULE_STATUS', 'Module status %s', Error);
E('ERR_WASI_ALREADY_STARTED', 'WASI instance has already started', Error);
+E('ERR_WEBASSEMBLY_RESPONSE', 'WebAssembly response %s', TypeError);
E('ERR_WORKER_INIT_FAILED', 'Worker initialization failure: %s', Error);
E('ERR_WORKER_INVALID_EXEC_ARGV', (errors, msg = 'invalid execArgv flags') =>
`Initiated Worker with ${msg}: ${ArrayPrototypeJoin(errors, ', ')}`,
diff --git a/node.gyp b/node.gyp
index e748208adcde25..4e06d00c823290 100644
--- a/node.gyp
+++ b/node.gyp
@@ -543,6 +543,7 @@
'src/node_util.cc',
'src/node_v8.cc',
'src/node_wasi.cc',
+ 'src/node_wasm_web_api.cc',
'src/node_watchdog.cc',
'src/node_worker.cc',
'src/node_zlib.cc',
diff --git a/src/api/environment.cc b/src/api/environment.cc
index 97261256858403..f3a8f49812d5ce 100644
--- a/src/api/environment.cc
+++ b/src/api/environment.cc
@@ -3,8 +3,10 @@
#include "node_errors.h"
#include "node_internals.h"
#include "node_native_module_env.h"
+#include "node_options-inl.h"
#include "node_platform.h"
#include "node_v8_platform-inl.h"
+#include "node_wasm_web_api.h"
#include "uv.h"
#if HAVE_INSPECTOR
@@ -252,6 +254,13 @@ void SetIsolateMiscHandlers(v8::Isolate* isolate, const IsolateSettings& s) {
s.allow_wasm_code_generation_callback : AllowWasmCodeGenerationCallback;
isolate->SetAllowWasmCodeGenerationCallback(allow_wasm_codegen_cb);
+ Mutex::ScopedLock lock(node::per_process::cli_options_mutex);
+ if (per_process::cli_options->get_per_isolate_options()
+ ->get_per_env_options()
+ ->experimental_fetch) {
+ isolate->SetWasmStreamingCallback(wasm_web_api::StartStreamingCompilation);
+ }
+
if ((s.flags & SHOULD_NOT_SET_PROMISE_REJECTION_CALLBACK) == 0) {
auto* promise_reject_cb = s.promise_reject_callback ?
s.promise_reject_callback : PromiseRejectCallback;
diff --git a/src/env.h b/src/env.h
index bededfcb5debfe..7e35833e45bd25 100644
--- a/src/env.h
+++ b/src/env.h
@@ -550,7 +550,9 @@ constexpr size_t kFsStatsBufferLength =
V(tls_wrap_constructor_function, v8::Function) \
V(trace_category_state_function, v8::Function) \
V(udp_constructor_function, v8::Function) \
- V(url_constructor_function, v8::Function)
+ V(url_constructor_function, v8::Function) \
+ V(wasm_streaming_compilation_impl, v8::Function) \
+ V(wasm_streaming_object_constructor, v8::Function)
class Environment;
struct AllocatedBuffer;
diff --git a/src/node_binding.cc b/src/node_binding.cc
index 29b9ccdaed8b10..2991ee34746e0f 100644
--- a/src/node_binding.cc
+++ b/src/node_binding.cc
@@ -87,6 +87,7 @@
V(uv) \
V(v8) \
V(wasi) \
+ V(wasm_web_api) \
V(watchdog) \
V(worker) \
V(zlib)
diff --git a/src/node_wasm_web_api.cc b/src/node_wasm_web_api.cc
new file mode 100644
index 00000000000000..b23096120b1121
--- /dev/null
+++ b/src/node_wasm_web_api.cc
@@ -0,0 +1,196 @@
+#include "node_wasm_web_api.h"
+
+#include "memory_tracker-inl.h"
+#include "node_errors.h"
+
+namespace node {
+namespace wasm_web_api {
+
+using v8::ArrayBuffer;
+using v8::ArrayBufferView;
+using v8::Context;
+using v8::Function;
+using v8::FunctionCallbackInfo;
+using v8::FunctionTemplate;
+using v8::Local;
+using v8::MaybeLocal;
+using v8::Object;
+using v8::Value;
+using v8::WasmStreaming;
+
+Local WasmStreamingObject::Initialize(Environment* env) {
+ Local templ = env->wasm_streaming_object_constructor();
+ if (!templ.IsEmpty()) {
+ return templ;
+ }
+
+ Local t = env->NewFunctionTemplate(New);
+ t->Inherit(BaseObject::GetConstructorTemplate(env));
+ t->InstanceTemplate()->SetInternalFieldCount(
+ WasmStreamingObject::kInternalFieldCount);
+
+ env->SetProtoMethod(t, "push", Push);
+ env->SetProtoMethod(t, "finish", Finish);
+ env->SetProtoMethod(t, "abort", Abort);
+
+ auto function = t->GetFunction(env->context()).ToLocalChecked();
+ env->set_wasm_streaming_object_constructor(function);
+ return function;
+}
+
+void WasmStreamingObject::RegisterExternalReferences(
+ ExternalReferenceRegistry* registry) {
+ registry->Register(Push);
+ registry->Register(Finish);
+ registry->Register(Abort);
+}
+
+void WasmStreamingObject::MemoryInfo(MemoryTracker* tracker) const {
+ // v8::WasmStreaming is opaque. We assume that the size of the WebAssembly
+ // module that is being compiled is roughly what V8 allocates (as in, off by
+ // only a small factor).
+ tracker->TrackFieldWithSize("streaming", wasm_size_);
+}
+
+MaybeLocal