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 File interface #181

Merged
merged 13 commits into from
Jan 10, 2025
48 changes: 31 additions & 17 deletions builtins/web/blob.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#include "blob.h"
#include "file.h"
#include "builtin.h"
#include "encode.h"
#include "extension-api.h"
#include "js/UniquePtr.h"
#include "rust-encoding.h"
#include "streams/native-stream-source.h"

#include "mozilla/UniquePtr.h"
#include "js/UniquePtr.h"
#include "js/ArrayBuffer.h"
#include "js/Conversions.h"
#include "js/experimental/TypedData.h"
Expand Down Expand Up @@ -152,6 +152,7 @@ namespace web {
namespace blob {

using js::Vector;
using file::File;

#define DEFINE_BLOB_METHOD(name) \
bool Blob::name(JSContext *cx, unsigned argc, JS::Value *vp) { \
Expand Down Expand Up @@ -520,6 +521,15 @@ Blob::LineEndings Blob::line_endings(JSObject *self) {
JS::GetReservedSlot(self, static_cast<size_t>(Blob::Slots::Endings)).toInt32());
}

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

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

bool Blob::append_value(JSContext *cx, HandleObject self, HandleValue val) {
auto blob = Blob::blob(self);

Expand Down Expand Up @@ -675,28 +685,14 @@ 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;
}

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

RootedValue blobParts(cx, args.get(0));
RootedValue opts(cx, args.get(1));
RootedObject self(cx, JS_NewObjectForConstructor(cx, &class_, args));

if (!self) {
return false;
}

bool Blob::init(JSContext *cx, HandleObject self, HandleValue blobParts, HandleValue opts) {
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));
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 All @@ -711,6 +707,24 @@ bool Blob::constructor(JSContext *cx, unsigned argc, JS::Value *vp) {
return false;
}

return true;
}

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

RootedValue blobParts(cx, args.get(0));
RootedValue opts(cx, args.get(1));
RootedObject self(cx, JS_NewObjectForConstructor(cx, &class_, args));

if (!self) {
return false;
}

if (!init(cx, self, blobParts, opts)) {
return false;
}

args.rval().setObject(*self);
return true;
}
Expand Down
6 changes: 5 additions & 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, Reserved1, Reserved2, Count };
enum Slots { Data, Type, Endings, Readers, Count };
enum LineEndings { Transparent, Native };

using HeapObj = Heap<JSObject *>;
Expand All @@ -69,9 +69,13 @@ class Blob : public TraceableBuiltinImpl<Blob> {
static size_t blob_size(JSObject *self);
static JSString *type(JSObject *self);
static LineEndings line_endings(JSObject *self);

static bool is_instance(const JSObject *obj);
static bool is_instance(const Value val);
static bool append_value(JSContext *cx, HandleObject self, HandleValue val);
static bool init_blob_parts(JSContext *cx, HandleObject self, HandleValue iterable);
static bool init_options(JSContext *cx, HandleObject self, HandleValue opts);
static bool init(JSContext *cx, HandleObject self, HandleValue blobParts, HandleValue opts);

static bool stream_cancel(JSContext *cx, JS::CallArgs args, JS::HandleObject stream,
JS::HandleObject owner, JS::HandleValue reason);
Expand Down
74 changes: 20 additions & 54 deletions builtins/web/file.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "file.h"
#include "blob.h"

#include "host_api.h"
#include "js/CallAndConstruct.h"
#include "js/CallArgs.h"
#include "js/TypeDecls.h"
Expand Down Expand Up @@ -35,10 +36,10 @@ bool init_last_modified(JSContext *cx, HandleValue initv, MutableHandleValue rva
}
}

// 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();
// If the last modification date and time are not known, the attribute must return the
// current date and time representing the number of milliseconds since the Unix Epoch;
auto ns_since_epoch = host_api::WallClock::now();
auto ms_since_epoch = ns_since_epoch / 1000000ULL;

rval.setInt32(ms_since_epoch);
return true;
Expand All @@ -52,11 +53,6 @@ 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 @@ -83,7 +79,7 @@ bool File::name_get(JSContext *cx, unsigned argc, JS::Value *vp) {
return api::throw_error(cx, api::Errors::WrongReceiver, "name get", "File");
}

auto name = JS::GetReservedSlot(self, static_cast<size_t>(ParentSlots::Name)).toString();
auto name = JS::GetReservedSlot(self, static_cast<size_t>(Slots::Name)).toString();
args.rval().setString(name);
return true;
}
Expand All @@ -96,55 +92,21 @@ bool File::lastModified_get(JSContext *cx, unsigned argc, JS::Value *vp) {
}

