Skip to content

Commit

Permalink
Implement File API
Browse files Browse the repository at this point in the history
  • Loading branch information
andreiltd committed Dec 9, 2024
1 parent 0749b09 commit 94de20c
Show file tree
Hide file tree
Showing 8 changed files with 456 additions and 2 deletions.
29 changes: 29 additions & 0 deletions builtins/web/blob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,35 @@ JSObject *Blob::create(JSContext *cx, std::unique_ptr<Blob::ByteBuffer> data, Ha
return self;
}

JSObject *Blob::create(JSContext *cx, HandleValue blobParts, HandleValue opts) {
RootedObject self(cx, JS_NewObjectWithGivenProto(cx, &class_, proto_obj));

if (!self) {
return nullptr;
}

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));

if (blobParts.isNull()) {
api::throw_error(cx, api::Errors::TypeError, "Blob.constructor", "blobParts", "be an object");
return nullptr;
}

// Walk the blob parts and append them to the blob's buffer.
if (!blobParts.isUndefined() && !init_blob_parts(cx, self, blobParts)) {
return nullptr;
}

if (!opts.isNullOrUndefined() && !init_options(cx, self, opts)) {
return nullptr;
}

return self;
}

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

Expand Down
4 changes: 3 additions & 1 deletion builtins/web/blob.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ 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 JSObject *create(JSContext *cx, HandleValue blobParts, HandleValue opts);

static bool init_class(JSContext *cx, HandleObject global);
static bool constructor(JSContext *cx, unsigned argc, Value *vp);
Expand Down
226 changes: 226 additions & 0 deletions builtins/web/file.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
#include "file.h"
#include "blob.h"
#include "js/TypeDecls.h"
#include "mozilla/Assertions.h"

namespace builtins {
namespace web {
namespace file {

bool maybe_file_instance(JSContext *cx, HandleValue value, MutableHandleValue instance) {
instance.setNull();

JS::ForOfIterator it(cx);
if (!it.init(value, JS::ForOfIterator::AllowNonIterable)) {
return false;
}

bool is_iterable = value.isObject() && it.valueIsIterable();
bool done;

if (is_iterable) {
JS::RootedValue item(cx);

if (!it.next(&item, &done)) {
return false;
}
if (item.isObject() && File::is_instance(&item.toObject())) {
instance.setObject(item.toObject());
}
}

return true;
}

using blob::Blob;

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

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

const JSFunctionSpec File::methods[] = {
JS_FN("arrayBuffer", File::arrayBuffer, 0, JSPROP_ENUMERATE),
JS_FN("bytes", File::bytes, 0, JSPROP_ENUMERATE),
JS_FN("slice", File::slice, 0, JSPROP_ENUMERATE),
JS_FN("stream", File::stream, 0, JSPROP_ENUMERATE),
JS_FN("text", File::text, 0, JSPROP_ENUMERATE),
JS_FS_END,
};

const JSPropertySpec File::properties[] = {
JS_PSG("size", File::size_get, JSPROP_ENUMERATE),
JS_PSG("type", File::type_get, JSPROP_ENUMERATE),
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,
};

#define DEFINE_BLOB_DELEGATE(name) \
bool File::name(JSContext *cx, unsigned argc, JS::Value *vp) { \
METHOD_HEADER(0) \
RootedObject blob(cx, File::blob(self)); \
return JS::Call(cx, blob, #name, JS::HandleValueArray(args), args.rval()); \
}

DEFINE_BLOB_DELEGATE(arrayBuffer)
DEFINE_BLOB_DELEGATE(bytes)
DEFINE_BLOB_DELEGATE(slice)
DEFINE_BLOB_DELEGATE(stream)
DEFINE_BLOB_DELEGATE(text)

bool File::size_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, "size get", "File");
}

RootedObject blob(cx, File::blob(self));
args.rval().setNumber(Blob::blob_size(blob));
return true;
}

bool File::type_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, "type get", "File");
}

RootedObject blob(cx, File::blob(self));
args.rval().setString(Blob::type(blob));
return true;
}

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>(File::Slots::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>(File::Slots::LastModified)).toInt32();
args.rval().setNumber(lastModified);
return true;
}

