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 implementation of FormData builtin #191

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
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
50 changes: 31 additions & 19 deletions builtins/web/blob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,18 @@ namespace blob {

using js::Vector;

#define DEFINE_BLOB_METHOD(name) \
bool Blob::name(JSContext *cx, unsigned argc, JS::Value *vp) { \
METHOD_HEADER(0) \
return name(cx, self, args.rval()); \
}

#define DEFINE_BLOB_METHOD_W_ARGS(name) \
bool Blob::name(JSContext *cx, unsigned argc, JS::Value *vp) { \
METHOD_HEADER(0) \
return name(cx, self, args, args.rval()); \
}

const JSFunctionSpec Blob::static_methods[] = {
JS_FS_END,
};
Expand Down Expand Up @@ -269,15 +281,19 @@ JSObject *Blob::data_to_owned_array_buffer(JSContext *cx, HandleObject self, siz
return array_buffer;
}

bool Blob::arrayBuffer(JSContext *cx, unsigned argc, JS::Value *vp) {
METHOD_HEADER(0)
DEFINE_BLOB_METHOD(arrayBuffer)
DEFINE_BLOB_METHOD(bytes)
DEFINE_BLOB_METHOD(stream)
DEFINE_BLOB_METHOD(text)
DEFINE_BLOB_METHOD_W_ARGS(slice)

bool Blob::arrayBuffer(JSContext *cx, HandleObject self, MutableHandleValue rval) {
JS::RootedObject promise(cx, JS::NewPromiseObject(cx, nullptr));
if (!promise) {
return false;
}

args.rval().setObject(*promise);
rval.setObject(*promise);

auto buffer = data_to_owned_array_buffer(cx, self);
if (!buffer) {
Expand All @@ -291,15 +307,13 @@ bool Blob::arrayBuffer(JSContext *cx, unsigned argc, JS::Value *vp) {
return true;
}

bool Blob::bytes(JSContext *cx, unsigned argc, JS::Value *vp) {
METHOD_HEADER(0)

bool Blob::bytes(JSContext *cx, HandleObject self, MutableHandleValue rval) {
JS::RootedObject promise(cx, JS::NewPromiseObject(cx, nullptr));
if (!promise) {
return false;
}

args.rval().setObject(*promise);
rval.setObject(*promise);

JS::RootedObject buffer(cx, data_to_owned_array_buffer(cx, self));
if (!buffer) {
Expand All @@ -319,9 +333,7 @@ bool Blob::bytes(JSContext *cx, unsigned argc, JS::Value *vp) {
return true;
}

bool Blob::slice(JSContext *cx, unsigned argc, JS::Value *vp) {
METHOD_HEADER(0)

bool Blob::slice(JSContext *cx, HandleObject self, const CallArgs &args, MutableHandleValue rval) {
auto src = Blob::blob(self);
int64_t size = src->length();
int64_t start = 0;
Expand Down Expand Up @@ -366,13 +378,11 @@ bool Blob::slice(JSContext *cx, unsigned argc, JS::Value *vp) {
return false;
}

args.rval().setObject(*new_blob);
rval.setObject(*new_blob);
return true;
}

bool Blob::stream(JSContext *cx, unsigned argc, JS::Value *vp) {
METHOD_HEADER(0)

bool Blob::stream(JSContext *cx, HandleObject self, MutableHandleValue rval) {
auto native_stream = streams::NativeStreamSource::create(cx, self, JS::UndefinedHandleValue,
stream_pull, stream_cancel);

Expand All @@ -394,19 +404,17 @@ bool Blob::stream(JSContext *cx, unsigned argc, JS::Value *vp) {
return false;
}

args.rval().setObject(*stream);
rval.setObject(*stream);
return true;
}

bool Blob::text(JSContext *cx, unsigned argc, JS::Value *vp) {
METHOD_HEADER(0)

bool Blob::text(JSContext *cx, HandleObject self, MutableHandleValue rval) {
JS::RootedObject promise(cx, JS::NewPromiseObject(cx, nullptr));
if (!promise) {
return false;
}

args.rval().setObject(*promise);
rval.setObject(*promise);

auto src = Blob::blob(self);
auto encoding = const_cast<jsencoding::Encoding *>(jsencoding::encoding_for_label_no_replacement(
Expand Down Expand Up @@ -669,6 +677,8 @@ JSObject *Blob::create(JSContext *cx, UniqueChars data, size_t data_len, HandleS
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));
SetReservedSlot(self, static_cast<uint32_t>(Slots::Reserved1), JS::NullValue());
SetReservedSlot(self, static_cast<uint32_t>(Slots::Reserved2), JS::NullValue());
return self;
}

Expand All @@ -687,6 +697,8 @@ bool Blob::constructor(JSContext *cx, unsigned argc, JS::Value *vp) {
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));
SetReservedSlot(self, static_cast<uint32_t>(Slots::Reserved1), JS::NullValue());
SetReservedSlot(self, static_cast<uint32_t>(Slots::Reserved2), JS::NullValue());

// Walk the blob parts and append them to the blob's buffer.
if (blobParts.isNull()) {
Expand Down
9 changes: 8 additions & 1 deletion builtins/web/blob.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,19 @@ class Blob : public TraceableBuiltinImpl<Blob> {
static const JSPropertySpec properties[];

static constexpr unsigned ctor_length = 0;
enum Slots { Data, Type, Endings, Readers, Count };
enum Slots { Data, Type, Endings, Readers, Reserved1, Reserved2, Count };
enum LineEndings { Transparent, Native };

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

static bool arrayBuffer(JSContext *cx, HandleObject self, MutableHandleValue rval);
static bool bytes(JSContext *cx, HandleObject self, MutableHandleValue rval);
static bool stream(JSContext *cx, HandleObject self, MutableHandleValue rval);
static bool text(JSContext *cx, HandleObject self, MutableHandleValue rval);
static bool slice(JSContext *cx, HandleObject self, const CallArgs &args, MutableHandleValue rval);

static ReadersMap *readers(JSObject *self);
static ByteBuffer *blob(JSObject *self);
static size_t blob_size(JSObject *self);
Expand All @@ -75,6 +81,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, UniqueChars data, size_t data_len, HandleString type);

static bool init_class(JSContext *cx, HandleObject global);
Expand Down
2 changes: 1 addition & 1 deletion builtins/web/fetch/request-response.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self,

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

Expand Down
199 changes: 199 additions & 0 deletions builtins/web/file.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
#include "file.h"
#include "blob.h"

#include "js/CallAndConstruct.h"
#include "js/CallArgs.h"
#include "js/TypeDecls.h"

#include "mozilla/Assertions.h"

namespace {

bool init_last_modified(JSContext *cx, HandleValue initv, MutableHandleValue rval) {
JS::RootedValue init_val(cx, initv);

if (!init_val.isNullOrUndefined()) {
JS::RootedObject opts(cx, init_val.toObjectOrNull());

if (opts) {
bool has_last_modified = false;
if (!JS_HasProperty(cx, opts, "lastModified", &has_last_modified)) {
return false;
}

if (has_last_modified) {
JS::RootedValue ts(cx);
if (!JS_GetProperty(cx, opts, "lastModified", &ts)) {
return false;
}

if (ts.isNumber()) {
rval.set(ts);
return true;
}
}
}
}

// If `lastModified` is not provided, set d to the current date and time.
auto now = std::chrono::system_clock::now();
auto ms_since_epoch =
std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();

rval.setInt32(ms_since_epoch);
return true;
}

} // namespace

namespace builtins {
namespace web {
namespace file {

using blob::Blob;

enum ParentSlots {
Name = Blob::Slots::Reserved1,
LastModified = Blob::Slots::Reserved2,
};

const JSFunctionSpec File::static_methods[] = {
JS_FS_END,
};

const JSPropertySpec File::static_properties[] = {
JS_PS_END,
};

const JSFunctionSpec File::methods[] = {
JS_FS_END,
};

const JSPropertySpec File::properties[] = {
JS_PSG("name", File::name_get, JSPROP_ENUMERATE),
JS_PSG("lastModified", File::lastModified_get, JSPROP_ENUMERATE),
JS_STRING_SYM_PS(toStringTag, "File", JSPROP_READONLY),
JS_PS_END,
};

bool File::name_get(JSContext *cx, unsigned argc, JS::Value *vp) {
METHOD_HEADER(0);
// TODO: Change this class so that its prototype isn't an instance of the class
if (self == proto_obj) {
return api::throw_error(cx, api::Errors::WrongReceiver, "name get", "File");
}

auto name = JS::GetReservedSlot(self, static_cast<size_t>(ParentSlots::Name)).toString();
args.rval().setString(name);
return true;
}

bool File::lastModified_get(JSContext *cx, unsigned argc, JS::Value *vp) {
METHOD_HEADER(0);
// TODO: Change this class so that its prototype isn't an instance of the class
if (self == proto_obj) {
return api::throw_error(cx, api::Errors::WrongReceiver, "lastModified get", "File");
}

auto lastModified =
JS::GetReservedSlot(self, static_cast<size_t>(ParentSlots::LastModified)).toInt32();
args.rval().setNumber(lastModified);
return true;
}

bool File::is_instance(const JSObject *obj) {
return obj != nullptr
&& JS::GetClass(obj) == &Blob::class_
&& !JS::GetReservedSlot(
(JSObject *)obj,
static_cast<size_t>(ParentSlots::Name)).isNullOrUndefined();
}

bool File::is_instance(const Value val) {
return val.isObject() && is_instance(&val.toObject());
}

JSObject *File::create(JSContext *cx, HandleValue fileBits, HandleValue fileName, HandleValue opts) {
RootedObject blob_ctor(cx, JS_GetConstructor(cx, Blob::proto_obj));
if (!blob_ctor) {
return nullptr;
}

RootedObject this_ctor(cx, JS_GetConstructor(cx, File::proto_obj));
if (!this_ctor) {
return nullptr;
}

MOZ_ASSERT(JS::IsConstructor(blob_ctor));
MOZ_ASSERT(JS::IsConstructor(this_ctor));

JS::RootedValueArray<2> blob_args(cx);
blob_args[0].set(fileBits);
blob_args[1].set(opts);

// 1. Let bytes be the result of processing blob parts given fileBits and options.
//
// We call the Blob constructor on `self` object to initialize it as a Blob.
// We pass `fileBits` and `options` to Blob constructor.
RootedValue blob_ctor_val(cx, JS::ObjectValue(*blob_ctor));
RootedObject self(cx);
if (!JS::Construct(cx, blob_ctor_val, this_ctor, blob_args, &self)) {
return nullptr;
}

// 2. Let n be the fileName argument to the constructor.
RootedString name(cx, JS::ToString(cx, fileName));
if (!name) {
return nullptr;
}

// 3. Process `FilePropertyBag` dictionary argument by running the following substeps:
// 1. and 2 - the steps for processing a `type` member are ensured by Blob implementation.
// 3. If the `lastModified` member is provided, let d be set to the lastModified dictionary
// member. If it is not provided, set d to the current date and time represented as the number of
// milliseconds since the Unix Epoch.
RootedValue lastModified(cx);
if (!init_last_modified(cx, opts, &lastModified)) {
return nullptr;
}

// Return a new File object F such that:
// 2. F refers to the bytes byte sequence.
// 3. F.size is set to the number of total bytes in bytes.
// 4. F.name is set to n.
// 5. F.type is set to t.
// 6. F.lastModified is set to d.
//
// Steps 2, 3 and 5 are handled by Blob. We extend the Blob by adding a `name`
// and the `lastModified` properties.
SetReservedSlot(self, static_cast<uint32_t>(ParentSlots::Name), JS::StringValue(name));
SetReservedSlot(self, static_cast<uint32_t>(ParentSlots::LastModified), lastModified);

return self;
}

bool File::constructor(JSContext *cx, unsigned argc, JS::Value *vp) {
CTOR_HEADER("File", 2);

RootedValue fileBits(cx, args.get(0));
RootedValue fileName(cx, args.get(1));
RootedValue opts(cx, args.get(2));

RootedObject self(cx, create(cx, fileBits, fileName, opts));
if (!self) {
return false;
}

args.rval().setObject(*self);
return true;
}

bool File::init_class(JSContext *cx, JS::HandleObject global) {
return init_class_impl(cx, global, Blob::proto_obj);
}

bool install(api::Engine *engine) { return File::init_class(engine->cx(), engine->global()); }

} // namespace file
} // namespace web
} // namespace builtins
Loading
Loading