auto lastModified =
JS::GetReservedSlot(self, static_cast<size_t>(ParentSlots::LastModified)).toInt32();
JS::GetReservedSlot(self, static_cast<size_t>(Slots::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);

bool File::init(JSContext *cx, HandleObject self, HandleValue fileBits, HandleValue fileName, HandleValue opts) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a doc comment with a link to the algorithm implemented here? See some of the other builtins for how I usually format those.

// 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;
if (!Blob::init(cx, self, fileBits, opts)) {
return false;
}

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

// 3. Process `FilePropertyBag` dictionary argument by running the following substeps:
Expand All @@ -154,7 +116,7 @@ JSObject *File::create(JSContext *cx, HandleValue fileBits, HandleValue fileName
// milliseconds since the Unix Epoch.
RootedValue lastModified(cx);
if (!init_last_modified(cx, opts, &lastModified)) {
return nullptr;
return false;
}

// Return a new File object F such that:
Expand All @@ -166,10 +128,10 @@ JSObject *File::create(JSContext *cx, HandleValue fileBits, HandleValue fileName
//
// 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);
SetReservedSlot(self, static_cast<uint32_t>(Slots::Name), JS::StringValue(name));
SetReservedSlot(self, static_cast<uint32_t>(Slots::LastModified), lastModified);
andreiltd marked this conversation as resolved.
Show resolved Hide resolved

return self;
return true;
}

bool File::constructor(JSContext *cx, unsigned argc, JS::Value *vp) {
Expand All @@ -179,11 +141,15 @@ bool File::constructor(JSContext *cx, unsigned argc, JS::Value *vp) {
RootedValue fileName(cx, args.get(1));
RootedValue opts(cx, args.get(2));

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

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

args.rval().setObject(*self);
return true;
}
Expand Down
9 changes: 4 additions & 5 deletions builtins/web/file.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef BUILTINS_WEB_FILE_H
#define BUILTINS_WEB_FILE_H

#include "blob.h"
#include "builtin.h"

namespace builtins {
Expand All @@ -11,7 +12,8 @@ class File : public BuiltinImpl<File> {
static bool lastModified_get(JSContext *cx, unsigned argc, JS::Value *vp);

public:
enum Slots { Count };
static constexpr int ParentSlots = blob::Blob::Slots::Count;
enum Slots { Name = ParentSlots, LastModified, Count };

static constexpr const char *class_name = "File";
static constexpr unsigned ctor_length = 2;
Expand All @@ -21,10 +23,7 @@ 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 *create(JSContext *cx, HandleValue fileBits, HandleValue fileName, HandleValue opts);
static bool init(JSContext *cx, HandleObject self, HandleValue fileBits, HandleValue fileName, HandleValue opts);
static bool init_class(JSContext *cx, HandleObject global);
static bool constructor(JSContext *cx, unsigned argc, Value *vp);
};
Expand Down
16 changes: 16 additions & 0 deletions host-apis/wasi-0.2.0/host_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,22 @@ void MonotonicClock::unsubscribe(const int32_t handle_id) {
wasi_io_poll_pollable_drop_own(own_pollable_t{handle_id});
}

uint64_t WallClock::now() {
andreiltd marked this conversation as resolved.
Show resolved Hide resolved
wasi_clocks_wall_clock_datetime_t time_now;
wasi_clocks_wall_clock_now(&time_now);

uint64_t total_nanoseconds = time_now.seconds * 1000000000ULL + time_now.nanoseconds;
andreiltd marked this conversation as resolved.
Show resolved Hide resolved
return total_nanoseconds;
}

uint64_t WallClock::resolution() {
wasi_clocks_wall_clock_datetime_t resolution;
wasi_clocks_wall_clock_resolution(&resolution);

uint64_t total_nanoseconds = resolution.seconds * 1000000000ULL + resolution.nanoseconds;
return total_nanoseconds;
}

vector<std::string> environment_get_arguments() {
bindings_list_string_t raw_args = {};
wasi_cli_environment_get_arguments(&raw_args);
Expand Down
8 changes: 8 additions & 0 deletions include/host_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,14 @@ class MonotonicClock final {
static void unsubscribe(PollableHandle handle_id);
};

class WallClock final {
public:
WallClock() = delete;

static uint64_t now();
static uint64_t resolution();
};

vector<std::string> environment_get_arguments();

} // namespace host_api
Expand Down
Loading