bool File::init_last_modified(JSContext *cx, HandleObject self, HandleValue initv) {
bool has_last_modified = false;

JS::RootedValue init_val(cx, initv);
JS::RootedObject opts(cx, init_val.toObjectOrNull());

if (!opts) {
return true;
}

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()) {
SetReservedSlot(self, static_cast<uint32_t>(Slots::LastModified), ts);
}
}

return true;
}

JSObject *File::blob(JSObject *self) {
MOZ_ASSERT(is_instance(self));
auto blob = &JS::GetReservedSlot(self, static_cast<size_t>(Blob::Slots::Data)).toObject();

MOZ_ASSERT(blob);
return blob;
}

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, JS_NewObjectForConstructor(cx, &class_, args));
if (!self) {
return false;
}

RootedValue other(cx);
if (!maybe_file_instance(cx, fileBits, &other)) {
return false;
}

if (!other.isNull()) {
MOZ_ASSERT(other.isObject());
RootedObject other_blob(cx, File::blob(&other.toObject()));
RootedValue blob_copy(cx);

if (!Call(cx, other_blob, "slice", HandleValueArray::empty(), &blob_copy)) {
return false;
}

SetReservedSlot(self, static_cast<uint32_t>(Slots::Blob), blob_copy);
} else {
RootedObject blob(cx, Blob::create(cx, fileBits, opts));
if (!blob) {
return false;
}
SetReservedSlot(self, static_cast<uint32_t>(Slots::Blob), JS::ObjectValue(*blob));
}


RootedString name(cx, JS::ToString(cx, fileName));
if (!name) {
return false;
}
SetReservedSlot(self, static_cast<uint32_t>(Slots::Name), JS::StringValue(name));

if (!opts.isNullOrUndefined()) {
if (!init_last_modified(cx, self, opts)) {
return false;
}
} else {
auto now = std::chrono::system_clock::now();
auto ms_since_epoch = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
SetReservedSlot(self, static_cast<uint32_t>(Slots::LastModified), JS::Int32Value(ms_since_epoch));
}

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

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

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

} // namespace file
} // namespace web
} // namespace builtins
47 changes: 47 additions & 0 deletions builtins/web/file.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#ifndef BUILTINS_WEB_FILE_H
#define BUILTINS_WEB_FILE_H

#include "builtin.h"

namespace builtins {
namespace web {
namespace file {

class File: public BuiltinImpl<File> {
static bool arrayBuffer(JSContext *cx, unsigned argc, JS::Value *vp);
static bool bytes(JSContext *cx, unsigned argc, JS::Value *vp);
static bool slice(JSContext *cx, unsigned argc, JS::Value *vp);
static bool stream(JSContext *cx, unsigned argc, JS::Value *vp);
static bool text(JSContext *cx, unsigned argc, JS::Value *vp);

static bool size_get(JSContext *cx, unsigned argc, JS::Value *vp);
static bool type_get(JSContext *cx, unsigned argc, JS::Value *vp);
static bool name_get(JSContext *cx, unsigned argc, JS::Value *vp);
static bool lastModified_get(JSContext *cx, unsigned argc, JS::Value *vp);

static bool init_last_modified(JSContext *cx, HandleObject self, HandleValue initv);

public:
enum Slots { Blob, Name, LastModified, Count };

static constexpr const char *class_name = "File";
static constexpr unsigned ctor_length = 2;

static const JSFunctionSpec static_methods[];
static const JSPropertySpec static_properties[];
static const JSFunctionSpec methods[];
static const JSPropertySpec properties[];

static JSObject *blob(JSObject *self);

static bool init_class(JSContext *cx, HandleObject global);
static bool constructor(JSContext *cx, unsigned argc, Value *vp);
};

bool install(api::Engine *engine);

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

#endif // BUILTINS_WEB_FILE_H
1 change: 1 addition & 0 deletions cmake/builtins.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ add_builtin(builtins/web/queue-microtask.cpp)
add_builtin(builtins/web/structured-clone.cpp)
add_builtin(builtins/web/base64.cpp)
add_builtin(builtins/web/blob.cpp)
add_builtin(builtins/web/file.cpp)
add_builtin(
builtins::web::dom_exception
SRC
Expand Down
Loading

0 comments on commit 94de20c

Please sign in to comment.