Skip to content

Commit

Permalink
Refactor File builtin
Browse files Browse the repository at this point in the history
  • Loading branch information
andreiltd committed Dec 12, 2024
1 parent 923eb3d commit 537903d
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 143 deletions.
2 changes: 1 addition & 1 deletion builtins/web/blob.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ 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, Reserved2, Reserved1, Count };
enum LineEndings { Transparent, Native };

using HeapObj = Heap<JSObject *>;
Expand Down
224 changes: 94 additions & 130 deletions builtins/web/file.cpp
Original file line number Diff line number Diff line change
@@ -1,40 +1,62 @@
#include "file.h"
#include "blob.h"

#include "js/CallAndConstruct.h"
#include "js/CallArgs.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;
}
#include "mozilla/Assertions.h"

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

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

if (!it.next(&item, &done)) {
return false;
}
if (item.isObject() && File::is_instance(&item.toObject())) {
instance.setObject(item.toObject());
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,
};
Expand All @@ -44,73 +66,24 @@ const JSPropertySpec File::static_properties[] = {
};

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 Blob::name(cx, blob, args.rval()); \
}

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

bool File::slice(JSContext *cx, unsigned argc, JS::Value *vp) {
METHOD_HEADER(0)
RootedObject blob(cx, File::blob(self));
return Blob::slice(cx, blob, args, args.rval());
}

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();
auto name = JS::GetReservedSlot(self, static_cast<size_t>(ParentSlots::Name)).toString();
args.rval().setString(name);
return true;
}
Expand All @@ -122,39 +95,11 @@ bool File::lastModified_get(JSContext *cx, unsigned argc, JS::Value *vp) {
return api::throw_error(cx, api::Errors::WrongReceiver, "lastModified get", "File");
}

auto lastModified = JS::GetReservedSlot(self, static_cast<size_t>(File::Slots::LastModified)).toInt32();
auto lastModified = JS::GetReservedSlot(self, static_cast<size_t>(ParentSlots::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();
Expand All @@ -163,65 +108,84 @@ JSObject *File::blob(JSObject *self) {
return blob;
}

bool File::is_instance(const JSObject *obj) {
return obj != nullptr
&& (JS::GetClass(obj) == &class_ || JS::GetClass(obj) == &Blob::class_);
}

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

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) {
RootedObject blob_ctor(cx, JS_GetConstructor(cx, Blob::proto_obj));
if (!blob_ctor) {
return false;
}

RootedValue other(cx);
if (!maybe_file_instance(cx, fileBits, &other)) {
RootedObject this_ctor(cx, JS_GetConstructor(cx, File::proto_obj));
if (!this_ctor) {
return false;
}

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

// Calling slice with no arguments copies the entire blob's content.
if (!Blob::slice(cx, other_blob, CallArgs(), &blob_copy)) {
return false;
}
JS::RootedValueArray<2> blob_args(cx);
blob_args[0].set(fileBits);
blob_args[1].set(opts);

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

// 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 false;
}

// 2. Let n be the fileName argument to the constructor.
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));
// 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 false;
}

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

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

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

bool install(api::Engine *engine) {
Expand Down
16 changes: 4 additions & 12 deletions builtins/web/file.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,12 @@
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 };
enum Slots { Count };

static constexpr const char *class_name = "File";
static constexpr unsigned ctor_length = 2;
Expand All @@ -32,6 +21,9 @@ class File: public BuiltinImpl<File> {
static const JSFunctionSpec methods[];
static const JSPropertySpec properties[];

static bool is_instance(const JSObject *obj);
static bool is_instance(const Value val);

static JSObject *blob(JSObject *self);

static bool init_class(JSContext *cx, HandleObject global);
Expand Down

0 comments on commit 537903d

Please sign in to comment.