diff --git a/builtins/web/blob.cpp b/builtins/web/blob.cpp index 360b94e0..aa5b35ae 100644 --- a/builtins/web/blob.cpp +++ b/builtins/web/blob.cpp @@ -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, }; @@ -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) { @@ -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) { @@ -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; @@ -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); @@ -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_for_label_no_replacement( @@ -669,6 +677,8 @@ JSObject *Blob::create(JSContext *cx, UniqueChars data, size_t data_len, HandleS SetReservedSlot(self, static_cast(Slots::Type), JS::StringValue(type)); SetReservedSlot(self, static_cast(Slots::Endings), JS::Int32Value(LineEndings::Transparent)); SetReservedSlot(self, static_cast(Slots::Readers), JS::PrivateValue(new ReadersMap)); + SetReservedSlot(self, static_cast(Slots::Reserved1), JS::NullValue()); + SetReservedSlot(self, static_cast(Slots::Reserved2), JS::NullValue()); return self; } @@ -687,6 +697,8 @@ bool Blob::constructor(JSContext *cx, unsigned argc, JS::Value *vp) { SetReservedSlot(self, static_cast(Slots::Endings), JS::Int32Value(LineEndings::Transparent)); SetReservedSlot(self, static_cast(Slots::Data), JS::PrivateValue(new ByteBuffer)); SetReservedSlot(self, static_cast(Slots::Readers), JS::PrivateValue(new ReadersMap)); + SetReservedSlot(self, static_cast(Slots::Reserved1), JS::NullValue()); + SetReservedSlot(self, static_cast(Slots::Reserved2), JS::NullValue()); // Walk the blob parts and append them to the blob's buffer. if (blobParts.isNull()) { diff --git a/builtins/web/blob.h b/builtins/web/blob.h index 8efe2d25..27c36943 100644 --- a/builtins/web/blob.h +++ b/builtins/web/blob.h @@ -51,13 +51,19 @@ class Blob : public TraceableBuiltinImpl { 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; using ByteBuffer = js::Vector; using ReadersMap = JS::GCHashMap, 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); @@ -75,6 +81,7 @@ class Blob : public TraceableBuiltinImpl { 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); diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index 855b9aa5..9a4ccc1a 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -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; } diff --git a/builtins/web/file.cpp b/builtins/web/file.cpp new file mode 100644 index 00000000..0b36b366 --- /dev/null +++ b/builtins/web/file.cpp @@ -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(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(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(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(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(ParentSlots::Name), JS::StringValue(name)); + SetReservedSlot(self, static_cast(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 diff --git a/builtins/web/file.h b/builtins/web/file.h new file mode 100644 index 00000000..05e7216b --- /dev/null +++ b/builtins/web/file.h @@ -0,0 +1,38 @@ +#ifndef BUILTINS_WEB_FILE_H +#define BUILTINS_WEB_FILE_H + +#include "builtin.h" + +namespace builtins { +namespace web { +namespace file { +class File : public BuiltinImpl { + static bool name_get(JSContext *cx, unsigned argc, JS::Value *vp); + static bool lastModified_get(JSContext *cx, unsigned argc, JS::Value *vp); + +public: + enum Slots { 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 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_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 diff --git a/builtins/web/form-data.cpp b/builtins/web/form-data.cpp new file mode 100644 index 00000000..d1d1dfcf --- /dev/null +++ b/builtins/web/form-data.cpp @@ -0,0 +1,287 @@ +#include "blob.h" +#include "builtin.h" +#include "encode.h" +#include "file.h" +#include "js/TypeDecls.h" +#include "js/Value.h" +#include "form-data.h" + +namespace { + +using builtins::web::form_data::FormDataEntry; + +FormDataEntry entry_from_kv_pair(std::string name, HandleValue value) { + FormDataEntry entry; + entry.name = std::move(name); + entry.value = value; + + return entry; +} + +} // namespace + +namespace builtins { +namespace web { +namespace form_data { + +using blob::Blob; +using file::File; + +const JSFunctionSpec FormData::static_methods[] = { + JS_FS_END, +}; + +const JSPropertySpec FormData::static_properties[] = { + JS_PS_END, +}; + +const JSFunctionSpec FormData::methods[] = { + JS_FN("append", append, 0, JSPROP_ENUMERATE), + JS_FN("delete", remove, 0, JSPROP_ENUMERATE), + JS_FN("get", get, 0, JSPROP_ENUMERATE), + JS_FN("getAll", FormData::getAll, 0, JSPROP_ENUMERATE), + JS_FN("has", FormData::has, 0, JSPROP_ENUMERATE), + JS_FN("set", FormData::set, 0, JSPROP_ENUMERATE), + JS_FS_END, +}; + +const JSPropertySpec FormData::properties[] = { + JS_PS_END, +}; + +FormData::EntryList* FormData::entry_list(JSObject *self) { + MOZ_ASSERT(is_instance(self)); + + auto entries = static_cast( + JS::GetReservedSlot(self, static_cast(FormData::Slots::Entries)).toPrivate()); + + MOZ_ASSERT(entries); + return entries; +} + +bool FormData::append(JSContext *cx, unsigned argc, JS::Value *vp) { + METHOD_HEADER(2) + + RootedValue name(cx, args.get(0)); + RootedValue value(cx, args.get(1)); + RootedValue filename(cx, args.get(2)); + + if (!name.isString()) { + return false; + } + + auto chars = core::encode(cx, name); + if (!chars) { + return false; + } + + std::string name_str(chars.ptr.get(), chars.len); + auto entries = entry_list(self); + + switch(args.length()) { + case 2: { + if (!value.isString()) { + return false; + } + + auto entry = entry_from_kv_pair(std::move(name_str), value); + entries->append(entry); + break; + } + case 3: { + if (!value.isObject()) { + return false; + } + + RootedObject obj(cx, &value.toObject()); + + if (File::is_instance(obj)) { + auto entry = entry_from_kv_pair(std::move(name_str), value); + entries->append(entry); + } else if (Blob::is_instance(obj)) { + HandleValueArray arr = HandleValueArray(value); + RootedObject file_bits(cx, NewArrayObject(cx, arr)); + if (!file_bits) { + return false; + } + + RootedValue file_bits_val(cx, JS::ObjectValue(*file_bits)); + RootedValue opts_val(cx, JS::NullValue()); + RootedValue filename_val(cx); + + if (filename.isNullOrUndefined()) { + RootedString empty(cx, JS_NewStringCopyZ(cx, "blob")); + if (!empty) { + return false; + } + + RootedValue empty_val(cx, JS::StringValue(empty)); + filename_val = empty_val; + } else { + filename_val = filename; + } + + RootedObject file(cx, File::create(cx, file_bits_val, filename_val, opts_val)); + if (!file) { + return false; + } + + RootedValue file_val(cx, JS::ObjectValue(*file)); + auto entry = entry_from_kv_pair(std::move(name_str), file_val); + entries->append(entry); + } else { + return false; + } + + break; + } + default: + return false; + } + + return true; +} + +bool FormData::remove(JSContext *cx, unsigned argc, JS::Value *vp) { + METHOD_HEADER(1) + + RootedValue name(cx, args.get(0)); + + if (!name.isString()) { + return false; + } + + auto name_to_remove = core::encode(cx, name); + if (!name_to_remove) { + return false; + } + + entry_list(self)->eraseIf([&](const FormDataEntry &entry) { + return entry.name == std::string_view(name_to_remove); + }); + + return true; +} + +bool FormData::get(JSContext *cx, unsigned argc, JS::Value *vp) { + METHOD_HEADER(1) + + RootedValue name(cx, args.get(0)); + + if (!name.isString()) { + return false; + } + + auto name_to_get = core::encode(cx, name); + if (!name_to_get) { + return false; + } + + auto entries = entry_list(self); + auto it = std::find_if(entries->begin(), entries->end(), [&](const FormDataEntry &entry) { + return entry.name == std::string_view(name_to_get); + }); + + if (it != entries->end()) { + args.rval().set(it->value); + } else { + args.rval().setUndefined(); + } + + return true; +} + +bool FormData::getAll(JSContext *cx, unsigned argc, JS::Value *vp) { + return true; +} + +bool FormData::has(JSContext *cx, unsigned argc, JS::Value *vp) { + METHOD_HEADER(1) + + RootedValue name(cx, args.get(0)); + + if (!name.isString()) { + return false; + } + + auto name_to_find = core::encode(cx, name); + if (!name_to_find) { + return false; + } + + auto entries = entry_list(self); + auto it = std::find_if(entries->begin(), entries->end(), [&](const FormDataEntry &entry) { + return entry.name == std::string_view(name_to_find); + }); + + args.rval().setBoolean(it != entries->end()); + return true; +} + +bool FormData::set(JSContext *cx, unsigned argc, JS::Value *vp) { + METHOD_HEADER(1) + + RootedValue name(cx, args.get(0)); + RootedValue value(cx, args.get(1)); + + if (!name.isString()) { + return false; + } + + auto name_to_find = core::encode(cx, name); + if (!name_to_find) { + return false; + } + + auto entries = entry_list(self); + auto it = std::find_if(entries->begin(), entries->end(), [&](const FormDataEntry &entry) { + return entry.name == std::string_view(name_to_find); + }); + + if (it != entries->end()) { + it->value = value; + } + + return true; +} + +bool FormData::constructor(JSContext *cx, unsigned argc, JS::Value *vp) { + CTOR_HEADER("FormData", 0); + + RootedObject self(cx, JS_NewObjectForConstructor(cx, &class_, args)); + + if (!self) { + return false; + } + + SetReservedSlot(self, static_cast(Slots::Entries), JS::PrivateValue(new EntryList)); + + args.rval().setObject(*self); + return true; +} + +void FormData::finalize(JS::GCContext *gcx, JSObject *self) { + MOZ_ASSERT(is_instance(self)); + auto entries = entry_list(self); + if (entries) { + free(entries); + } +} + +void FormData::trace(JSTracer *trc, JSObject *self) { + MOZ_ASSERT(is_instance(self)); + auto entries = entry_list(self); + entries->trace(trc); +} + +bool FormData::init_class(JSContext *cx, JS::HandleObject global) { + return init_class_impl(cx, global); +} + +bool install(api::Engine *engine) { + return FormData::init_class(engine->cx(), engine->global()); +} + +} // namespace form_data +} // namespace web +} // namespace builtins diff --git a/builtins/web/form-data.h b/builtins/web/form-data.h new file mode 100644 index 00000000..7c0cd3dc --- /dev/null +++ b/builtins/web/form-data.h @@ -0,0 +1,55 @@ +#ifndef BUILTINS_WEB_FORM_FDATA_H +#define BUILTINS_WEB_FORM_FDATA_H + +#include "builtin.h" + +namespace builtins { +namespace web { +namespace form_data { + +struct FormDataEntry { + std::string name; + JS::Heap value; + + void trace(JSTracer *trc) { + TraceEdge(trc, &value, "FormDataEntry value"); + } +}; + +class FormData : public TraceableBuiltinImpl { + static bool append(JSContext *cx, unsigned argc, JS::Value *vp); + static bool remove(JSContext *cx, unsigned argc, JS::Value *vp); + static bool get(JSContext *cx, unsigned argc, JS::Value *vp); + static bool getAll(JSContext *cx, unsigned argc, JS::Value *vp); + static bool has(JSContext *cx, unsigned argc, JS::Value *vp); + static bool set(JSContext *cx, unsigned argc, JS::Value *vp); + + using EntryList = JS::GCVector; + static EntryList* entry_list(JSObject *self); + +public: + static constexpr const char *class_name = "FormData"; + + static const JSFunctionSpec static_methods[]; + static const JSPropertySpec static_properties[]; + static const JSFunctionSpec methods[]; + static const JSPropertySpec properties[]; + + static constexpr unsigned ctor_length = 0; + + enum Slots { Entries, Count }; + + static bool init_class(JSContext *cx, HandleObject global); + static bool constructor(JSContext *cx, unsigned argc, Value *vp); + static void finalize(JS::GCContext *gcx, JSObject *self); + static void trace(JSTracer *trc, JSObject *self); +}; + +bool install(api::Engine *engine); + +} // namespace form_data +} // namespace web +} // namespace builtins + + +#endif // BUILTINS_WEB_FORM_FDATA_H diff --git a/cmake/builtins.cmake b/cmake/builtins.cmake index 16cc09f1..9e6c7fdb 100644 --- a/cmake/builtins.cmake +++ b/cmake/builtins.cmake @@ -18,6 +18,8 @@ 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/form-data.cpp) add_builtin( builtins::web::dom_exception SRC diff --git a/tests/wpt-harness/expectations/FileAPI/file/File-constructor.any.js.json b/tests/wpt-harness/expectations/FileAPI/file/File-constructor.any.js.json new file mode 100644 index 00000000..d0f41a63 --- /dev/null +++ b/tests/wpt-harness/expectations/FileAPI/file/File-constructor.any.js.json @@ -0,0 +1,149 @@ +{ + "File interface object exists": { + "status": "PASS" + }, + "Required arguments": { + "status": "PASS" + }, + "empty fileBits": { + "status": "PASS" + }, + "DOMString fileBits": { + "status": "PASS" + }, + "Unicode DOMString fileBits": { + "status": "PASS" + }, + "String object fileBits": { + "status": "PASS" + }, + "Empty Blob fileBits": { + "status": "PASS" + }, + "Blob fileBits": { + "status": "PASS" + }, + "Empty File fileBits": { + "status": "PASS" + }, + "File fileBits": { + "status": "PASS" + }, + "ArrayBuffer fileBits": { + "status": "PASS" + }, + "Typed array fileBits": { + "status": "PASS" + }, + "Various fileBits": { + "status": "PASS" + }, + "Number in fileBits": { + "status": "PASS" + }, + "Array in fileBits": { + "status": "PASS" + }, + "Object in fileBits": { + "status": "PASS" + }, + "Object with toString in fileBits": { + "status": "PASS" + }, + "Custom @@iterator": { + "status": "PASS" + }, + "Invalid bits argument: \"hello\"": { + "status": "PASS" + }, + "Invalid bits argument: 0": { + "status": "PASS" + }, + "Invalid bits argument: null": { + "status": "PASS" + }, + "Bits argument: object that throws": { + "status": "PASS" + }, + "Using fileName": { + "status": "PASS" + }, + "No replacement when using special character in fileName": { + "status": "PASS" + }, + "Using null fileName": { + "status": "PASS" + }, + "Using number fileName": { + "status": "PASS" + }, + "Using empty string fileName": { + "status": "PASS" + }, + "Using type in File constructor: text/plain": { + "status": "PASS" + }, + "Using type in File constructor: text/plain;charset=UTF-8": { + "status": "PASS" + }, + "Using type in File constructor: TEXT/PLAIN": { + "status": "PASS" + }, + "Using type in File constructor: 𝓽𝓮𝔁𝓽/𝔭𝔩𝔞𝔦𝔫": { + "status": "PASS" + }, + "Using type in File constructor: ascii/nonprintable\u001f": { + "status": "PASS" + }, + "Using type in File constructor: ascii/nonprintable": { + "status": "PASS" + }, + "Using type in File constructor: nonasciiî": { + "status": "PASS" + }, + "Using type in File constructor: nonasciiሴ": { + "status": "PASS" + }, + "Using type in File constructor: nonparsable": { + "status": "PASS" + }, + "Using lastModified": { + "status": "PASS" + }, + "Misusing name": { + "status": "PASS" + }, + "Unknown properties are ignored": { + "status": "PASS" + }, + "Invalid property bag: 123": { + "status": "PASS" + }, + "Invalid property bag: 123.4": { + "status": "PASS" + }, + "Invalid property bag: true": { + "status": "PASS" + }, + "Invalid property bag: \"abc\"": { + "status": "PASS" + }, + "Unusual but valid property bag: null": { + "status": "PASS" + }, + "Unusual but valid property bag: undefined": { + "status": "PASS" + }, + "Unusual but valid property bag: 1,2,3": { + "status": "PASS" + }, + "Unusual but valid property bag: /regex/": { + "status": "PASS" + }, + "Unusual but valid property bag: function() {}": { + "status": "PASS" + }, + "Property bag propagates exceptions": { + "status": "PASS" + } +} diff --git a/tests/wpt-harness/pre-harness.js b/tests/wpt-harness/pre-harness.js index d4d2991e..1b198a15 100644 --- a/tests/wpt-harness/pre-harness.js +++ b/tests/wpt-harness/pre-harness.js @@ -31,5 +31,4 @@ globalThis.crypto.subtle.generateKey = function () {return Promise.reject(new Er globalThis.FormData = class FormData{}; globalThis.SharedArrayBuffer = class SharedArrayBuffer{}; globalThis.MessageChannel = class MessageChannel{}; -globalThis.File = class File{}; ; diff --git a/tests/wpt-harness/tests.json b/tests/wpt-harness/tests.json index 7be26535..333fa950 100644 --- a/tests/wpt-harness/tests.json +++ b/tests/wpt-harness/tests.json @@ -172,6 +172,7 @@ "FileAPI/blob/Blob-slice-overflow.any.js", "FileAPI/blob/Blob-stream.any.js", "FileAPI/blob/Blob-text.any.js", + "FileAPI/file/File-constructor.any.js", "hr-time/basic.any.js", "hr-time/idlharness.any.js", "hr-time/monotonic-clock.any.js",