diff --git a/include/modules/globals.h b/include/modules/globals.h index ff3aec6..1904407 100644 --- a/include/modules/globals.h +++ b/include/modules/globals.h @@ -5,9 +5,7 @@ void init_globals_module(void); -ant_value_t js_structured_clone(ant_t *js, ant_value_t *args, int nargs); ant_value_t js_report_error(ant_t *js, ant_value_t *args, int nargs); - bool js_fire_unhandled_rejection(ant_t *js, ant_value_t promise_val, ant_value_t reason); void js_fire_rejection_handled(ant_t *js, ant_value_t promise_val, ant_value_t reason); diff --git a/include/modules/structured-clone.h b/include/modules/structured-clone.h new file mode 100644 index 0000000..c865041 --- /dev/null +++ b/include/modules/structured-clone.h @@ -0,0 +1,9 @@ +#ifndef STRUCTURED_CLONE_H +#define STRUCTURED_CLONE_H + +#include "types.h" + +void init_structured_clone_module(void); +ant_value_t js_structured_clone(ant_t *js, ant_value_t *args, int nargs); + +#endif diff --git a/src/main.c b/src/main.c index e645464..63a0c01 100644 --- a/src/main.c +++ b/src/main.c @@ -73,6 +73,7 @@ #include "modules/domexception.h" #include "modules/abort.h" #include "modules/globals.h" +#include "modules/structured-clone.h" #include "modules/v8.h" #include "modules/worker_threads.h" #include "modules/headers.h" @@ -584,6 +585,7 @@ int main(int argc, char *argv[]) { init_timer_module(); init_domexception_module(); init_globals_module(); + init_structured_clone_module(); init_abort_module(); init_headers_module(); init_blob_module(); diff --git a/src/modules/formdata.c b/src/modules/formdata.c index 6ba29d5..6ac4917 100644 --- a/src/modules/formdata.c +++ b/src/modules/formdata.c @@ -156,40 +156,51 @@ static ant_value_t extract_file_entry( blob_data_t *bd = blob_get_data(val); if (!bd) return js_mkerr_typed(js, JS_ERR_TYPE, "FormData value must be a string, Blob, or File"); - const char *fname = NULL; - char *fname_owned = NULL; - - if (vtype(filename_v) != T_UNDEF) { - ant_value_t fv = filename_v; - if (vtype(fv) != T_STR) { fv = js_tostring_val(js, fv); if (is_err(fv)) return fv; } - fname_owned = strdup(js_getstr(js, fv, NULL)); - fname = fname_owned; - } - - else if (bd->name && bd->name[0]) fname = bd->name; - else fname = "blob"; - - int64_t last_modified = bd->last_modified; - if (!last_modified) { - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - last_modified = (int64_t)ts.tv_sec * 1000LL + (int64_t)(ts.tv_nsec / 1000000); - } + bool is_file = (bd->name != NULL); + bool no_filename_override = (vtype(filename_v) == T_UNDEF); - ant_value_t file_obj = blob_create(js, bd->data, bd->size, bd->type); - if (is_err(file_obj)) { free(fname_owned); return file_obj; } + ant_value_t stored_val; - blob_data_t *nbd = blob_get_data(file_obj); - if (nbd) { - nbd->name = strdup(fname); - nbd->last_modified = last_modified; - } free(fname_owned); - - js_set_proto_init(file_obj, g_file_proto); - if (is_set) fd_delete_name(d, name); + if (is_file && no_filename_override) { + stored_val = val; + } else { + const char *fname = NULL; + char *fname_owned = NULL; + + if (!no_filename_override) { + ant_value_t fv = filename_v; + if (vtype(fv) != T_STR) { fv = js_tostring_val(js, fv); if (is_err(fv)) return fv; } + fname_owned = strdup(js_getstr(js, fv, NULL)); + fname = fname_owned; + } + + else if (bd->name && bd->name[0]) fname = bd->name; + else fname = "blob"; + + int64_t last_modified = bd->last_modified; + if (!last_modified) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + last_modified = (int64_t)ts.tv_sec * 1000LL + (int64_t)(ts.tv_nsec / 1000000); + } + + ant_value_t file_obj = blob_create(js, bd->data, bd->size, bd->type); + if (is_err(file_obj)) { free(fname_owned); return file_obj; } + + blob_data_t *nbd = blob_get_data(file_obj); + if (nbd) { + nbd->name = strdup(fname); + nbd->last_modified = last_modified; + } + + free(fname_owned); + js_set_proto_init(file_obj, g_file_proto); + stored_val = file_obj; + } + if (is_set) fd_delete_name(d, name); size_t idx = (size_t)js_arr_len(js, values_arr); - js_arr_push(js, values_arr, file_obj); + js_arr_push(js, values_arr, stored_val); return fd_append_file(d, name, idx) ? js_mkundef() : js_mkerr(js, "out of memory"); } @@ -400,7 +411,7 @@ static ant_value_t js_formdata_values(ant_t *js, ant_value_t *args, int nargs) { static ant_value_t js_formdata_ctor(ant_t *js, ant_value_t *args, int nargs) { if (vtype(js->new_target) == T_UNDEF) return js_mkerr_typed(js, JS_ERR_TYPE, "FormData constructor requires 'new'"); - if (nargs >= 1 && vtype(args[0]) != T_UNDEF && vtype(args[0]) != T_NULL) + if (nargs >= 1 && vtype(args[0]) != T_UNDEF) return js_mkerr_typed(js, JS_ERR_TYPE, "FormData does not support a form element argument"); fd_data_t *d = fd_data_new(); diff --git a/src/modules/globals.c b/src/modules/globals.c index c32dfc8..1318b0e 100644 --- a/src/modules/globals.c +++ b/src/modules/globals.c @@ -1,7 +1,6 @@ #include #include #include -#include #include "ant.h" #include "errors.h" @@ -10,10 +9,7 @@ #include "descriptors.h" #include "silver/engine.h" -#include "modules/buffer.h" #include "modules/events.h" -#include "modules/collections.h" -#include "modules/domexception.h" #include "modules/globals.h" ant_value_t js_report_error(ant_t *js, ant_value_t *args, int nargs) { @@ -104,206 +100,6 @@ void js_fire_rejection_handled(ant_t *js, ant_value_t promise_val, ant_value_t r sv_vm_call(js->vm, js, handler, global, call_args, 1, NULL, false); } -static void sc_add_seen(ant_t *js, ant_value_t seen, ant_value_t val, ant_value_t clone) { - char key[24]; - snprintf(key, sizeof(key), "%" PRIxPTR, (uintptr_t)vdata(val)); - js_set(js, seen, key, clone); -} - -static ant_value_t sc_lookup_seen(ant_t *js, ant_value_t seen, ant_value_t val) { - char key[24]; - snprintf(key, sizeof(key), "%" PRIxPTR, (uintptr_t)vdata(val)); - ant_value_t found = js_get(js, seen, key); - return (vtype(found) == T_UNDEF) ? js_mkundef() : found; -} - -static const char *sc_ta_type_name(TypedArrayType type) { - static const char *names[] = { - "Int8Array", "Uint8Array", "Uint8ClampedArray", - "Int16Array", "Uint16Array", - "Int32Array", "Uint32Array", - "Float32Array", "Float64Array", - "BigInt64Array", "BigUint64Array" - }; - int i = (int)type; - if (i < 0 || i > 10) return "Uint8Array"; - return names[i]; -} - -static ant_value_t sc_clone_rec(ant_t *js, ant_value_t val, ant_value_t seen) { - uint8_t t = vtype(val); - - if (t == T_UNDEF || t == T_NULL || t == T_BOOL || t == T_NUM || t == T_BIGINT || t == T_STR) return val; - if (t == T_SYMBOL) return js_throw(js, make_dom_exception(js, "Symbol cannot be serialized", "DataCloneError")); - - if (t == T_TYPEDARRAY) { - ant_value_t existing = sc_lookup_seen(js, seen, val); - if (vtype(existing) != T_UNDEF) return existing; - - TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(val); - if (!ta || !ta->buffer) - return js_throw(js, make_dom_exception(js, "TypedArray could not be cloned", "DataCloneError")); - - ArrayBufferData *new_buf = create_array_buffer_data(ta->byte_length); - if (!new_buf) return js_mkerr(js, "out of memory"); - if (ta->byte_length > 0) memcpy(new_buf->data, ta->buffer->data + ta->byte_offset, ta->byte_length); - - ant_value_t clone = create_typed_array(js, ta->type, new_buf, 0, ta->length, sc_ta_type_name(ta->type)); - sc_add_seen(js, seen, val, clone); - - return clone; - } - - if (t == T_FUNC || t == T_CFUNC || t == T_CLOSURE) - return js_throw(js, make_dom_exception(js, "() => {} could not be cloned", "DataCloneError")); - if (t == T_PROMISE || t == T_GENERATOR) - return js_throw(js, make_dom_exception(js, "Value could not be cloned", "DataCloneError")); - if (!is_object_type(val)) - return js_throw(js, make_dom_exception(js, "Value could not be cloned", "DataCloneError")); - - ant_value_t existing = sc_lookup_seen(js, seen, val); - if (vtype(existing) != T_UNDEF) return existing; - - if (t == T_ARR) { - ant_value_t clone = js_mkarr(js); - sc_add_seen(js, seen, val, clone); - - ant_offset_t len = js_arr_len(js, val); - for (ant_offset_t i = 0; i < len; i++) { - ant_value_t ic = sc_clone_rec(js, js_arr_get(js, val, i), seen); - if (is_err(ic)) return ic; - js_arr_push(js, clone, ic); - } - - return clone; - } - - ant_object_t *obj_ptr = js_obj_ptr(val); - if (!obj_ptr) - return js_throw(js, make_dom_exception(js, "Value could not be cloned", "DataCloneError")); - - if (obj_ptr->type_tag == T_WEAKMAP || obj_ptr->type_tag == T_WEAKSET) - return js_throw(js, make_dom_exception(js, "WeakMap/WeakSet could not be cloned", "DataCloneError")); - - if (obj_ptr->type_tag == T_MAP) { - ant_value_t clone = js_mkobj(js); - js_obj_ptr(clone)->type_tag = T_MAP; - - ant_value_t map_proto = js_get_ctor_proto(js, "Map", 3); - if (is_special_object(map_proto)) js_set_proto_init(clone, map_proto); - - map_entry_t **new_head = ant_calloc(sizeof(map_entry_t *)); - if (!new_head) return js_mkerr(js, "out of memory"); - - *new_head = NULL; - js_set_slot(clone, SLOT_DATA, ANT_PTR(new_head)); - sc_add_seen(js, seen, val, clone); - - map_entry_t **src_head = get_map_from_obj(js, val); - if (src_head && *src_head) { - map_entry_t *e, *tmp; - HASH_ITER(hh, *src_head, e, tmp) { - ant_value_t vc = sc_clone_rec(js, e->value, seen); - if (is_err(vc)) return vc; - - map_entry_t *ne = ant_calloc(sizeof(map_entry_t)); - if (!ne) return js_mkerr(js, "out of memory"); - ne->key = strdup(e->key); - ne->value = vc; - HASH_ADD_STR(*new_head, key, ne); - }} - - return clone; - } - - if (obj_ptr->type_tag == T_SET) { - ant_value_t clone = js_mkobj(js); - js_obj_ptr(clone)->type_tag = T_SET; - - ant_value_t set_proto = js_get_ctor_proto(js, "Set", 3); - if (is_special_object(set_proto)) js_set_proto_init(clone, set_proto); - - set_entry_t **new_head = ant_calloc(sizeof(set_entry_t *)); - if (!new_head) return js_mkerr(js, "out of memory"); - *new_head = NULL; - js_set_slot(clone, SLOT_DATA, ANT_PTR(new_head)); - sc_add_seen(js, seen, val, clone); - - set_entry_t **src_head = get_set_from_obj(js, val); - if (src_head && *src_head) { - set_entry_t *e, *tmp; - HASH_ITER(hh, *src_head, e, tmp) { - ant_value_t vc = sc_clone_rec(js, e->value, seen); - if (is_err(vc)) return vc; - - set_entry_t *ne = ant_calloc(sizeof(set_entry_t)); - if (!ne) return js_mkerr(js, "out of memory"); - ne->key = strdup(e->key); - ne->value = vc; - HASH_ADD_STR(*new_head, key, ne); - }} - - return clone; - } - - if (js_get_slot(val, SLOT_ERROR_BRAND) == js_true) { - ant_value_t clone = js_mkobj(js); - sc_add_seen(js, seen, val, clone); - - const char *msg = get_str_prop(js, val, "message", 7, NULL); - const char *name = get_str_prop(js, val, "name", 4, NULL); - const char *stack = get_str_prop(js, val, "stack", 5, NULL); - - if (msg) js_set(js, clone, "message", js_mkstr(js, msg, strlen(msg))); - if (name) js_set(js, clone, "name", js_mkstr(js, name, strlen(name))); - if (stack) js_set(js, clone, "stack", js_mkstr(js, stack, strlen(stack))); - js_set_slot(clone, SLOT_ERROR_BRAND, js_true); - - ant_value_t err_type = js_get_slot(val, SLOT_ERR_TYPE); - if (vtype(err_type) != T_UNDEF) js_set_slot(clone, SLOT_ERR_TYPE, err_type); - - return clone; - } - - ant_value_t date_proto = js_get_ctor_proto(js, "Date", 4); - if ( - vtype(date_proto) != T_UNDEF - && is_object_type(date_proto) - && js_is_prototype_of(js, date_proto, val) - ) { - ant_value_t clone = js_mkobj(js); - js_set_proto_init(clone, date_proto); - js_set_slot(clone, SLOT_DATA, js_get_slot(val, SLOT_DATA)); - sc_add_seen(js, seen, val, clone); - - return clone; - } - - ant_value_t clone = js_mkobj(js); - sc_add_seen(js, seen, val, clone); - - ant_iter_t iter = js_prop_iter_begin(js, val); - const char *key; - size_t key_len; - ant_value_t pval; - - while (js_prop_iter_next(&iter, &key, &key_len, &pval)) { - ant_value_t pc = sc_clone_rec(js, pval, seen); - if (is_err(pc)) { js_prop_iter_end(&iter); return pc; } - js_set(js, clone, key, pc); - } - - js_prop_iter_end(&iter); - return clone; -} - -ant_value_t js_structured_clone(ant_t *js, ant_value_t *args, int nargs) { - if (nargs < 1) return js_mkundef(); - // TODO: transfer option (args[1]) accepted but not yet implemented - ant_value_t seen = js_mkobj(js); - return sc_clone_rec(js, args[0], seen); -} - void init_globals_module(void) { ant_t *js = rt->js; ant_value_t global = js_glob(js); @@ -311,6 +107,4 @@ void init_globals_module(void) { js_set(js, global, "reportError", js_mkfun(js_report_error)); js_set_descriptor(js, global, "reportError", 11, JS_DESC_W | JS_DESC_C); - js_set(js, global, "structuredClone", js_mkfun(js_structured_clone)); - js_set_descriptor(js, global, "structuredClone", 15, JS_DESC_W | JS_DESC_C); } diff --git a/src/modules/structured-clone.c b/src/modules/structured-clone.c new file mode 100644 index 0000000..0646a07 --- /dev/null +++ b/src/modules/structured-clone.c @@ -0,0 +1,306 @@ +#include +#include + +#include + +#include "ant.h" +#include "errors.h" +#include "runtime.h" +#include "internal.h" +#include "descriptors.h" + +#include "modules/buffer.h" +#include "modules/collections.h" +#include "modules/domexception.h" +#include "modules/blob.h" +#include "modules/structured-clone.h" + +typedef struct sc_entry { + ant_value_t key; + ant_value_t value; + UT_hash_handle hh; +} sc_entry_t; + +static void sc_add(sc_entry_t **table, ant_value_t key, ant_value_t value) { + sc_entry_t *e = calloc(1, sizeof(sc_entry_t)); + if (!e) return; + e->key = key; + e->value = value; + HASH_ADD(hh, *table, key, sizeof(ant_value_t), e); +} + +static ant_value_t sc_lookup(sc_entry_t **table, ant_value_t key) { + sc_entry_t *e; + HASH_FIND(hh, *table, &key, sizeof(ant_value_t), e); + return e ? e->value : js_mkundef(); +} + +static bool sc_has(sc_entry_t **table, ant_value_t key) { + sc_entry_t *e; + HASH_FIND(hh, *table, &key, sizeof(ant_value_t), e); + return e != NULL; +} + +static void sc_free(sc_entry_t **table) { + sc_entry_t *e, *tmp; + HASH_ITER(hh, *table, e, tmp) { + HASH_DEL(*table, e); + free(e); + } +} + +static const char *sc_ta_type_name(TypedArrayType type) { + static const char *names[] = { + "Int8Array", "Uint8Array", "Uint8ClampedArray", + "Int16Array", "Uint16Array", + "Int32Array", "Uint32Array", + "Float32Array", "Float64Array", + "BigInt64Array", "BigUint64Array" + }; + int i = (int)type; + if (i < 0 || i > 10) return "Uint8Array"; + return names[i]; +} + +static ant_value_t sc_clone_rec(ant_t *js, ant_value_t val, sc_entry_t **seen, sc_entry_t **transfer) { + uint8_t t = vtype(val); + + if (t == T_UNDEF || t == T_NULL || t == T_BOOL || t == T_NUM || t == T_BIGINT || t == T_STR) return val; + if (t == T_SYMBOL) return js_throw(js, make_dom_exception(js, "Symbol cannot be serialized", "DataCloneError")); + + if (t == T_TYPEDARRAY) { + ant_value_t existing = sc_lookup(seen, val); + if (vtype(existing) != T_UNDEF) return existing; + + TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(val); + if (!ta || !ta->buffer) + return js_throw(js, make_dom_exception(js, "TypedArray could not be cloned", "DataCloneError")); + + ArrayBufferData *new_buf = create_array_buffer_data(ta->byte_length); + if (!new_buf) return js_mkerr(js, "out of memory"); + if (ta->byte_length > 0) memcpy(new_buf->data, ta->buffer->data + ta->byte_offset, ta->byte_length); + + ant_value_t clone = create_typed_array(js, ta->type, new_buf, 0, ta->length, sc_ta_type_name(ta->type)); + sc_add(seen, val, clone); + + return clone; + } + + if (t == T_FUNC || t == T_CFUNC || t == T_CLOSURE) + return js_throw(js, make_dom_exception(js, "() => {} could not be cloned", "DataCloneError")); + if (t == T_PROMISE || t == T_GENERATOR) + return js_throw(js, make_dom_exception(js, "Value could not be cloned", "DataCloneError")); + if (!is_object_type(val)) + return js_throw(js, make_dom_exception(js, "Value could not be cloned", "DataCloneError")); + + ant_value_t existing = sc_lookup(seen, val); + if (vtype(existing) != T_UNDEF) return existing; + + ant_value_t buf_slot_v = js_get_slot(val, SLOT_BUFFER); + if (vtype(buf_slot_v) == T_NUM) { + ArrayBufferData *abd = (ArrayBufferData *)(uintptr_t)(size_t)js_getnum(buf_slot_v); + if (abd && !abd->is_detached) { + if (transfer && sc_has(transfer, val)) { + ArrayBufferData *new_abd = calloc(1, sizeof(ArrayBufferData)); + if (!new_abd) return js_mkerr(js, "out of memory"); + new_abd->data = abd->data; + new_abd->length = abd->length; + new_abd->capacity = abd->capacity; + new_abd->ref_count = 1; + abd->data = NULL; + abd->length = 0; + abd->capacity = 0; + abd->is_detached = 1; + js_set(js, val, "byteLength", js_mknum(0)); + ant_value_t clone = create_arraybuffer_obj(js, new_abd); + sc_add(seen, val, clone); + return clone; + } + ArrayBufferData *new_abd = create_array_buffer_data(abd->length); + if (!new_abd) return js_mkerr(js, "out of memory"); + if (abd->length > 0) memcpy(new_abd->data, abd->data, abd->length); + ant_value_t clone = create_arraybuffer_obj(js, new_abd); + sc_add(seen, val, clone); + return clone; + }} + + if (t == T_ARR) { + ant_value_t clone = js_mkarr(js); + sc_add(seen, val, clone); + + ant_offset_t len = js_arr_len(js, val); + for (ant_offset_t i = 0; i < len; i++) { + ant_value_t ic = sc_clone_rec(js, js_arr_get(js, val, i), seen, transfer); + if (is_err(ic)) return ic; + js_arr_push(js, clone, ic); + } + + return clone; + } + + ant_object_t *obj_ptr = js_obj_ptr(val); + if (!obj_ptr) + return js_throw(js, make_dom_exception(js, "Value could not be cloned", "DataCloneError")); + + if (obj_ptr->type_tag == T_WEAKMAP || obj_ptr->type_tag == T_WEAKSET) + return js_throw(js, make_dom_exception(js, "WeakMap/WeakSet could not be cloned", "DataCloneError")); + + if (obj_ptr->type_tag == T_MAP) { + ant_value_t clone = js_mkobj(js); + js_obj_ptr(clone)->type_tag = T_MAP; + + ant_value_t map_proto = js_get_ctor_proto(js, "Map", 3); + if (is_special_object(map_proto)) js_set_proto_init(clone, map_proto); + + map_entry_t **new_head = ant_calloc(sizeof(map_entry_t *)); + if (!new_head) return js_mkerr(js, "out of memory"); + + *new_head = NULL; + js_set_slot(clone, SLOT_DATA, ANT_PTR(new_head)); + sc_add(seen, val, clone); + + map_entry_t **src_head = get_map_from_obj(js, val); + if (src_head && *src_head) { + map_entry_t *e, *tmp; + HASH_ITER(hh, *src_head, e, tmp) { + ant_value_t vc = sc_clone_rec(js, e->value, seen, transfer); + if (is_err(vc)) return vc; + + map_entry_t *ne = ant_calloc(sizeof(map_entry_t)); + if (!ne) return js_mkerr(js, "out of memory"); + ne->key = strdup(e->key); + ne->value = vc; + HASH_ADD_STR(*new_head, key, ne); + }} + + return clone; + } + + if (obj_ptr->type_tag == T_SET) { + ant_value_t clone = js_mkobj(js); + js_obj_ptr(clone)->type_tag = T_SET; + + ant_value_t set_proto = js_get_ctor_proto(js, "Set", 3); + if (is_special_object(set_proto)) js_set_proto_init(clone, set_proto); + + set_entry_t **new_head = ant_calloc(sizeof(set_entry_t *)); + if (!new_head) return js_mkerr(js, "out of memory"); + *new_head = NULL; + js_set_slot(clone, SLOT_DATA, ANT_PTR(new_head)); + sc_add(seen, val, clone); + + set_entry_t **src_head = get_set_from_obj(js, val); + if (src_head && *src_head) { + set_entry_t *e, *tmp; + HASH_ITER(hh, *src_head, e, tmp) { + ant_value_t vc = sc_clone_rec(js, e->value, seen, transfer); + if (is_err(vc)) return vc; + + set_entry_t *ne = ant_calloc(sizeof(set_entry_t)); + if (!ne) return js_mkerr(js, "out of memory"); + ne->key = strdup(e->key); + ne->value = vc; + HASH_ADD_STR(*new_head, key, ne); + }} + + return clone; + } + + if (js_get_slot(val, SLOT_ERROR_BRAND) == js_true) { + ant_value_t clone = js_mkobj(js); + sc_add(seen, val, clone); + + const char *msg = get_str_prop(js, val, "message", 7, NULL); + const char *name = get_str_prop(js, val, "name", 4, NULL); + const char *stack = get_str_prop(js, val, "stack", 5, NULL); + + if (msg) js_set(js, clone, "message", js_mkstr(js, msg, strlen(msg))); + if (name) js_set(js, clone, "name", js_mkstr(js, name, strlen(name))); + if (stack) js_set(js, clone, "stack", js_mkstr(js, stack, strlen(stack))); + js_set_slot(clone, SLOT_ERROR_BRAND, js_true); + + ant_value_t err_type = js_get_slot(val, SLOT_ERR_TYPE); + if (vtype(err_type) != T_UNDEF) js_set_slot(clone, SLOT_ERR_TYPE, err_type); + + return clone; + } + + ant_value_t date_proto = js_get_ctor_proto(js, "Date", 4); + if ( + vtype(date_proto) != T_UNDEF + && is_object_type(date_proto) + && js_is_prototype_of(js, date_proto, val) + ) { + ant_value_t clone = js_mkobj(js); + js_set_proto_init(clone, date_proto); + js_set_slot(clone, SLOT_DATA, js_get_slot(val, SLOT_DATA)); + sc_add(seen, val, clone); + + return clone; + } + + blob_data_t *bd = js_is_prototype_of(js, g_blob_proto, val) ? blob_get_data(val) : NULL; + if (bd) { + ant_value_t clone = blob_create(js, bd->data, bd->size, bd->type); + if (is_err(clone)) return clone; + sc_add(seen, val, clone); + if (bd->name) { + blob_data_t *nbd = blob_get_data(clone); + if (nbd) { + nbd->name = strdup(bd->name); + nbd->last_modified = bd->last_modified; + } + js_set_proto_init(clone, g_file_proto); + } + return clone; + } + + ant_value_t clone = js_mkobj(js); + sc_add(seen, val, clone); + + ant_iter_t iter = js_prop_iter_begin(js, val); + const char *key; + size_t key_len; + ant_value_t pval; + + while (js_prop_iter_next(&iter, &key, &key_len, &pval)) { + ant_value_t pc = sc_clone_rec(js, pval, seen, transfer); + if (is_err(pc)) { js_prop_iter_end(&iter); return pc; } + js_set(js, clone, key, pc); + } + + js_prop_iter_end(&iter); + return clone; +} + +ant_value_t js_structured_clone(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 1) return js_mkundef(); + + sc_entry_t *transfer = NULL; + + if (nargs < 2 || !is_object_type(args[1])) goto clone; + ant_value_t xfer_arr = js_get(js, args[1], "transfer"); + if (vtype(xfer_arr) != T_ARR) goto clone; + + ant_offset_t xfer_len = js_arr_len(js, xfer_arr); + for (ant_offset_t i = 0; i < xfer_len; i++) { + ant_value_t item = js_arr_get(js, xfer_arr, i); + if (is_object_type(item)) sc_add(&transfer, item, js_true); + } + +clone:; + + sc_entry_t *seen = NULL; + ant_value_t result = sc_clone_rec(js, args[0], &seen, &transfer); + sc_free(&seen); + sc_free(&transfer); + return result; +} + +void init_structured_clone_module(void) { + ant_t *js = rt->js; + ant_value_t global = js_glob(js); + + js_set(js, global, "structuredClone", js_mkfun(js_structured_clone)); + js_set_descriptor(js, global, "structuredClone", 15, JS_DESC_W | JS_DESC_C); +}