Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Request and Response blob methods #177

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 38 additions & 24 deletions builtins/web/blob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
#include "builtin.h"
#include "encode.h"
#include "extension-api.h"
#include "mozilla/UniquePtr.h"
#include "js/UniquePtr.h"
#include "rust-encoding.h"
#include "streams/native-stream-source.h"

#include "mozilla/UniquePtr.h"
#include "js/ArrayBuffer.h"
#include "js/Conversions.h"
#include "js/experimental/TypedData.h"
#include "js/HashTable.h"
#include "js/Stream.h"
#include "js/TypeDecls.h"
#include "js/Value.h"
Expand Down Expand Up @@ -149,6 +151,8 @@ namespace builtins {
namespace web {
namespace blob {

using js::Vector;

const JSFunctionSpec Blob::static_methods[] = {
JS_FS_END,
};
Expand Down Expand Up @@ -247,15 +251,15 @@ JSObject *Blob::data_to_owned_array_buffer(JSContext *cx, HandleObject self) {
JSObject *Blob::data_to_owned_array_buffer(JSContext *cx, HandleObject self, size_t offset,
size_t size, size_t *bytes_read) {
auto blob = Blob::blob(self);
auto blob_size = blob->size();
auto blob_size = blob->length();
*bytes_read = 0;

MOZ_ASSERT(offset <= blob_size);

size_t available_bytes = blob_size - offset;
size_t read_size = std::min(size, available_bytes);

auto span = std::span<uint8_t>(blob->data() + offset, read_size);
auto span = std::span<uint8_t>(blob->begin() + offset, read_size);
auto array_buffer = new_array_buffer_from_span(cx, span);
if (!array_buffer) {
return nullptr;
Expand Down Expand Up @@ -319,7 +323,7 @@ bool Blob::slice(JSContext *cx, unsigned argc, JS::Value *vp) {
METHOD_HEADER(0)

auto src = Blob::blob(self);
int64_t size = src->size();
int64_t size = src->length();
int64_t start = 0;
int64_t end = size;

Expand Down Expand Up @@ -350,11 +354,14 @@ bool Blob::slice(JSContext *cx, unsigned argc, JS::Value *vp) {
start = (start < 0) ? std::max((size + start), 0LL) : std::min(start, size);
end = (end < 0) ? std::max((size + end), 0LL) : std::min(end, size);

auto dst = (end - start > 0)
? std::make_unique<std::vector<uint8_t>>(src->begin() + start, src->begin() + end)
: std::make_unique<std::vector<uint8_t>>();
auto slice_len = std::max(end - start, 0LL);
auto dst = (slice_len > 0) ? UniqueChars(js_pod_malloc<char>(slice_len)) : nullptr;

if (dst) {
std::copy(src->begin() + start, src->begin() + end, dst.get());
}

JS::RootedObject new_blob(cx, create(cx, std::move(dst), contentType));
JS::RootedObject new_blob(cx, create(cx, std::move(dst), slice_len, contentType));
if (!new_blob) {
return false;
}
Expand All @@ -376,7 +383,7 @@ bool Blob::stream(JSContext *cx, unsigned argc, JS::Value *vp) {

auto readers = Blob::readers(self);
auto blob = Blob::blob(self);
auto span = std::span<uint8_t>(blob->data(), blob->size());
auto span = std::span<uint8_t>(blob->begin(), blob->length());

if (!readers->put(source, BlobReader(span))) {
return false;
Expand Down Expand Up @@ -408,7 +415,7 @@ bool Blob::text(JSContext *cx, unsigned argc, JS::Value *vp) {
auto decoder = jsencoding::encoding_new_decoder_with_bom_removal(encoding);
MOZ_ASSERT(decoder);

auto src_len = src->size();
auto src_len = src->length();
auto dst_len = jsencoding::decoder_max_utf16_buffer_length(decoder, src_len);

JS::UniqueTwoByteChars dst(new char16_t[dst_len + 1]);
Expand All @@ -420,7 +427,7 @@ bool Blob::text(JSContext *cx, unsigned argc, JS::Value *vp) {
bool had_replacements;
auto dst_data = reinterpret_cast<uint16_t *>(dst.get());

jsencoding::decoder_decode_to_utf16(decoder, src->data(), &src_len, dst_data, &dst_len, true,
jsencoding::decoder_decode_to_utf16(decoder, src->begin(), &src_len, dst_data, &dst_len, true,
&had_replacements);

JS::RootedString str(cx, JS_NewUCString(cx, std::move(dst), dst_len));
Expand Down Expand Up @@ -474,17 +481,17 @@ bool Blob::stream_pull(JSContext *cx, JS::CallArgs args, JS::HandleObject source
return true;
}

std::vector<uint8_t> *Blob::blob(JSObject *self) {
Blob::ByteBuffer *Blob::blob(JSObject *self) {
MOZ_ASSERT(is_instance(self));
auto blob = static_cast<std::vector<uint8_t> *>(
auto blob = static_cast<ByteBuffer *>(
JS::GetReservedSlot(self, static_cast<size_t>(Blob::Slots::Data)).toPrivate());

MOZ_ASSERT(blob);
return blob;
}

size_t Blob::blob_size(JSObject *self) {
return blob(self)->size();
return blob(self)->length();
}

JSString *Blob::type(JSObject *self) {
Expand Down Expand Up @@ -515,14 +522,16 @@ bool Blob::append_value(JSContext *cx, HandleObject self, HandleValue val) {

if (Blob::is_instance(obj)) {
auto src = Blob::blob(obj);
blob->insert(blob->end(), src->begin(), src->end());
return true;
return blob->append(src->begin(), src->end());
} else if (JS_IsArrayBufferViewObject(obj) || JS::IsArrayBufferObject(obj)) {
auto span = value_to_buffer(cx, val, "Blob Parts");
if (span.has_value()) {
blob->insert(blob->end(), span->begin(), span->end());
auto src = span->data();
auto len = span->size();
return blob->append(src, src + len);
} else {
return true;
}
return true;
}
} else if (val.isString()) {
auto chars = core::encode(cx, val);
Expand All @@ -534,14 +543,13 @@ bool Blob::append_value(JSContext *cx, HandleObject self, HandleValue val) {
auto converted = convert_line_endings_to_native(chars);
auto src = converted.data();
auto len = converted.length();
blob->insert(blob->end(), src, src + len);
return blob->append(src, src + len);

} else {
auto src = chars.ptr.get();
auto len = chars.len;
blob->insert(blob->end(), src, src + len);
return blob->append(src, src + len);
}
return true;
}

// FALLBACK: if we ever get here convert, to string and call append again
Expand Down Expand Up @@ -645,13 +653,19 @@ bool Blob::init_options(JSContext *cx, HandleObject self, HandleValue initv) {
return true;
}

JSObject *Blob::create(JSContext *cx, std::unique_ptr<Blob::ByteBuffer> data, HandleString type) {
JSObject *Blob::create(JSContext *cx, UniqueChars data, size_t data_len, HandleString type) {
JSObject *self = JS_NewObjectWithGivenProto(cx, &class_, proto_obj);
if (!self) {
return nullptr;
}

SetReservedSlot(self, static_cast<uint32_t>(Slots::Data), JS::PrivateValue(data.release()));
auto blob = new ByteBuffer;
if (data != nullptr) {
// Take the ownership of given data.
blob->replaceRawBuffer(reinterpret_cast<uint8_t *>(data.release()), data_len);
}

SetReservedSlot(self, static_cast<uint32_t>(Slots::Data), JS::PrivateValue(blob));
SetReservedSlot(self, static_cast<uint32_t>(Slots::Type), JS::StringValue(type));
SetReservedSlot(self, static_cast<uint32_t>(Slots::Endings), JS::Int32Value(LineEndings::Transparent));
SetReservedSlot(self, static_cast<uint32_t>(Slots::Readers), JS::PrivateValue(new ReadersMap));
Expand All @@ -669,9 +683,9 @@ bool Blob::constructor(JSContext *cx, unsigned argc, JS::Value *vp) {
return false;
}

SetReservedSlot(self, static_cast<uint32_t>(Slots::Data), JS::PrivateValue(new std::vector<uint8_t>()));
SetReservedSlot(self, static_cast<uint32_t>(Slots::Type), JS_GetEmptyStringValue(cx));
SetReservedSlot(self, static_cast<uint32_t>(Slots::Endings), JS::Int32Value(LineEndings::Transparent));
SetReservedSlot(self, static_cast<uint32_t>(Slots::Data), JS::PrivateValue(new ByteBuffer));
SetReservedSlot(self, static_cast<uint32_t>(Slots::Readers), JS::PrivateValue(new ReadersMap));

// Walk the blob parts and append them to the blob's buffer.
Expand Down
8 changes: 4 additions & 4 deletions builtins/web/blob.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "js/AllocPolicy.h"
#include "js/GCHashTable.h"
#include "js/TypeDecls.h"
#include "js/Vector.h"

namespace builtins {
namespace web {
Expand Down Expand Up @@ -53,10 +54,9 @@ class Blob : public TraceableBuiltinImpl<Blob> {
enum Slots { Data, Type, Endings, Readers, Count };
enum LineEndings { Transparent, Native };

using ByteBuffer = std::vector<uint8_t>;
using HeapObj = Heap<JSObject *>;
using ReadersMap =
JS::GCHashMap<HeapObj, BlobReader, js::StableCellHasher<HeapObj>, js::SystemAllocPolicy>;
using ByteBuffer = js::Vector<uint8_t, 0, js::SystemAllocPolicy>;
using ReadersMap = JS::GCHashMap<HeapObj, BlobReader, js::StableCellHasher<HeapObj>, js::SystemAllocPolicy>;

static ReadersMap *readers(JSObject *self);
static ByteBuffer *blob(JSObject *self);
Expand All @@ -75,7 +75,7 @@ class Blob : public TraceableBuiltinImpl<Blob> {
static JSObject *data_to_owned_array_buffer(JSContext *cx, HandleObject self);
static JSObject *data_to_owned_array_buffer(JSContext *cx, HandleObject self, size_t offset,
size_t size, size_t *bytes_read);
static JSObject *create(JSContext *cx, std::unique_ptr<ByteBuffer> data, HandleString type);
static JSObject *create(JSContext *cx, UniqueChars data, size_t data_len, HandleString type);

static bool init_class(JSContext *cx, HandleObject global);
static bool constructor(JSContext *cx, unsigned argc, Value *vp);
Expand Down
34 changes: 33 additions & 1 deletion builtins/web/fetch/request-response.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "request-response.h"

#include "../blob.h"
#include "../streams/native-stream-source.h"
#include "../streams/transform-stream.h"
#include "../url.h"
Expand All @@ -8,6 +9,7 @@
#include "extension-api.h"
#include "fetch_event.h"
#include "host_api.h"
#include "js/String.h"
#include "picosha2.h"

#include "js/Array.h"
Expand All @@ -25,6 +27,8 @@
#include "js/experimental/TypedData.h"
#pragma clang diagnostic pop

using builtins::web::blob::Blob;

namespace builtins::web::streams {

bool NativeStreamSource::stream_is_body(JSContext *cx, JS::HandleObject stream) {
Expand Down Expand Up @@ -288,6 +292,7 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self,
// We currently support five types of body inputs:
// - byte sequence
// - buffer source
// - Blob
// - USV strings
// - URLSearchParams
// - ReadableStream
Expand All @@ -296,8 +301,24 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self,
// TODO: Support the other possible inputs to Body.

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

if (Blob::is_instance(body_obj)) {
RootedValue stream(cx);
if (!Call(cx, body_obj, "stream", HandleValueArray::empty(), &stream)) {
return false;
}

MOZ_ASSERT(stream.isObject());
JS_SetReservedSlot(self, static_cast<uint32_t>(RequestOrResponse::Slots::BodyStream), stream);

if (body_obj && JS::IsReadableStream(body_obj)) {
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)) {
return api::throw_error(cx, FetchErrors::BodyStreamUnusable);
}
Expand Down Expand Up @@ -539,6 +560,15 @@ bool RequestOrResponse::parse_body(JSContext *cx, JS::HandleObject self, JS::Uni
}
static_cast<void>(buf.release());
result.setObject(*array_buffer);
} else if constexpr (result_type == RequestOrResponse::BodyReadResult::Blob) {
JS::RootedString contentType(cx, JS_GetEmptyString(cx));
JS::RootedObject blob(cx, blob::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) {
Expand Down Expand Up @@ -1303,6 +1333,7 @@ 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("clone", Request::clone, 0, JSPROP_ENUMERATE),
Expand Down Expand Up @@ -2294,6 +2325,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_FS_END,
Expand Down
1 change: 1 addition & 0 deletions builtins/web/fetch/request-response.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class RequestOrResponse final {

enum class BodyReadResult {
ArrayBuffer,
Blob,
JSON,
Text,
};
Expand Down
Loading
Loading