Skip to content

Commit

Permalink
feat: StarlingMonkey update (#1067)
Browse files Browse the repository at this point in the history
  • Loading branch information
guybedford authored Dec 9, 2024
1 parent c160c4a commit 857f6fa
Show file tree
Hide file tree
Showing 45 changed files with 1,529 additions and 13,781 deletions.
5 changes: 5 additions & 0 deletions integration-tests/js-compute/fixtures/app/src/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ routes.set('/response/request-body-init', async () => {
return postResp;
});

routes.set('/response/blob', async () => {
const blob = new Blob(['<h1>blob</h1>'], { type: 'text/html' });
return new Response(blob);
});

function iteratableToStream(iterable) {
return new ReadableStream({
async pull(controller) {
Expand Down
8 changes: 8 additions & 0 deletions integration-tests/js-compute/fixtures/app/tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,14 @@
"GET /request/clone/called-unbound": {},
"GET /request/clone/valid": {},
"GET /request/clone/invalid": {},
"GET /response/blob": {
"environments": ["<disabled for now>"],
"downstream_response": {
"status": 200,
"headers": { "content-type": "text/html" },
"body": ["<h1>blob</h1>"]
}
},
"GET /response/stall": {
"body_streaming": "none"
},
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"test:cli": "brittle --bail integration-tests/cli/**.test.js",
"test:integration": "node ./integration-tests/js-compute/test.js",
"test:wpt": "tests/wpt-harness/build-wpt-runtime.sh && node ./tests/wpt-harness/run-wpt.mjs -vv",
"test:wpt:debug": "tests/wpt-harness/build-wpt-runtime.sh --debug-build && node ./tests/wpt-harness/run-wpt.mjs --starlingmonkey -vv",
"test:wpt:debug": "tests/wpt-harness/build-wpt-runtime.sh --debug-build && node ./tests/wpt-harness/run-wpt.mjs -vv",
"test:types": "tsd",
"build": "./runtime/fastly/build-release.sh",
"build:debug": "./runtime/fastly/build-debug.sh",
Expand Down
2 changes: 1 addition & 1 deletion runtime/StarlingMonkey
Submodule StarlingMonkey updated 59 files
+4 −2 CMakeLists.txt
+72 −0 README.md
+738 −0 builtins/web/blob.cpp
+92 −0 builtins/web/blob.h
+2 −3 builtins/web/fetch/fetch_event.cpp
+170 −117 builtins/web/fetch/request-response.cpp
+6 −6 builtins/web/fetch/request-response.h
+25 −15 builtins/web/streams/native-stream-source.cpp
+11 −3 builtins/web/streams/native-stream-source.h
+4 −3 builtins/web/streams/transform-stream.cpp
+48 −25 builtins/web/structured-clone.cpp
+1 −0 cmake/builtins.cmake
+254 −0 host-apis/wasi-0.2.0/handles.h
+1 −0 host-apis/wasi-0.2.0/host_api.cmake
+94 −272 host-apis/wasi-0.2.0/host_api.cpp
+16 −0 include/builtin.h
+1 −0 include/extension-api.h
+2 −1 include/host_api.h
+88 −0 justfile
+52 −0 tests/e2e/blob/blob.js
+2 −0 tests/e2e/blob/expect_serve_stdout.txt
+1 −0 tests/e2e/multi-stream-forwarding/expect_serve_body.txt
+58 −0 tests/e2e/multi-stream-forwarding/multi-stream-forwarding.js
+11 −3 tests/test.sh
+3 −0 tests/tests.cmake
+17 −0 tests/wpt-harness/expectations/FileAPI/blob/Blob-array-buffer.any.js.json
+17 −0 tests/wpt-harness/expectations/FileAPI/blob/Blob-bytes.any.js.json
+14 −0 tests/wpt-harness/expectations/FileAPI/blob/Blob-constructor-dom.window.js.json
+191 −0 tests/wpt-harness/expectations/FileAPI/blob/Blob-constructor.any.js.json
+14 −0 tests/wpt-harness/expectations/FileAPI/blob/Blob-slice-overflow.any.js.json
+383 −0 tests/wpt-harness/expectations/FileAPI/blob/Blob-slice.any.js.json
+20 −0 tests/wpt-harness/expectations/FileAPI/blob/Blob-stream.any.js.json
+26 −0 tests/wpt-harness/expectations/FileAPI/blob/Blob-text.any.js.json
+27 −0 tests/wpt-harness/expectations/WebCryptoAPI/getRandomValues.any.js.json
+3 −0 tests/wpt-harness/expectations/fetch/api/basic/keepalive.any.js.json
+1 −1 tests/wpt-harness/expectations/fetch/api/body/cloned-any.js.json
+3 −0 tests/wpt-harness/expectations/fetch/api/request/request-constructor-init-body-override.any.js.json
+3 −3 tests/wpt-harness/expectations/fetch/api/request/request-consume-empty.any.js.json
+11 −11 tests/wpt-harness/expectations/fetch/api/request/request-consume.any.js.json
+29 −0 tests/wpt-harness/expectations/fetch/api/request/request-disturbed.any.js.json
+1 −1 tests/wpt-harness/expectations/fetch/api/request/request-headers.any.js.json
+3 −3 tests/wpt-harness/expectations/fetch/api/request/request-init-contenttype.any.js.json
+2 −2 tests/wpt-harness/expectations/fetch/api/request/request-structure.any.js.json
+3 −3 tests/wpt-harness/expectations/fetch/api/response/response-consume-empty.any.js.json
+2 −2 tests/wpt-harness/expectations/fetch/api/response/response-error-from-stream.any.js.json
+3 −3 tests/wpt-harness/expectations/fetch/api/response/response-init-contenttype.any.js.json
+1 −1 tests/wpt-harness/expectations/fetch/api/response/response-stream-bad-chunk.any.js.json
+3 −3 tests/wpt-harness/expectations/fetch/api/response/response-stream-disturbed-1.any.js.json
+3 −3 tests/wpt-harness/expectations/fetch/api/response/response-stream-disturbed-2.any.js.json
+3 −3 tests/wpt-harness/expectations/fetch/api/response/response-stream-disturbed-3.any.js.json
+3 −3 tests/wpt-harness/expectations/fetch/api/response/response-stream-disturbed-4.any.js.json
+16 −16 tests/wpt-harness/expectations/html/webappapis/structured-clone/structured-clone.any.js.json
+3 −0 tests/wpt-harness/expectations/streams/readable-byte-streams/tee.any.js.json
+39 −0 tests/wpt-harness/expectations/url/url-constructor.any.js.json
+39 −0 tests/wpt-harness/expectations/url/url-origin.any.js.json
+4 −0 tests/wpt-harness/post-harness.js
+1 −1 tests/wpt-harness/pre-harness.js
+9 −0 tests/wpt-harness/tests.json
+7 −7 tests/wpt-harness/wpt.cmake
2 changes: 1 addition & 1 deletion runtime/fastly/builtins/cache-core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,7 @@ bool CacheEntry::body(JSContext *cx, unsigned argc, JS::Value *vp) {
// pull. With the default HWM of 1.0, the streams implementation causes a
// pull, which means we enqueue a read from the host handle, which we quite
// often have no interest in at all.
JS::RootedObject body_stream(cx, JS::NewReadableDefaultStreamObject(cx, source, nullptr, 0.0));
JS::RootedObject body_stream(cx, NativeStreamSource::stream(source));
if (!body_stream) {
return false;
}
Expand Down
96 changes: 72 additions & 24 deletions runtime/fastly/builtins/fetch/request-response.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "request-response.h"
#include "../../../StarlingMonkey/builtins/web/base64.h"
// #include "../../../StarlingMonkey/builtins/web/blob.h"
#include "../../../StarlingMonkey/builtins/web/dom-exception.h"
#include "../../../StarlingMonkey/builtins/web/streams/native-stream-source.h"
#include "../../../StarlingMonkey/builtins/web/streams/transform-stream.h"
Expand Down Expand Up @@ -33,6 +34,8 @@
#pragma clang diagnostic pop

using builtins::web::base64::valueToJSByteString;
// using builtins::web::blob::Blob;
// using builtins::web::blob::BlobReader;
using builtins::web::dom_exception::DOMException;

// We use the StarlingMonkey Headers implementation, despite it supporting features that we do
Expand Down Expand Up @@ -65,10 +68,6 @@ using fastly::kv_store::KVStoreEntry;

namespace builtins::web::streams {

JSObject *NativeStreamSource::stream(JSObject *self) {
return fastly::fetch::RequestOrResponse::body_stream(owner(self));
}

bool NativeStreamSource::stream_is_body(JSContext *cx, JS::HandleObject stream) {
JSObject *stream_source = get_stream_source(cx, stream);
return NativeStreamSource::is_instance(stream_source) &&
Expand All @@ -80,16 +79,15 @@ bool NativeStreamSource::stream_is_body(JSContext *cx, JS::HandleObject stream)
namespace fastly::fetch {

namespace {
bool error_stream_controller_with_pending_exception(JSContext *cx, JS::HandleObject controller) {
bool error_stream_controller_with_pending_exception(JSContext *cx, JS::HandleObject stream) {
JS::RootedValue exn(cx);
if (!JS_GetPendingException(cx, &exn))
return false;
JS_ClearPendingException(cx);

JS::RootedValueArray<1> args(cx);
args[0].set(exn);
JS::RootedValue r(cx);
return JS::Call(cx, controller, "error", args, &r);
RootedValue args(cx);
args.set(exn);
return JS::ReadableStreamError(cx, stream, args);
}

constexpr size_t HANDLE_READ_CHUNK_SIZE = 8192;
Expand All @@ -101,18 +99,18 @@ bool process_body_read(JSContext *cx, host_api::HttpBody::Handle handle, JS::Han
MOZ_ASSERT(NativeStreamSource::is_instance(streamSource));
host_api::HttpBody body(handle);
JS::RootedObject owner(cx, NativeStreamSource::owner(streamSource));
JS::RootedObject controller(cx, NativeStreamSource::controller(streamSource));
JS::RootedObject stream(cx, NativeStreamSource::stream(streamSource));

auto read_res = body.read(HANDLE_READ_CHUNK_SIZE);
if (auto *err = read_res.to_err()) {
HANDLE_ERROR(cx, *err);
return error_stream_controller_with_pending_exception(cx, controller);
return error_stream_controller_with_pending_exception(cx, stream);
}

auto &chunk = read_res.unwrap();
if (chunk.len == 0) {
JS::RootedValue r(cx);
return JS::Call(cx, controller, "close", JS::HandleValueArray::empty(), &r);
return JS::ReadableStreamClose(cx, stream);
}

// We don't release control of chunk's data until after we've checked that the array buffer
Expand All @@ -122,7 +120,7 @@ bool process_body_read(JSContext *cx, host_api::HttpBody::Handle handle, JS::Han
cx, JS::NewArrayBufferWithContents(cx, chunk.len, chunk.ptr.get(),
JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory));
if (!buffer) {
return error_stream_controller_with_pending_exception(cx, controller);
return error_stream_controller_with_pending_exception(cx, stream);
}

// At this point `buffer` has taken full ownership of the chunk's data.
Expand All @@ -133,11 +131,10 @@ bool process_body_read(JSContext *cx, host_api::HttpBody::Handle handle, JS::Han
return false;
}

JS::RootedValueArray<1> enqueue_args(cx);
enqueue_args[0].setObject(*byte_array);
JS::RootedValue r(cx);
if (!JS::Call(cx, controller, "enqueue", enqueue_args, &r)) {
return error_stream_controller_with_pending_exception(cx, controller);
RootedValue enqueue_val(cx);
enqueue_val.setObject(*byte_array);
if (!JS::ReadableStreamEnqueue(cx, stream, enqueue_val)) {
return error_stream_controller_with_pending_exception(cx, stream);
}

return true;
Expand Down Expand Up @@ -349,6 +346,7 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self,
const char *content_type = nullptr;

// We currently support five types of body inputs:
// - Blob
// - byte sequence
// - buffer source
// - USV strings
Expand All @@ -360,6 +358,45 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self,

JS::RootedObject body_obj(cx, body_val.isObject() ? &body_val.toObject() : nullptr);

host_api::HostString host_type_str;

// Blob support disabled pending bug fix in test
// /override-content-length/request/init/object-literal/true
/*if (body_obj && Blob::is_instance(body_obj)) {
auto native_stream = NativeStreamSource::create(cx, body_obj, JS::UndefinedHandleValue,
Blob::stream_pull, Blob::stream_cancel);
if (!native_stream) {
return false;
}
JS::RootedObject source(cx, native_stream);
if (!source) {
return false;
}
auto readers = Blob::readers(body_obj);
auto blob = Blob::blob(body_obj);
auto span = std::span<uint8_t>(blob->begin(), blob->length());
if (!readers->put(source, BlobReader(span))) {
return false;
}
JS::RootedObject stream(cx, NativeStreamSource::stream(native_stream));
if (!stream) {
return false;
}
JS_SetReservedSlot(self, static_cast<uint32_t>(RequestOrResponse::Slots::BodyStream),
JS::ObjectValue(*stream));
JS::RootedString type_str(cx, Blob::type(body_obj));
if (JS::GetStringLength(type_str) > 0) {
host_type_str = core::encode(cx, type_str);
MOZ_ASSERT(host_type_str);
content_type = host_type_str.ptr.get();
}
} else */
if (body_obj && JS::IsReadableStream(body_obj)) {
if (RequestOrResponse::body_unusable(cx, body_obj)) {
JS_ReportErrorNumberLatin1(cx, FastlyGetErrorMessage, nullptr,
Expand Down Expand Up @@ -547,7 +584,19 @@ bool RequestOrResponse::parse_body(JSContext *cx, JS::HandleObject self, JS::Uni
}
static_cast<void>(buf.release());
result.setObject(*array_buffer);
} else {
}
// TODO: Blob support disabled pending bug fix
/* else if constexpr (result_type == RequestOrResponse::BodyReadResult::Blob) {
JS::RootedString contentType(cx, JS_GetEmptyString(cx));
JS::RootedObject blob(cx, Blob::create(cx, std::move(buf), len, contentType));
if (!blob) {
return RejectPromiseWithPendingError(cx, result_promise);
}
result.setObject(*blob);
} */
else {
JS::RootedString text(cx, JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(buf.get(), len)));
if (!text) {
return RejectPromiseWithPendingError(cx, result_promise);
Expand Down Expand Up @@ -1185,11 +1234,7 @@ JSObject *RequestOrResponse::create_body_stream(JSContext *cx, JS::HandleObject
if (!source)
return nullptr;

// Create a readable stream with a highwater mark of 0.0 to prevent an eager
// pull. With the default HWM of 1.0, the streams implementation causes a
// pull, which means we enqueue a read from the host handle, which we quite
// often have no interest in at all.
JS::RootedObject body_stream(cx, JS::NewReadableDefaultStreamObject(cx, source, nullptr, 0.0));
JS::RootedObject body_stream(cx, NativeStreamSource::stream(source));
if (!body_stream) {
return nullptr;
}
Expand Down Expand Up @@ -1647,6 +1692,8 @@ const JSPropertySpec Request::static_properties[] = {
const JSFunctionSpec Request::methods[] = {
JS_FN("arrayBuffer", Request::bodyAll<RequestOrResponse::BodyReadResult::ArrayBuffer>, 0,
JSPROP_ENUMERATE),
// JS_FN("blob", Request::bodyAll<RequestOrResponse::BodyReadResult::Blob>, 0,
// JSPROP_ENUMERATE),
JS_FN("json", Request::bodyAll<RequestOrResponse::BodyReadResult::JSON>, 0, JSPROP_ENUMERATE),
JS_FN("text", Request::bodyAll<RequestOrResponse::BodyReadResult::Text>, 0, JSPROP_ENUMERATE),
JS_FN("setCacheOverride", Request::setCacheOverride, 3, JSPROP_ENUMERATE),
Expand Down Expand Up @@ -2900,6 +2947,7 @@ const JSPropertySpec Response::static_properties[] = {
const JSFunctionSpec Response::methods[] = {
JS_FN("arrayBuffer", bodyAll<RequestOrResponse::BodyReadResult::ArrayBuffer>, 0,
JSPROP_ENUMERATE),
// JS_FN("blob", bodyAll<RequestOrResponse::BodyReadResult::Blob>, 0, JSPROP_ENUMERATE),
JS_FN("json", bodyAll<RequestOrResponse::BodyReadResult::JSON>, 0, JSPROP_ENUMERATE),
JS_FN("text", bodyAll<RequestOrResponse::BodyReadResult::Text>, 0, JSPROP_ENUMERATE),
JS_FN("setManualFramingHeaders", Response::setManualFramingHeaders, 1, JSPROP_ENUMERATE),
Expand Down
2 changes: 2 additions & 0 deletions runtime/fastly/builtins/fetch/request-response.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class RequestOrResponse final {

enum class BodyReadResult {
ArrayBuffer,
Blob,
JSON,
Text,
};
Expand Down Expand Up @@ -214,6 +215,7 @@ class Response final : public builtins::BuiltinImpl<Response> {
HasBody = static_cast<int>(RequestOrResponse::Slots::HasBody),
BodyUsed = static_cast<int>(RequestOrResponse::Slots::BodyUsed),
Headers = static_cast<int>(RequestOrResponse::Slots::Headers),
URL = static_cast<int>(RequestOrResponse::Slots::Headers),
ManualFramingHeaders = static_cast<int>(RequestOrResponse::Slots::ManualFramingHeaders),
Backend = static_cast<int>(RequestOrResponse::Slots::Backend),
IsUpstream = static_cast<int>(RequestOrResponse::Slots::Count),
Expand Down
Loading

0 comments on commit 857f6fa

Please sign in to comment.