@scriptc/runtime 0.0.22 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/scr_fetch.c CHANGED
@@ -156,6 +156,10 @@ struct SfSignal {
156
156
  bool aborted;
157
157
  ScrDyn *reason;
158
158
  ScrError *error_reason;
159
+ bool reason_self;
160
+ ScrDynHandleTag reason_self_tag;
161
+ bool reason_tracked;
162
+ SfSignal *reason_next;
159
163
  ScrDyn *onabort;
160
164
  size_t onabort_order;
161
165
  SfAbortListener *listeners;
@@ -270,6 +274,7 @@ struct SfStream {
270
274
  struct SfHeaders {
271
275
  size_t rc;
272
276
  ScrArr *pairs;
277
+ bool immutable;
273
278
  };
274
279
 
275
280
  struct SfResponse {
@@ -318,9 +323,10 @@ struct SfTransfer {
318
323
 
319
324
  static SfTransfer *sf_live;
320
325
  /* Weak registries: owners unlink themselves on destruction. They let the
321
- * fetch teardown sever native→callback edges before final cycle collection,
322
- * including an otherwise-untraceable callback→captured-handle backedge. */
326
+ * fetch teardown sever opaque native→dyn edges before final cycle collection,
327
+ * including otherwise-untraceable callback/reason→handle backedges. */
323
328
  static SfSignal *sf_callback_signals;
329
+ static SfSignal *sf_reason_signals;
324
330
  static SfStream *sf_callback_streams;
325
331
 
326
332
  static void sf_oom(void) {
@@ -428,6 +434,53 @@ static void sf_signal_untrack_callbacks(SfSignal *s) {
428
434
  s->callbacks_next = NULL;
429
435
  }
430
436
 
437
+ static void sf_signal_track_reason(SfSignal *s) {
438
+ if (s->reason_tracked) return;
439
+ s->reason_tracked = true;
440
+ s->reason_next = sf_reason_signals;
441
+ sf_reason_signals = s;
442
+ }
443
+
444
+ static void sf_signal_untrack_reason(SfSignal *s) {
445
+ if (!s->reason_tracked) return;
446
+ for (SfSignal **at = &sf_reason_signals; *at;
447
+ at = &(*at)->reason_next) {
448
+ if (*at == s) {
449
+ *at = s->reason_next;
450
+ break;
451
+ }
452
+ }
453
+ s->reason_tracked = false;
454
+ s->reason_next = NULL;
455
+ }
456
+
457
+ /* A controller may abort with itself or its own signal as the reason.
458
+ * Those handles point back to this SfSignal, so retaining their ScrDyn box
459
+ * would create an opaque RC cycle. Remember the identity as tag + pointer
460
+ * instead and mint an equivalent handle whenever the reason is observed;
461
+ * checked-dynamic strict equality uses that same pair. Other reasons stay
462
+ * strongly owned and join the teardown registry so larger reason/handle
463
+ * cycles can be severed once no Web object remains observable. */
464
+ static ScrDyn *sf_signal_reason_ref(SfSignal *s) {
465
+ if (s->reason_self) {
466
+ return scr_dyn_new_handle(s, s->reason_self_tag);
467
+ }
468
+ return scr_dyn_retain(s->reason);
469
+ }
470
+
471
+ static void sf_signal_drop_reason(SfSignal *s) {
472
+ ScrDyn *reason = s->reason;
473
+ ScrError *error_reason = s->error_reason;
474
+ sf_signal_untrack_reason(s);
475
+ s->reason = NULL;
476
+ s->error_reason = NULL;
477
+ s->reason_self = false;
478
+ scr_error_release(error_reason);
479
+ /* May release a handle that owns s and destroy it reentrantly. All s
480
+ * fields used by this cleanup are therefore cleared before this call. */
481
+ scr_dyn_release(reason);
482
+ }
483
+
431
484
  static SfSignal *sf_signal_new(void) {
432
485
  SfSignal *s = calloc(1, sizeof *s);
433
486
  if (!s) sf_oom();
@@ -490,8 +543,7 @@ static void sf_signal_release(SfSignal *s) {
490
543
  free(s->source_watches);
491
544
  free(s->sources);
492
545
  sf_signal_drop_callbacks(s);
493
- scr_dyn_release(s->reason);
494
- scr_error_release(s->error_reason);
546
+ sf_signal_drop_reason(s);
495
547
  free(s);
496
548
  }
497
549
 
@@ -876,7 +928,16 @@ static void sf_signal_abort_full(SfSignal *s, ScrDyn *reason,
876
928
  ScrError *error_reason) {
877
929
  if (s->aborted) return;
878
930
  s->aborted = true;
879
- s->reason = scr_dyn_retain(reason);
931
+ if (reason && reason->kind == SCR_DYN_HANDLE &&
932
+ reason->v.handle.ptr == s &&
933
+ (reason->v.handle.tag == SCR_DYNH_ABORT_SIGNAL ||
934
+ reason->v.handle.tag == SCR_DYNH_ABORT_CONTROLLER)) {
935
+ s->reason_self = true;
936
+ s->reason_self_tag = reason->v.handle.tag;
937
+ } else {
938
+ s->reason = scr_dyn_retain(reason);
939
+ sf_signal_track_reason(s);
940
+ }
880
941
  s->error_reason = error_reason ? scr_error_retain(error_reason) : NULL;
881
942
  while (s->watchers) {
882
943
  SfSignalWatch *w = s->watchers;
@@ -975,7 +1036,9 @@ ScrDyn *scr_fetch_abort_now(ScrDyn *reason) {
975
1036
 
976
1037
  static void sf_any_source_abort(SfSignalWatch *w, SfSignal *source) {
977
1038
  SfSignal *out = (SfSignal *)w->owner;
978
- sf_signal_abort_full(out, source->reason, source->error_reason);
1039
+ ScrDyn *reason = sf_signal_reason_ref(source);
1040
+ sf_signal_abort_full(out, reason, source->error_reason);
1041
+ scr_dyn_release(reason);
979
1042
  }
980
1043
 
981
1044
  ScrDyn *scr_fetch_abort_any(ScrDyn *signals) {
@@ -1004,7 +1067,9 @@ ScrDyn *scr_fetch_abort_any(ScrDyn *signals) {
1004
1067
  }
1005
1068
  out->sources[i] = sf_signal_retain(source);
1006
1069
  if (source->aborted) {
1007
- sf_signal_abort_full(out, source->reason, source->error_reason);
1070
+ ScrDyn *reason = sf_signal_reason_ref(source);
1071
+ sf_signal_abort_full(out, reason, source->error_reason);
1072
+ scr_dyn_release(reason);
1008
1073
  } else {
1009
1074
  out->source_watches[i] =
1010
1075
  sf_signal_watch(source, out, &sf_any_source_abort);
@@ -1015,6 +1080,47 @@ ScrDyn *scr_fetch_abort_any(ScrDyn *signals) {
1015
1080
  return boxed;
1016
1081
  }
1017
1082
 
1083
+ /* AbortController is a distinct public handle over the same native signal
1084
+ * state fetch and AbortSignal.any observe. Reading `.signal` boxes that
1085
+ * state with the AbortSignal tag, so repeated reads preserve JS identity
1086
+ * through the checked-dynamic handle equality rule (tag + pointer). */
1087
+ ScrDyn *scr_fetch_abort_controller_new(void) {
1088
+ SfSignal *s = sf_signal_new();
1089
+ ScrDyn *out = scr_dyn_new_handle(s, SCR_DYNH_ABORT_CONTROLLER);
1090
+ sf_signal_release(s);
1091
+ return out;
1092
+ }
1093
+
1094
+ static ScrDyn *sf_abort_controller_invoke(
1095
+ void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args,
1096
+ size_t argc, const char *what) {
1097
+ SfSignal *s = ptr;
1098
+ if (strcmp(method, "abort") == 0) {
1099
+ if (!s->aborted) {
1100
+ ScrDyn *reason = argc > 0 ? args[0] : NULL;
1101
+ if (!reason || reason->kind == SCR_DYN_UNDEF) {
1102
+ sf_signal_abort_default(s, false);
1103
+ } else {
1104
+ ScrError *e = sf_is_error_dyn(reason) ? scr_error_from_dyn(reason) : NULL;
1105
+ sf_signal_abort_full(s, reason, e);
1106
+ scr_error_release(e);
1107
+ }
1108
+ }
1109
+ return scr_dyn_retain(scr_dyn_undefined());
1110
+ }
1111
+ (void)self;
1112
+ sf_not_function(what);
1113
+ return NULL;
1114
+ }
1115
+
1116
+ static ScrDyn *sf_abort_controller_get(
1117
+ void *ptr, const char *key, size_t len) {
1118
+ if (sf_name(key, len, "signal")) {
1119
+ return scr_dyn_new_handle(ptr, SCR_DYNH_ABORT_SIGNAL);
1120
+ }
1121
+ return NULL;
1122
+ }
1123
+
1018
1124
  static bool sf_signal_listener_options(
1019
1125
  ScrDyn *const *args, size_t argc, bool *capture, bool *once,
1020
1126
  SfSignal **option_signal) {
@@ -1078,7 +1184,9 @@ static ScrDyn *sf_signal_invoke(void *ptr, ScrDyn *self, const char *method,
1078
1184
  SfSignal *s = ptr;
1079
1185
  if (strcmp(method, "throwIfAborted") == 0) {
1080
1186
  if (s->aborted) {
1081
- sf_throw_dyn_reason(s->reason);
1187
+ ScrDyn *reason = sf_signal_reason_ref(s);
1188
+ sf_throw_dyn_reason(reason);
1189
+ scr_dyn_release(reason);
1082
1190
  return NULL;
1083
1191
  }
1084
1192
  return scr_dyn_retain(scr_dyn_undefined());
@@ -1199,7 +1307,7 @@ static ScrDyn *sf_signal_get(void *ptr, const char *key, size_t len) {
1199
1307
  SfSignal *s = ptr;
1200
1308
  if (sf_name(key, len, "aborted")) return scr_dyn_new_bool(s->aborted);
1201
1309
  if (sf_name(key, len, "reason")) {
1202
- return s->aborted ? scr_dyn_retain(s->reason)
1310
+ return s->aborted ? sf_signal_reason_ref(s)
1203
1311
  : scr_dyn_retain(scr_dyn_undefined());
1204
1312
  }
1205
1313
  if (sf_name(key, len, "onabort")) {
@@ -2627,6 +2735,18 @@ static ScrDyn *sf_controller_get(void *ptr, const char *key, size_t len) {
2627
2735
 
2628
2736
  static bool sf_header_name_ok(const char *s, size_t len);
2629
2737
 
2738
+ static ScrStr *sf_header_name_lower(const char *data, size_t len) {
2739
+ char *lower = malloc(len ? len : 1);
2740
+ if (!lower) sf_oom();
2741
+ for (size_t i = 0; i < len; i++) {
2742
+ char c = data[i];
2743
+ lower[i] = c >= 'A' && c <= 'Z' ? (char)(c - 'A' + 'a') : c;
2744
+ }
2745
+ ScrStr *out = scr_str_new(lower, len);
2746
+ free(lower);
2747
+ return out;
2748
+ }
2749
+
2630
2750
  /* HTTP field values are ByteStrings: each wire octet becomes the same
2631
2751
  * U+00XX code point in the JS string. ScrStr stores UTF-8, so obs-text
2632
2752
  * bytes need a real Latin-1→UTF-8 expansion before they enter a Headers
@@ -2670,11 +2790,12 @@ static ScrStr *sf_location_to_utf8(const ScrStr *raw) {
2670
2790
  return out;
2671
2791
  }
2672
2792
 
2673
- static SfHeaders *sf_headers_new(ScrArr *pairs) {
2793
+ static SfHeaders *sf_headers_new(ScrArr *pairs, bool immutable) {
2674
2794
  SfHeaders *h = calloc(1, sizeof *h);
2675
2795
  if (!h) sf_oom();
2676
2796
  h->rc = 1;
2677
2797
  h->pairs = pairs;
2798
+ h->immutable = immutable;
2678
2799
  return h;
2679
2800
  }
2680
2801
 
@@ -2682,13 +2803,16 @@ static SfHeaders *sf_headers_new_response(ScrArr *raw_pairs) {
2682
2803
  size_t n = (size_t)scr_arr_len(raw_pairs);
2683
2804
  ScrArr *pairs = scr_arr_new(SCR_ELEM_STR, n);
2684
2805
  for (size_t i = 0; i + 1 < n; i += 2) {
2685
- scr_arr_push_ref(pairs, scr_arr_get_ref(raw_pairs, (double)i));
2806
+ ScrStr *raw_name = scr_arr_get_ref(raw_pairs, (double)i);
2807
+ scr_arr_push_ref(
2808
+ pairs, sf_header_name_lower(raw_name->data, raw_name->len));
2809
+ scr_str_release(raw_name);
2686
2810
  ScrStr *raw = scr_arr_get_ref(raw_pairs, (double)(i + 1));
2687
2811
  scr_arr_push_ref(pairs, sf_latin1_to_utf8(raw));
2688
2812
  scr_str_release(raw);
2689
2813
  }
2690
2814
  scr_arr_release(raw_pairs);
2691
- return sf_headers_new(pairs);
2815
+ return sf_headers_new(pairs, true);
2692
2816
  }
2693
2817
 
2694
2818
  static SfHeaders *sf_headers_retain(SfHeaders *h) {
@@ -2710,24 +2834,38 @@ static void sf_headers_release_v(void *p) {
2710
2834
  sf_headers_release((SfHeaders *)p);
2711
2835
  }
2712
2836
 
2713
- static ScrStr *sf_headers_name(const ScrDyn *value) {
2837
+ static bool sf_header_value_ok(const ScrStr *value);
2838
+ static ScrStr *sf_header_value_bytestring(ScrStr *value);
2839
+ static ScrStr *sf_trim_header_value(const ScrStr *value);
2840
+
2841
+ static void sf_headers_invalid(
2842
+ const char *method, const ScrStr *value, const char *kind) {
2843
+ size_t cap = strlen(method) + value->len + strlen(kind) + 48;
2844
+ char *message = malloc(cap);
2845
+ if (!message) sf_oom();
2846
+ int head = snprintf(message, cap, "Headers.%s: \"", method);
2847
+ if (head < 0 || (size_t)head >= cap) sf_oom();
2848
+ size_t at = (size_t)head;
2849
+ memcpy(message + at, value->data, value->len);
2850
+ at += value->len;
2851
+ int tail = snprintf(
2852
+ message + at, cap - at,
2853
+ "\" is an invalid header %s.", kind);
2854
+ if (tail < 0 || (size_t)tail >= cap - at) sf_oom();
2855
+ scr_throw_error_msg(SCR_ERR_TYPE, message, at + (size_t)tail);
2856
+ free(message);
2857
+ }
2858
+
2859
+ static ScrStr *sf_headers_name(const ScrDyn *value, const char *method) {
2714
2860
  ScrStr *raw =
2715
2861
  scr_dyn_string_coerce_js(value ? value : scr_dyn_undefined());
2716
2862
  if (!raw) return NULL;
2717
2863
  if (!sf_header_name_ok(raw->data, raw->len)) {
2864
+ sf_headers_invalid(method, raw, "name");
2718
2865
  scr_str_release(raw);
2719
- sf_type_error("Headers: invalid header name");
2720
2866
  return NULL;
2721
2867
  }
2722
- char *lower = malloc(raw->len);
2723
- if (!lower) sf_oom();
2724
- for (size_t i = 0; i < raw->len; i++) {
2725
- char c = raw->data[i];
2726
- lower[i] =
2727
- c >= 'A' && c <= 'Z' ? (char)(c - 'A' + 'a') : c;
2728
- }
2729
- ScrStr *out = scr_str_new(lower, raw->len);
2730
- free(lower);
2868
+ ScrStr *out = sf_header_name_lower(raw->data, raw->len);
2731
2869
  scr_str_release(raw);
2732
2870
  return out;
2733
2871
  }
@@ -2778,6 +2916,92 @@ static ScrStr *sf_headers_get_value(SfHeaders *h, const ScrStr *name) {
2778
2916
  return out;
2779
2917
  }
2780
2918
 
2919
+ static void sf_headers_delete_name(SfHeaders *h, const ScrStr *name) {
2920
+ ScrArr *old = h->pairs;
2921
+ size_t n = (size_t)scr_arr_len(old);
2922
+ ScrArr *next = scr_arr_new(SCR_ELEM_STR, n);
2923
+ for (size_t i = 0; i + 1 < n; i += 2) {
2924
+ ScrStr *key = scr_arr_get_ref(old, (double)i);
2925
+ ScrStr *value = scr_arr_get_ref(old, (double)(i + 1));
2926
+ if (sf_headers_key_eq(key, name)) {
2927
+ scr_str_release(key);
2928
+ scr_str_release(value);
2929
+ continue;
2930
+ }
2931
+ scr_arr_push_ref(next, key);
2932
+ scr_arr_push_ref(next, value);
2933
+ }
2934
+ h->pairs = next;
2935
+ scr_arr_release(old);
2936
+ }
2937
+
2938
+ static bool sf_headers_convert_pair(
2939
+ const ScrDyn *name_value, const ScrDyn *value_value,
2940
+ const char *method, ScrStr **name_out, ScrStr **value_out) {
2941
+ ScrStr *name = sf_headers_name(name_value, method);
2942
+ if (!name) return false;
2943
+ ScrStr *raw = scr_dyn_string_coerce_js(
2944
+ value_value ? value_value : scr_dyn_undefined());
2945
+ ScrStr *bytes = raw ? sf_header_value_bytestring(raw) : NULL;
2946
+ scr_str_release(raw);
2947
+ if (!bytes) {
2948
+ scr_str_release(name);
2949
+ return false;
2950
+ }
2951
+ if (!sf_header_value_ok(bytes)) {
2952
+ ScrStr *display = sf_latin1_to_utf8(bytes);
2953
+ sf_headers_invalid(method, display, "value");
2954
+ scr_str_release(display);
2955
+ scr_str_release(bytes);
2956
+ scr_str_release(name);
2957
+ return false;
2958
+ }
2959
+ ScrStr *trimmed = sf_trim_header_value(bytes);
2960
+ scr_str_release(bytes);
2961
+ ScrStr *normalized = sf_latin1_to_utf8(trimmed);
2962
+ scr_str_release(trimmed);
2963
+ *name_out = name;
2964
+ *value_out = normalized;
2965
+ return true;
2966
+ }
2967
+
2968
+ /* Consumes a normalized lowercase name and UTF-8 value. Headers stores one
2969
+ * joined value for ordinary fields, separate values for Set-Cookie, and the
2970
+ * Fetch-specific semicolon separator for Cookie. */
2971
+ static void sf_headers_append_pair(
2972
+ SfHeaders *h, ScrStr *name, ScrStr *value) {
2973
+ if (!(name->len == 10 && memcmp(name->data, "set-cookie", 10) == 0)) {
2974
+ size_t n = (size_t)scr_arr_len(h->pairs);
2975
+ for (size_t i = 0; i + 1 < n; i += 2) {
2976
+ ScrStr *key = scr_arr_get_ref(h->pairs, (double)i);
2977
+ bool match = sf_headers_key_eq(key, name);
2978
+ scr_str_release(key);
2979
+ if (!match) continue;
2980
+
2981
+ ScrStr *previous = scr_arr_get_ref(h->pairs, (double)(i + 1));
2982
+ const char *separator =
2983
+ name->len == 6 && memcmp(name->data, "cookie", 6) == 0
2984
+ ? "; "
2985
+ : ", ";
2986
+ size_t joined_len = previous->len + 2 + value->len;
2987
+ char *joined_data = malloc(joined_len ? joined_len : 1);
2988
+ if (!joined_data) sf_oom();
2989
+ memcpy(joined_data, previous->data, previous->len);
2990
+ memcpy(joined_data + previous->len, separator, 2);
2991
+ memcpy(joined_data + previous->len + 2, value->data, value->len);
2992
+ ScrStr *joined = scr_str_new(joined_data, joined_len);
2993
+ free(joined_data);
2994
+ scr_str_release(previous);
2995
+ scr_str_release(name);
2996
+ scr_str_release(value);
2997
+ scr_arr_set_ref(h->pairs, (double)(i + 1), joined);
2998
+ return;
2999
+ }
3000
+ }
3001
+ scr_arr_push_ref(h->pairs, name);
3002
+ scr_arr_push_ref(h->pairs, value);
3003
+ }
3004
+
2781
3005
  static int sf_headers_name_cmp(const void *left, const void *right) {
2782
3006
  const ScrStr *a = *(ScrStr *const *)left;
2783
3007
  const ScrStr *b = *(ScrStr *const *)right;
@@ -2822,7 +3046,7 @@ static ScrDyn *sf_headers_invoke(void *ptr, ScrDyn *self,
2822
3046
  sf_type_error("Headers.get/has requires a header name");
2823
3047
  return NULL;
2824
3048
  }
2825
- ScrStr *name = sf_headers_name(args[0]);
3049
+ ScrStr *name = sf_headers_name(args[0], method);
2826
3050
  if (!name) return NULL;
2827
3051
  ScrStr *value = sf_headers_get_value(h, name);
2828
3052
  scr_str_release(name);
@@ -2859,12 +3083,25 @@ static ScrDyn *sf_headers_invoke(void *ptr, ScrDyn *self,
2859
3083
  ScrDyn *callback = args[0];
2860
3084
  const ScrDyn *this_arg =
2861
3085
  argc > 1 ? args[1] : scr_dyn_undefined();
2862
- size_t count = 0;
2863
- ScrStr **names = sf_headers_sorted_names(h, &count);
2864
- for (size_t i = 0; i < count; i++) {
3086
+ /* Headers iteration is live. Re-sort the current list for each numeric
3087
+ * index so callback mutations have the same effects as Node: deleting
3088
+ * a future name skips it, inserting a later name visits it, and
3089
+ * inserting before the current index can make the current name appear
3090
+ * again. The old one-time snapshot was only safe while every native
3091
+ * Headers handle was immutable. */
3092
+ for (size_t i = 0;; i++) {
3093
+ size_t count = 0;
3094
+ ScrStr **names = sf_headers_sorted_names(h, &count);
3095
+ if (i >= count) {
3096
+ for (size_t j = 0; j < count; j++) scr_str_release(names[j]);
3097
+ free(names);
3098
+ break;
3099
+ }
2865
3100
  ScrStr *value = sf_headers_get_value(h, names[i]);
2866
3101
  ScrDyn *boxed_value = scr_dyn_new_str(value);
2867
3102
  ScrDyn *boxed_name = scr_dyn_new_str(names[i]);
3103
+ for (size_t j = 0; j < count; j++) scr_str_release(names[j]);
3104
+ free(names);
2868
3105
  ScrDyn *call_args[3] = {boxed_value, boxed_name, self};
2869
3106
  scr_dyn_this_push_dyn(this_arg);
2870
3107
  ScrDyn *result =
@@ -2873,17 +3110,11 @@ static ScrDyn *sf_headers_invoke(void *ptr, ScrDyn *self,
2873
3110
  scr_dyn_release(boxed_value);
2874
3111
  scr_dyn_release(boxed_name);
2875
3112
  scr_str_release(value);
2876
- scr_str_release(names[i]);
2877
3113
  if (!result) {
2878
- for (size_t j = i + 1; j < count; j++) {
2879
- scr_str_release(names[j]);
2880
- }
2881
- free(names);
2882
3114
  return NULL;
2883
3115
  }
2884
3116
  scr_dyn_release(result);
2885
3117
  }
2886
- free(names);
2887
3118
  return scr_dyn_retain(scr_dyn_undefined());
2888
3119
  }
2889
3120
  if (strcmp(method, "append") == 0 || strcmp(method, "delete") == 0 ||
@@ -2893,8 +3124,33 @@ static ScrDyn *sf_headers_invoke(void *ptr, ScrDyn *self,
2893
3124
  sf_type_error("Headers mutation requires its declared arguments");
2894
3125
  return NULL;
2895
3126
  }
2896
- sf_type_error("immutable");
2897
- return NULL;
3127
+ if (strcmp(method, "delete") == 0) {
3128
+ ScrStr *name = sf_headers_name(args[0], method);
3129
+ if (!name) return NULL;
3130
+ if (h->immutable) {
3131
+ scr_str_release(name);
3132
+ sf_type_error("immutable");
3133
+ return NULL;
3134
+ }
3135
+ sf_headers_delete_name(h, name);
3136
+ scr_str_release(name);
3137
+ return scr_dyn_retain(scr_dyn_undefined());
3138
+ }
3139
+ ScrStr *name = NULL;
3140
+ ScrStr *value = NULL;
3141
+ if (!sf_headers_convert_pair(
3142
+ args[0], args[1], method, &name, &value)) {
3143
+ return NULL;
3144
+ }
3145
+ if (h->immutable) {
3146
+ scr_str_release(name);
3147
+ scr_str_release(value);
3148
+ sf_type_error("immutable");
3149
+ return NULL;
3150
+ }
3151
+ if (strcmp(method, "set") == 0) sf_headers_delete_name(h, name);
3152
+ sf_headers_append_pair(h, name, value);
3153
+ return scr_dyn_retain(scr_dyn_undefined());
2898
3154
  }
2899
3155
  sf_not_function(what);
2900
3156
  return NULL;
@@ -3121,6 +3377,7 @@ static void sf_reject_reason(SfTransfer *t, ScrDyn *reason) {
3121
3377
  static void sf_transfer_abort_watch(SfSignalWatch *w, SfSignal *signal) {
3122
3378
  SfTransfer *t = w->owner;
3123
3379
  if (t->done) return;
3380
+ ScrDyn *reason = sf_signal_reason_ref(signal);
3124
3381
  if (t->request_stream && t->request_stream->request_owner == t) {
3125
3382
  SfStream *request = t->request_stream;
3126
3383
  request->request_owner = NULL;
@@ -3132,11 +3389,12 @@ static void sf_transfer_abort_watch(SfSignalWatch *w, SfSignal *signal) {
3132
3389
  }
3133
3390
  if (t->client) scr_http_client_destroy(t->client);
3134
3391
  if (t->response_sent && t->response_stream) {
3135
- sf_stream_error(t->response_stream, signal->reason);
3392
+ sf_stream_error(t->response_stream, reason);
3136
3393
  sf_settle(t);
3137
3394
  } else {
3138
- sf_reject_reason(t, signal->reason);
3395
+ sf_reject_reason(t, reason);
3139
3396
  }
3397
+ scr_dyn_release(reason);
3140
3398
  }
3141
3399
 
3142
3400
  static void sf_transfer_stream_error(SfTransfer *t, ScrDyn *reason) {
@@ -3649,20 +3907,29 @@ static ScrStr *sf_trim_header_value(const ScrStr *value) {
3649
3907
  }
3650
3908
 
3651
3909
  static bool sf_push_header(ScrArr *pairs, const char *name, size_t name_len,
3652
- ScrStr *value) {
3910
+ ScrStr *value, bool request_guard) {
3653
3911
  if (!sf_header_name_ok(name, name_len)) {
3654
- sf_type_error("fetch failed");
3912
+ ScrStr *display = scr_str_new(name, name_len);
3913
+ sf_headers_invalid("append", display, "name");
3914
+ scr_str_release(display);
3655
3915
  return false;
3656
3916
  }
3657
3917
  ScrStr *byte_value = sf_header_value_bytestring(value);
3658
3918
  if (!byte_value) return false;
3659
3919
  if (!sf_header_value_ok(byte_value)) {
3920
+ ScrStr *display = sf_latin1_to_utf8(byte_value);
3921
+ sf_headers_invalid("append", display, "value");
3922
+ scr_str_release(display);
3660
3923
  scr_str_release(byte_value);
3661
- sf_type_error("fetch failed");
3662
3924
  return false;
3663
3925
  }
3664
3926
  ScrStr *normalized = sf_trim_header_value(byte_value);
3665
3927
  scr_str_release(byte_value);
3928
+ if (!request_guard) {
3929
+ ScrStr *utf8 = sf_latin1_to_utf8(normalized);
3930
+ scr_str_release(normalized);
3931
+ normalized = utf8;
3932
+ }
3666
3933
  if (!sf_header_name_ci(name, name_len, "set-cookie")) {
3667
3934
  size_t n = (size_t)scr_arr_len(pairs);
3668
3935
  for (size_t i = 0; i + 1 < n; i += 2) {
@@ -3695,7 +3962,7 @@ static bool sf_push_header(ScrArr *pairs, const char *name, size_t name_len,
3695
3962
  free(joined_data);
3696
3963
  scr_str_release(previous);
3697
3964
  scr_str_release(normalized);
3698
- if (!sf_request_header_ok(name, name_len, joined)) {
3965
+ if (request_guard && !sf_request_header_ok(name, name_len, joined)) {
3699
3966
  scr_str_release(joined);
3700
3967
  sf_type_error("fetch failed");
3701
3968
  return false;
@@ -3704,63 +3971,125 @@ static bool sf_push_header(ScrArr *pairs, const char *name, size_t name_len,
3704
3971
  return true;
3705
3972
  }
3706
3973
  }
3707
- if (!sf_request_header_ok(name, name_len, normalized)) {
3974
+ if (request_guard && !sf_request_header_ok(name, name_len, normalized)) {
3708
3975
  scr_str_release(normalized);
3709
3976
  sf_type_error("fetch failed");
3710
3977
  return false;
3711
3978
  }
3712
- scr_arr_push_ref(pairs, scr_str_new(name, name_len));
3979
+ scr_arr_push_ref(
3980
+ pairs,
3981
+ request_guard
3982
+ ? scr_str_new(name, name_len)
3983
+ : sf_header_name_lower(name, name_len));
3713
3984
  scr_arr_push_ref(pairs, normalized);
3714
3985
  return true;
3715
3986
  }
3716
3987
 
3717
- static bool sf_add_headers(ScrArr *pairs, const ScrDyn *headers) {
3718
- if (!headers || headers->kind == SCR_DYN_UNDEF ||
3719
- headers->kind == SCR_DYN_NULL) {
3720
- return true;
3988
+ static void sf_headers_constructor_type_error(void) {
3989
+ static const char message[] =
3990
+ "Headers constructor: Argument 1 could not be converted to one of: "
3991
+ "sequence<sequence<ByteString>>, record<ByteString, ByteString>.";
3992
+ sf_type_error(message);
3993
+ }
3994
+
3995
+ static void sf_headers_pair_length_error(size_t len) {
3996
+ char message[96];
3997
+ int n = snprintf(
3998
+ message, sizeof message,
3999
+ "Headers constructor: expected name/value pair to be length 2, "
4000
+ "found %zu.", len);
4001
+ if (n < 0 || (size_t)n >= sizeof message) sf_oom();
4002
+ sf_type_error(message);
4003
+ }
4004
+
4005
+ /* The WebIDL HeadersInit conversion phase. It performs the observable
4006
+ * sequence/record walk and every ByteString coercion, but deliberately
4007
+ * leaves header-name/value syntax validation for initialization. Response
4008
+ * construction converts this snapshot before status/statusText and only
4009
+ * validates it after body extraction, matching Undici's two phases. */
4010
+ static ScrArr *sf_headers_convert_init(const ScrDyn *headers) {
4011
+ if (headers && headers->kind == SCR_DYN_TYPED_REF) {
4012
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(headers);
4013
+ ScrArr *converted = sf_headers_convert_init(materialized);
4014
+ scr_dyn_release(materialized);
4015
+ return converted;
4016
+ }
4017
+ ScrArr *converted = scr_arr_new(SCR_ELEM_STR, 8);
4018
+ if (!headers || headers->kind == SCR_DYN_UNDEF) {
4019
+ return converted;
3721
4020
  }
3722
4021
  if (headers->kind == SCR_DYN_OBJ) {
3723
4022
  for (size_t i = 0; i < headers->v.obj.len; i++) {
3724
4023
  const ScrDynEntry *e = &headers->v.obj.entries[i];
3725
- ScrStr *name = scr_str_new(e->key, e->key_len);
4024
+ ScrStr *raw_name = scr_str_new(e->key, e->key_len);
4025
+ ScrStr *name_bytes = sf_header_value_bytestring(raw_name);
4026
+ ScrStr *name = name_bytes ? sf_latin1_to_utf8(name_bytes) : NULL;
4027
+ scr_str_release(name_bytes);
4028
+ scr_str_release(raw_name);
3726
4029
  ScrDyn *source = scr_dyn_retain(e->value);
3727
- ScrStr *value = scr_dyn_string_coerce_js(source);
4030
+ ScrStr *raw_value = name ? scr_dyn_string_coerce_js(source) : NULL;
3728
4031
  scr_dyn_release(source);
3729
- bool ok = value && sf_push_header(pairs, name->data, name->len, value);
3730
- scr_str_release(value);
3731
- scr_str_release(name);
3732
- if (!ok) {
3733
- if (!scr_exc_pending()) sf_type_error("fetch failed");
3734
- return false;
4032
+ ScrStr *value_bytes = raw_value
4033
+ ? sf_header_value_bytestring(raw_value)
4034
+ : NULL;
4035
+ ScrStr *value = value_bytes ? sf_latin1_to_utf8(value_bytes) : NULL;
4036
+ scr_str_release(value_bytes);
4037
+ scr_str_release(raw_value);
4038
+ if (!name || !value) {
4039
+ scr_str_release(name);
4040
+ scr_str_release(value);
4041
+ goto fail;
3735
4042
  }
4043
+ scr_arr_push_ref(converted, name);
4044
+ scr_arr_push_ref(converted, value);
3736
4045
  }
3737
- return true;
4046
+ return converted;
3738
4047
  }
3739
4048
  if (headers->kind == SCR_DYN_ARR) {
3740
4049
  for (size_t i = 0; i < headers->v.arr.len; i++) {
3741
4050
  ScrDyn *pair = scr_dyn_retain(headers->v.arr.items[i]);
3742
4051
  if (pair->kind != SCR_DYN_ARR || pair->v.arr.len != 2) {
4052
+ size_t pair_len = pair->kind == SCR_DYN_ARR ? pair->v.arr.len : 0;
3743
4053
  scr_dyn_release(pair);
3744
- if (!scr_exc_pending()) sf_type_error("fetch failed");
3745
- return false;
4054
+ if (!scr_exc_pending()) {
4055
+ if (pair_len > 0 || headers->v.arr.items[i]->kind == SCR_DYN_ARR) {
4056
+ sf_headers_pair_length_error(pair_len);
4057
+ } else {
4058
+ sf_headers_constructor_type_error();
4059
+ }
4060
+ }
4061
+ goto fail;
3746
4062
  }
3747
4063
  ScrDyn *name_source = scr_dyn_retain(pair->v.arr.items[0]);
3748
4064
  ScrDyn *value_source = scr_dyn_retain(pair->v.arr.items[1]);
3749
4065
  scr_dyn_release(pair);
3750
- ScrStr *name = scr_dyn_string_coerce_js(name_source);
4066
+ ScrStr *raw_name = scr_dyn_string_coerce_js(name_source);
3751
4067
  scr_dyn_release(name_source);
3752
- ScrStr *value = name ? scr_dyn_string_coerce_js(value_source) : NULL;
4068
+ ScrStr *name_bytes = raw_name
4069
+ ? sf_header_value_bytestring(raw_name)
4070
+ : NULL;
4071
+ ScrStr *name = name_bytes ? sf_latin1_to_utf8(name_bytes) : NULL;
4072
+ scr_str_release(name_bytes);
4073
+ scr_str_release(raw_name);
4074
+ ScrStr *raw_value = name
4075
+ ? scr_dyn_string_coerce_js(value_source)
4076
+ : NULL;
3753
4077
  scr_dyn_release(value_source);
3754
- bool ok = name && value &&
3755
- sf_push_header(pairs, name->data, name->len, value);
3756
- scr_str_release(value);
3757
- scr_str_release(name);
3758
- if (!ok) {
3759
- if (!scr_exc_pending()) sf_type_error("fetch failed");
3760
- return false;
4078
+ ScrStr *value_bytes = raw_value
4079
+ ? sf_header_value_bytestring(raw_value)
4080
+ : NULL;
4081
+ ScrStr *value = value_bytes ? sf_latin1_to_utf8(value_bytes) : NULL;
4082
+ scr_str_release(value_bytes);
4083
+ scr_str_release(raw_value);
4084
+ if (!name || !value) {
4085
+ scr_str_release(name);
4086
+ scr_str_release(value);
4087
+ goto fail;
3761
4088
  }
4089
+ scr_arr_push_ref(converted, name);
4090
+ scr_arr_push_ref(converted, value);
3762
4091
  }
3763
- return true;
4092
+ return converted;
3764
4093
  }
3765
4094
  if (headers->kind == SCR_DYN_HANDLE &&
3766
4095
  headers->v.handle.tag == SCR_DYNH_FETCH_HEADERS) {
@@ -3770,15 +4099,44 @@ static bool sf_add_headers(ScrArr *pairs, const ScrDyn *headers) {
3770
4099
  ScrStr *name = scr_arr_get_ref(source->pairs, (double)i);
3771
4100
  ScrStr *value =
3772
4101
  scr_arr_get_ref(source->pairs, (double)(i + 1));
3773
- bool ok = sf_push_header(pairs, name->data, name->len, value);
3774
- scr_str_release(name);
3775
- scr_str_release(value);
3776
- if (!ok) return false;
4102
+ scr_arr_push_ref(converted, name);
4103
+ scr_arr_push_ref(converted, value);
3777
4104
  }
3778
- return true;
4105
+ return converted;
3779
4106
  }
3780
- sf_type_error("fetch failed");
3781
- return false;
4107
+ /* Functions are WebIDL objects and an ordinary function has no
4108
+ * enumerable HeadersInit entries. The checked-dynamic function model's
4109
+ * expando properties are non-enumerable by construction. */
4110
+ if (headers->kind == SCR_DYN_FUNC) return converted;
4111
+ sf_headers_constructor_type_error();
4112
+
4113
+ fail:
4114
+ scr_arr_release(converted);
4115
+ return NULL;
4116
+ }
4117
+
4118
+ static bool sf_add_converted_headers(
4119
+ ScrArr *pairs, ScrArr *converted, bool request_guard) {
4120
+ size_t n = (size_t)scr_arr_len(converted);
4121
+ for (size_t i = 0; i + 1 < n; i += 2) {
4122
+ ScrStr *name = scr_arr_get_ref(converted, (double)i);
4123
+ ScrStr *value = scr_arr_get_ref(converted, (double)(i + 1));
4124
+ bool ok = sf_push_header(
4125
+ pairs, name->data, name->len, value, request_guard);
4126
+ scr_str_release(name);
4127
+ scr_str_release(value);
4128
+ if (!ok) return false;
4129
+ }
4130
+ return true;
4131
+ }
4132
+
4133
+ static bool sf_add_headers(
4134
+ ScrArr *pairs, const ScrDyn *headers, bool request_guard) {
4135
+ ScrArr *converted = sf_headers_convert_init(headers);
4136
+ if (!converted) return false;
4137
+ bool ok = sf_add_converted_headers(pairs, converted, request_guard);
4138
+ scr_arr_release(converted);
4139
+ return ok;
3782
4140
  }
3783
4141
 
3784
4142
  static void sf_push_header_text(ScrArr *pairs, const char *name,
@@ -4595,6 +4953,213 @@ static bool sf_start_hop(SfTransfer *t) {
4595
4953
  return true;
4596
4954
  }
4597
4955
 
4956
+ static bool sf_response_init_object_like(const ScrDyn *init) {
4957
+ if (!init) return false;
4958
+ switch (init->kind) {
4959
+ case SCR_DYN_OBJ:
4960
+ case SCR_DYN_ARR:
4961
+ case SCR_DYN_BYTES:
4962
+ case SCR_DYN_FUNC:
4963
+ case SCR_DYN_HANDLE:
4964
+ case SCR_DYN_PROMISE:
4965
+ case SCR_DYN_TYPED_REF:
4966
+ case SCR_DYN_JSVAL:
4967
+ return true;
4968
+ default:
4969
+ return false;
4970
+ }
4971
+ }
4972
+
4973
+ /* Owned property read for the checked-dynamic object kinds that can carry
4974
+ * ResponseInit dictionary members. Arrays/bytes/native handles/promises
4975
+ * have no such expandos in the static model and therefore answer absent. */
4976
+ static ScrDyn *sf_response_init_get(
4977
+ const ScrDyn *init, const char *key, size_t len) {
4978
+ if (!init) return NULL;
4979
+ if (init->kind == SCR_DYN_OBJ) {
4980
+ ScrDyn *value = scr_dyn_obj_get(init, key, len);
4981
+ return value ? scr_dyn_retain(value) : NULL;
4982
+ }
4983
+ if (init->kind == SCR_DYN_FUNC) {
4984
+ return scr_dyn_fn_get(init, key, len);
4985
+ }
4986
+ if (init->kind == SCR_DYN_HANDLE &&
4987
+ init->v.handle.tag == SCR_DYNH_FETCH_RESPONSE) {
4988
+ return sf_response_get(init->v.handle.ptr, key, len);
4989
+ }
4990
+ if (init->kind == SCR_DYN_TYPED_REF) {
4991
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(init);
4992
+ ScrDyn *value = sf_response_init_get(materialized, key, len);
4993
+ scr_dyn_release(materialized);
4994
+ return value;
4995
+ }
4996
+ return NULL;
4997
+ }
4998
+
4999
+ ScrDyn *scr_fetch_response_new(ScrDyn *body, ScrDyn *init) {
5000
+ ScrArr *header_pairs = NULL;
5001
+ ScrArr *converted_headers = NULL;
5002
+ ScrBytes *body_bytes = NULL;
5003
+ ScrStr *status_text = NULL;
5004
+ SfStream *stream = NULL;
5005
+ bool null_body =
5006
+ !body || body->kind == SCR_DYN_UNDEF || body->kind == SCR_DYN_NULL;
5007
+ bool text_body = false;
5008
+ int status = 200;
5009
+ bool status_range_error = false;
5010
+
5011
+ /* WebIDL converts constructor arguments from left to right. BodyInit's
5012
+ * string arm therefore runs before ResponseInit's dictionary conversion,
5013
+ * while a ReadableStream is only brand-converted here — its locked/
5014
+ * disturbed state belongs to the later body-extraction phase. */
5015
+ if (!null_body &&
5016
+ !(body->kind == SCR_DYN_HANDLE &&
5017
+ body->v.handle.tag == SCR_DYNH_WEB_STREAM)) {
5018
+ if (body->kind == SCR_DYN_BYTES) {
5019
+ body_bytes = scr_bytes_copy(body->v.bytes);
5020
+ } else {
5021
+ ScrStr *text =
5022
+ body->kind == SCR_DYN_STR
5023
+ ? scr_str_retain(body->v.str)
5024
+ : scr_dyn_string_coerce_js(body);
5025
+ if (!text) goto fail;
5026
+ ScrStr *encoding = scr_str_new("utf8", 4);
5027
+ body_bytes = scr_bytes_from_str(text, encoding);
5028
+ scr_str_release(encoding);
5029
+ scr_str_release(text);
5030
+ text_body = true;
5031
+ }
5032
+ }
5033
+
5034
+ if (init && init->kind != SCR_DYN_UNDEF && init->kind != SCR_DYN_NULL &&
5035
+ !sf_response_init_object_like(init)) {
5036
+ sf_type_error("Response constructor init must be an object");
5037
+ goto fail;
5038
+ }
5039
+
5040
+ /* ResponseInit's WebIDL dictionary conversion is lexicographic and the
5041
+ * nested HeadersInit conversion is observable: headers' ByteString
5042
+ * coercions precede status and statusText. Header syntax validation is a
5043
+ * later response-initialization step, after body extraction. */
5044
+ ScrDyn *init_headers = sf_response_init_get(init, "headers", 7);
5045
+ converted_headers = sf_headers_convert_init(init_headers);
5046
+ scr_dyn_release(init_headers);
5047
+ if (!converted_headers) goto fail;
5048
+
5049
+ if (init && sf_response_init_object_like(init)) {
5050
+ ScrDyn *status_value = sf_response_init_get(init, "status", 6);
5051
+ if (status_value && status_value->kind != SCR_DYN_UNDEF) {
5052
+ double converted;
5053
+ if (!scr_dyn_number_coerce_js(status_value, &converted)) {
5054
+ scr_dyn_release(status_value);
5055
+ goto fail;
5056
+ }
5057
+ converted = trunc(converted);
5058
+ if (!isfinite(converted) || converted < 0.0) {
5059
+ status_range_error = true;
5060
+ } else {
5061
+ if (converted > 65535.0) converted = fmod(converted, 65536.0);
5062
+ status = (int)converted;
5063
+ }
5064
+ }
5065
+ scr_dyn_release(status_value);
5066
+
5067
+ ScrDyn *status_text_value =
5068
+ sf_response_init_get(init, "statusText", 10);
5069
+ if (status_text_value && status_text_value->kind != SCR_DYN_UNDEF) {
5070
+ ScrStr *raw = scr_dyn_string_coerce_js(status_text_value);
5071
+ ScrStr *bytes = raw ? sf_header_value_bytestring(raw) : NULL;
5072
+ scr_str_release(raw);
5073
+ status_text = bytes ? sf_latin1_to_utf8(bytes) : NULL;
5074
+ scr_str_release(bytes);
5075
+ if (!status_text) {
5076
+ scr_dyn_release(status_text_value);
5077
+ goto fail;
5078
+ }
5079
+ }
5080
+ scr_dyn_release(status_text_value);
5081
+ }
5082
+ if (!status_text) status_text = scr_str_new("", 0);
5083
+
5084
+ /* Extract BodyInit before validating the response metadata. This makes a
5085
+ * locked/disturbed stream win over the later status range, statusText HTTP
5086
+ * reason-phrase, and HeadersInit validation exactly as in pinned Node. */
5087
+ if (null_body) {
5088
+ stream = sf_stream_new_native();
5089
+ sf_stream_close(stream);
5090
+ } else if (body->kind == SCR_DYN_HANDLE &&
5091
+ body->v.handle.tag == SCR_DYNH_WEB_STREAM) {
5092
+ stream = (SfStream *)body->v.handle.ptr;
5093
+ if (stream->reader || stream->internal_lock || stream->disturbed) {
5094
+ stream = NULL; /* borrowed until the successful retain below */
5095
+ sf_type_error("Response body object should not be disturbed or locked");
5096
+ goto fail;
5097
+ }
5098
+ sf_stream_retain(stream);
5099
+ } else {
5100
+ stream = sf_stream_new_native();
5101
+ sf_stream_enqueue_bytes(stream, body_bytes);
5102
+ sf_stream_close(stream);
5103
+ scr_bytes_release(body_bytes);
5104
+ body_bytes = NULL;
5105
+ }
5106
+
5107
+ if (status_range_error || status < 200 || status > 599) {
5108
+ static const char message[] =
5109
+ "init[\"status\"] must be in the range of 200 to 599, inclusive.";
5110
+ scr_throw_error_msg(SCR_ERR_RANGE, message, sizeof message - 1);
5111
+ goto fail;
5112
+ }
5113
+ if (!sf_header_value_ok(status_text)) {
5114
+ sf_type_error("Invalid statusText");
5115
+ goto fail;
5116
+ }
5117
+
5118
+ header_pairs = scr_arr_new(SCR_ELEM_STR, 8);
5119
+ if (!sf_add_converted_headers(
5120
+ header_pairs, converted_headers, false)) goto fail;
5121
+ scr_arr_release(converted_headers);
5122
+ converted_headers = NULL;
5123
+
5124
+ if (!null_body &&
5125
+ (status == 204 || status == 205 || status == 304)) {
5126
+ char message[80];
5127
+ int len = snprintf(message, sizeof message,
5128
+ "Response constructor: Invalid response status code %d",
5129
+ status);
5130
+ if (len < 0 || (size_t)len >= sizeof message) sf_oom();
5131
+ sf_type_error(message);
5132
+ goto fail;
5133
+ }
5134
+ if (text_body && !sf_pairs_have(header_pairs, "content-type")) {
5135
+ sf_push_header_text(header_pairs, "content-type",
5136
+ "text/plain;charset=UTF-8", 24);
5137
+ }
5138
+
5139
+ SfResponse *response = calloc(1, sizeof *response);
5140
+ if (!response) sf_oom();
5141
+ response->rc = 1;
5142
+ response->body = stream;
5143
+ response->headers = sf_headers_new(header_pairs, false);
5144
+ response->url = scr_str_new("", 0);
5145
+ response->status_text = status_text;
5146
+ response->status = status;
5147
+ response->redirected = false;
5148
+ response->null_body = null_body;
5149
+ ScrDyn *out =
5150
+ scr_dyn_new_handle(response, SCR_DYNH_FETCH_RESPONSE);
5151
+ sf_response_release(response);
5152
+ return out;
5153
+
5154
+ fail:
5155
+ sf_stream_release(stream);
5156
+ scr_bytes_release(body_bytes);
5157
+ scr_str_release(status_text);
5158
+ scr_arr_release(header_pairs);
5159
+ scr_arr_release(converted_headers);
5160
+ return NULL;
5161
+ }
5162
+
4598
5163
  ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init) {
4599
5164
  ScrPromise *promise = scr_promise_new();
4600
5165
  if (init && init->kind != SCR_DYN_OBJ &&
@@ -4686,7 +5251,7 @@ ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init) {
4686
5251
  init && init->kind == SCR_DYN_OBJ
4687
5252
  ? scr_dyn_obj_get(init, "headers", 7)
4688
5253
  : NULL;
4689
- if (!sf_add_headers(headers, init_headers)) {
5254
+ if (!sf_add_headers(headers, init_headers, true)) {
4690
5255
  scr_dyn_release(coerced_body);
4691
5256
  scr_arr_release(headers);
4692
5257
  return sf_reject_now(promise, "fetch failed");
@@ -4826,7 +5391,9 @@ ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init) {
4826
5391
  scr_arr_release(headers);
4827
5392
  scr_str_release(method);
4828
5393
  scr_url_release(u);
4829
- sf_reject_promise_reason(promise, signal->reason);
5394
+ ScrDyn *reason = sf_signal_reason_ref(signal);
5395
+ sf_reject_promise_reason(promise, reason);
5396
+ scr_dyn_release(reason);
4830
5397
  return promise;
4831
5398
  }
4832
5399
 
@@ -4867,6 +5434,9 @@ static bool sf_no_set(void *ptr, const char *key, size_t len,
4867
5434
  static const ScrDynHandleOps sf_signal_ops = {
4868
5435
  "AbortSignal", &sf_signal_retain_v, &sf_signal_release_v,
4869
5436
  &sf_signal_invoke, &sf_signal_get, &sf_signal_set, NULL};
5437
+ static const ScrDynHandleOps sf_abort_controller_ops = {
5438
+ "AbortController", &sf_signal_retain_v, &sf_signal_release_v,
5439
+ &sf_abort_controller_invoke, &sf_abort_controller_get, &sf_no_set, NULL};
4870
5440
  static const ScrDynHandleOps sf_stream_ops = {
4871
5441
  "ReadableStream", &sf_stream_retain_v, &sf_stream_release_v,
4872
5442
  &sf_stream_invoke, &sf_stream_get, &sf_no_set, NULL};
@@ -4907,6 +5477,9 @@ static void sf_teardown(void) {
4907
5477
  while (sf_callback_signals) {
4908
5478
  sf_signal_drop_callbacks(sf_callback_signals);
4909
5479
  }
5480
+ while (sf_reason_signals) {
5481
+ sf_signal_drop_reason(sf_reason_signals);
5482
+ }
4910
5483
  sf_proxy_snapshot_free();
4911
5484
  }
4912
5485
 
@@ -4918,6 +5491,8 @@ void scr_fetch_install(void) {
4918
5491
  sf_proxy_snapshot();
4919
5492
  scr_net_install();
4920
5493
  scr_dyn_handle_install(SCR_DYNH_ABORT_SIGNAL, &sf_signal_ops);
5494
+ scr_dyn_handle_install(SCR_DYNH_ABORT_CONTROLLER,
5495
+ &sf_abort_controller_ops);
4921
5496
  scr_dyn_handle_install(SCR_DYNH_WEB_STREAM, &sf_stream_ops);
4922
5497
  scr_dyn_handle_install(SCR_DYNH_WEB_READER, &sf_reader_ops);
4923
5498
  scr_dyn_handle_install(SCR_DYNH_WEB_CONTROLLER, &sf_controller_ops);