quickjs 0.13.0 → 0.15.0
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.
- checksums.yaml +4 -4
- data/README.md +32 -2
- data/Rakefile +7 -0
- data/ext/quickjsrb/extconf.rb +8 -2
- data/ext/quickjsrb/quickjsrb.c +389 -65
- data/ext/quickjsrb/quickjsrb.h +6 -4
- data/ext/quickjsrb/quickjsrb_crypto.c +71 -0
- data/ext/quickjsrb/quickjsrb_crypto.h +6 -0
- data/ext/quickjsrb/quickjsrb_crypto_subtle.c +1001 -0
- data/ext/quickjsrb/quickjsrb_crypto_subtle.h +6 -0
- data/ext/quickjsrb/vendor/polyfill-intl-en.min.js +3 -11
- data/ext/quickjsrb/vendor/polyfill-url.min.js +1 -0
- data/lib/quickjs/crypto_key.rb +15 -0
- data/lib/quickjs/function.rb +36 -0
- data/lib/quickjs/subtle_crypto.rb +493 -0
- data/lib/quickjs/version.rb +1 -1
- data/lib/quickjs.rb +4 -0
- data/polyfills/check-licenses.mjs +72 -0
- data/polyfills/package-lock.json +183 -154
- data/polyfills/package.json +9 -8
- data/polyfills/rolldown.config.mjs +8 -0
- data/polyfills/src/url.js +1089 -0
- data/sig/quickjs.rbs +15 -1
- metadata +12 -2
data/ext/quickjsrb/quickjsrb.c
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#include "quickjsrb.h"
|
|
2
2
|
#include "quickjsrb_file.h"
|
|
3
|
+
#include "quickjsrb_crypto.h"
|
|
3
4
|
|
|
4
5
|
const char *featureStdId = "feature_std";
|
|
5
6
|
const char *featureOsId = "feature_os";
|
|
@@ -7,6 +8,8 @@ const char *featureTimeoutId = "feature_timeout";
|
|
|
7
8
|
const char *featurePolyfillIntlId = "feature_polyfill_intl";
|
|
8
9
|
const char *featurePolyfillFileId = "feature_polyfill_file";
|
|
9
10
|
const char *featurePolyfillEncodingId = "feature_polyfill_encoding";
|
|
11
|
+
const char *featurePolyfillUrlId = "feature_polyfill_url";
|
|
12
|
+
const char *featurePolyfillCryptoId = "feature_polyfill_crypto";
|
|
10
13
|
|
|
11
14
|
const char *undefinedId = "undefined";
|
|
12
15
|
const char *nanId = "NaN";
|
|
@@ -23,6 +26,9 @@ const int num_native_errors = sizeof(native_errors) / sizeof(native_errors[0]);
|
|
|
23
26
|
|
|
24
27
|
static int dispatch_log(VMData *data, const char *severity, VALUE r_row);
|
|
25
28
|
|
|
29
|
+
JSValue to_js_value(JSContext *ctx, VALUE r_value);
|
|
30
|
+
VALUE to_rb_value(JSContext *ctx, JSValue j_val);
|
|
31
|
+
|
|
26
32
|
JSValue j_error_from_ruby_error(JSContext *ctx, VALUE r_error)
|
|
27
33
|
{
|
|
28
34
|
JSValue j_error = JS_NewError(ctx); // may wanna have custom error class to determine in JS' end
|
|
@@ -42,6 +48,20 @@ JSValue j_error_from_ruby_error(JSContext *ctx, VALUE r_error)
|
|
|
42
48
|
return j_error;
|
|
43
49
|
}
|
|
44
50
|
|
|
51
|
+
typedef struct
|
|
52
|
+
{
|
|
53
|
+
JSContext *ctx;
|
|
54
|
+
JSValue j_obj;
|
|
55
|
+
} RbHashToJsArg;
|
|
56
|
+
|
|
57
|
+
static int rb_hash_entry_to_js(VALUE r_key, VALUE r_val, VALUE extra)
|
|
58
|
+
{
|
|
59
|
+
RbHashToJsArg *arg = (RbHashToJsArg *)extra;
|
|
60
|
+
VALUE r_key_str = rb_funcall(r_key, rb_intern("to_s"), 0);
|
|
61
|
+
JS_SetPropertyStr(arg->ctx, arg->j_obj, StringValueCStr(r_key_str), to_js_value(arg->ctx, r_val));
|
|
62
|
+
return ST_CONTINUE;
|
|
63
|
+
}
|
|
64
|
+
|
|
45
65
|
JSValue to_js_value(JSContext *ctx, VALUE r_value)
|
|
46
66
|
{
|
|
47
67
|
switch (TYPE(r_value))
|
|
@@ -71,6 +91,15 @@ JSValue to_js_value(JSContext *ctx, VALUE r_value)
|
|
|
71
91
|
}
|
|
72
92
|
case T_SYMBOL:
|
|
73
93
|
{
|
|
94
|
+
if (r_value == QUICKJSRB_SYM(undefinedId))
|
|
95
|
+
return JS_UNDEFINED;
|
|
96
|
+
if (r_value == QUICKJSRB_SYM(nanId))
|
|
97
|
+
{
|
|
98
|
+
JSValue j_global = JS_GetGlobalObject(ctx);
|
|
99
|
+
JSValue j_nan = JS_GetPropertyStr(ctx, j_global, "NaN");
|
|
100
|
+
JS_FreeValue(ctx, j_global);
|
|
101
|
+
return j_nan;
|
|
102
|
+
}
|
|
74
103
|
VALUE r_str = rb_funcall(r_value, rb_intern("to_s"), 0);
|
|
75
104
|
char *str = StringValueCStr(r_str);
|
|
76
105
|
|
|
@@ -80,14 +109,22 @@ JSValue to_js_value(JSContext *ctx, VALUE r_value)
|
|
|
80
109
|
return JS_TRUE;
|
|
81
110
|
case T_FALSE:
|
|
82
111
|
return JS_FALSE;
|
|
83
|
-
case T_HASH:
|
|
84
112
|
case T_ARRAY:
|
|
85
113
|
{
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
114
|
+
int len = RARRAY_LEN(r_value);
|
|
115
|
+
JSValue j_arr = JS_NewArray(ctx);
|
|
116
|
+
for (int i = 0; i < len; i++)
|
|
117
|
+
{
|
|
118
|
+
JS_SetPropertyUint32(ctx, j_arr, (uint32_t)i, to_js_value(ctx, RARRAY_AREF(r_value, i)));
|
|
119
|
+
}
|
|
120
|
+
return j_arr;
|
|
121
|
+
}
|
|
122
|
+
case T_HASH:
|
|
123
|
+
{
|
|
124
|
+
JSValue j_obj = JS_NewObject(ctx);
|
|
125
|
+
RbHashToJsArg arg = {ctx, j_obj};
|
|
126
|
+
rb_hash_foreach(r_value, rb_hash_entry_to_js, (VALUE)&arg);
|
|
127
|
+
return j_obj;
|
|
91
128
|
}
|
|
92
129
|
default:
|
|
93
130
|
{
|
|
@@ -154,6 +191,59 @@ VALUE to_r_json(JSContext *ctx, JSValue j_val)
|
|
|
154
191
|
return r_str;
|
|
155
192
|
}
|
|
156
193
|
|
|
194
|
+
static int js_is_plain_object(JSContext *ctx, JSValue j_val)
|
|
195
|
+
{
|
|
196
|
+
JSValue j_proto = JS_GetPrototype(ctx, j_val);
|
|
197
|
+
if (JS_IsNull(j_proto))
|
|
198
|
+
return 1; // Object.create(null)
|
|
199
|
+
JSValue j_global = JS_GetGlobalObject(ctx);
|
|
200
|
+
JSValue j_Object = JS_GetPropertyStr(ctx, j_global, "Object");
|
|
201
|
+
JSValue j_Object_proto = JS_GetPropertyStr(ctx, j_Object, "prototype");
|
|
202
|
+
int result = JS_StrictEq(ctx, j_proto, j_Object_proto);
|
|
203
|
+
JS_FreeValue(ctx, j_proto);
|
|
204
|
+
JS_FreeValue(ctx, j_global);
|
|
205
|
+
JS_FreeValue(ctx, j_Object);
|
|
206
|
+
JS_FreeValue(ctx, j_Object_proto);
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
static VALUE js_array_to_rb(JSContext *ctx, JSValue j_val)
|
|
211
|
+
{
|
|
212
|
+
JSValue j_length = JS_GetPropertyStr(ctx, j_val, "length");
|
|
213
|
+
uint32_t length = 0;
|
|
214
|
+
JS_ToUint32(ctx, &length, j_length);
|
|
215
|
+
JS_FreeValue(ctx, j_length);
|
|
216
|
+
|
|
217
|
+
VALUE r_array = rb_ary_new_capa(length);
|
|
218
|
+
for (uint32_t i = 0; i < length; i++)
|
|
219
|
+
{
|
|
220
|
+
JSValue j_elem = JS_GetPropertyUint32(ctx, j_val, i);
|
|
221
|
+
rb_ary_push(r_array, to_rb_value(ctx, j_elem));
|
|
222
|
+
JS_FreeValue(ctx, j_elem);
|
|
223
|
+
}
|
|
224
|
+
return r_array;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
static VALUE js_plain_object_to_rb(JSContext *ctx, JSValue j_val)
|
|
228
|
+
{
|
|
229
|
+
JSPropertyEnum *ptab;
|
|
230
|
+
uint32_t plen;
|
|
231
|
+
if (JS_GetOwnPropertyNames(ctx, &ptab, &plen, j_val, JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0)
|
|
232
|
+
return rb_hash_new();
|
|
233
|
+
|
|
234
|
+
VALUE r_hash = rb_hash_new();
|
|
235
|
+
for (uint32_t i = 0; i < plen; i++)
|
|
236
|
+
{
|
|
237
|
+
const char *key = JS_AtomToCString(ctx, ptab[i].atom);
|
|
238
|
+
JSValue j_prop = JS_GetProperty(ctx, j_val, ptab[i].atom);
|
|
239
|
+
rb_hash_aset(r_hash, rb_str_new2(key), to_rb_value(ctx, j_prop));
|
|
240
|
+
JS_FreeCString(ctx, key);
|
|
241
|
+
JS_FreeValue(ctx, j_prop);
|
|
242
|
+
}
|
|
243
|
+
JS_FreePropertyEnum(ctx, ptab, plen);
|
|
244
|
+
return r_hash;
|
|
245
|
+
}
|
|
246
|
+
|
|
157
247
|
VALUE to_rb_value(JSContext *ctx, JSValue j_val)
|
|
158
248
|
{
|
|
159
249
|
switch (JS_VALUE_GET_NORM_TAG(j_val))
|
|
@@ -197,6 +287,18 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
|
|
|
197
287
|
return Qnil;
|
|
198
288
|
}
|
|
199
289
|
|
|
290
|
+
if (JS_IsFunction(ctx, j_val))
|
|
291
|
+
{
|
|
292
|
+
JSValue j_toStringFunc = JS_GetPropertyStr(ctx, j_val, "toString");
|
|
293
|
+
JSValue j_source = JS_Call(ctx, j_toStringFunc, j_val, 0, NULL);
|
|
294
|
+
JS_FreeValue(ctx, j_toStringFunc);
|
|
295
|
+
const char *source = JS_ToCString(ctx, j_source);
|
|
296
|
+
JS_FreeValue(ctx, j_source);
|
|
297
|
+
VALUE r_source = rb_str_new2(source);
|
|
298
|
+
JS_FreeCString(ctx, source);
|
|
299
|
+
return rb_funcall(rb_path2class("Quickjs::Function"), rb_intern("new"), 1, r_source);
|
|
300
|
+
}
|
|
301
|
+
|
|
200
302
|
if (JS_IsError(ctx, j_val))
|
|
201
303
|
{
|
|
202
304
|
VALUE r_maybe_ruby_error = find_ruby_error(ctx, j_val);
|
|
@@ -236,6 +338,13 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
|
|
|
236
338
|
return r_maybe_file;
|
|
237
339
|
}
|
|
238
340
|
|
|
341
|
+
if (JS_IsArray(ctx, j_val))
|
|
342
|
+
return js_array_to_rb(ctx, j_val);
|
|
343
|
+
|
|
344
|
+
if (js_is_plain_object(ctx, j_val))
|
|
345
|
+
return js_plain_object_to_rb(ctx, j_val);
|
|
346
|
+
|
|
347
|
+
// Fallback: non-plain objects (Date, RegExp, Map, class instances, functions, etc.)
|
|
239
348
|
VALUE r_str = to_r_json(ctx, j_val);
|
|
240
349
|
|
|
241
350
|
if (rb_funcall(r_str, rb_intern("=="), 1, rb_str_new2("undefined")))
|
|
@@ -486,20 +595,14 @@ static VALUE r_try_call_listener(VALUE r_args)
|
|
|
486
595
|
|
|
487
596
|
static int dispatch_log(VMData *data, const char *severity, VALUE r_row)
|
|
488
597
|
{
|
|
598
|
+
if (NIL_P(data->log_listener))
|
|
599
|
+
return 0;
|
|
600
|
+
|
|
489
601
|
VALUE r_log = r_log_new(severity, r_row);
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
rb_protect(r_try_call_listener, r_args, &error);
|
|
495
|
-
if (error)
|
|
496
|
-
return error;
|
|
497
|
-
}
|
|
498
|
-
else
|
|
499
|
-
{
|
|
500
|
-
rb_ary_push(data->logs, r_log);
|
|
501
|
-
}
|
|
502
|
-
return 0;
|
|
602
|
+
VALUE r_args = rb_ary_new3(2, data->log_listener, r_log);
|
|
603
|
+
int error;
|
|
604
|
+
rb_protect(r_try_call_listener, r_args, &error);
|
|
605
|
+
return error;
|
|
503
606
|
}
|
|
504
607
|
|
|
505
608
|
static VALUE vm_m_on_log(VALUE r_self)
|
|
@@ -680,6 +783,18 @@ static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE r_self)
|
|
|
680
783
|
JS_FreeValue(data->context, j_polyfillEncodingResult);
|
|
681
784
|
}
|
|
682
785
|
|
|
786
|
+
if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, QUICKJSRB_SYM(featurePolyfillUrlId))))
|
|
787
|
+
{
|
|
788
|
+
JSValue j_polyfillUrlObject = JS_ReadObject(data->context, &qjsc_polyfill_url_min, qjsc_polyfill_url_min_size, JS_READ_OBJ_BYTECODE);
|
|
789
|
+
JSValue j_polyfillUrlResult = JS_EvalFunction(data->context, j_polyfillUrlObject);
|
|
790
|
+
JS_FreeValue(data->context, j_polyfillUrlResult);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, QUICKJSRB_SYM(featurePolyfillCryptoId))))
|
|
794
|
+
{
|
|
795
|
+
quickjsrb_init_crypto(data->context, j_global);
|
|
796
|
+
}
|
|
797
|
+
|
|
683
798
|
JSValue j_console = JS_NewObject(data->context);
|
|
684
799
|
JS_SetPropertyStr(
|
|
685
800
|
data->context, j_console, "log",
|
|
@@ -713,6 +828,20 @@ static int interrupt_handler(JSRuntime *runtime, void *opaque)
|
|
|
713
828
|
return elapsed_ms >= eval_time->limit_ms ? 1 : 0;
|
|
714
829
|
}
|
|
715
830
|
|
|
831
|
+
static VALUE to_rb_return_value(JSContext *ctx, JSValue j_val)
|
|
832
|
+
{
|
|
833
|
+
if (JS_VALUE_GET_NORM_TAG(j_val) == JS_TAG_OBJECT && JS_PromiseState(ctx, j_val) != -1)
|
|
834
|
+
{
|
|
835
|
+
JS_FreeValue(ctx, j_val);
|
|
836
|
+
VALUE r_error_message = rb_str_new2("An unawaited Promise was returned to the top-level");
|
|
837
|
+
rb_exc_raise(rb_funcall(QUICKJSRB_ERROR_FOR(QUICKJSRB_NO_AWAIT_ERROR), rb_intern("new"), 2, r_error_message, Qnil));
|
|
838
|
+
return Qnil;
|
|
839
|
+
}
|
|
840
|
+
VALUE result = to_rb_value(ctx, j_val);
|
|
841
|
+
JS_FreeValue(ctx, j_val);
|
|
842
|
+
return result;
|
|
843
|
+
}
|
|
844
|
+
|
|
716
845
|
static VALUE vm_m_evalCode(VALUE r_self, VALUE r_code)
|
|
717
846
|
{
|
|
718
847
|
VMData *data;
|
|
@@ -730,26 +859,11 @@ static VALUE vm_m_evalCode(VALUE r_self, VALUE r_code)
|
|
|
730
859
|
char *code = StringValueCStr(r_code);
|
|
731
860
|
JSValue j_codeResult = JS_Eval(data->context, code, strlen(code), "<code>", JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_ASYNC);
|
|
732
861
|
JSValue j_awaitedResult = js_std_await(data->context, j_codeResult); // This frees j_codeResult
|
|
862
|
+
// JS_EVAL_FLAG_ASYNC wraps the result in {value, done} — extract the actual value
|
|
863
|
+
// Free j_awaitedResult before to_rb_return_value because it may raise (longjmp), which would skip cleanup
|
|
733
864
|
JSValue j_returnedValue = JS_GetPropertyStr(data->context, j_awaitedResult, "value");
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
{
|
|
737
|
-
JS_FreeValue(data->context, j_returnedValue);
|
|
738
|
-
JS_FreeValue(data->context, j_awaitedResult);
|
|
739
|
-
VALUE r_error_message = rb_str_new2("An unawaited Promise was returned to the top-level");
|
|
740
|
-
rb_exc_raise(rb_funcall(QUICKJSRB_ERROR_FOR(QUICKJSRB_NO_AWAIT_ERROR), rb_intern("new"), 2, r_error_message, Qnil));
|
|
741
|
-
return Qnil;
|
|
742
|
-
}
|
|
743
|
-
else
|
|
744
|
-
{
|
|
745
|
-
// Free j_awaitedResult before to_rb_value because to_rb_value may
|
|
746
|
-
// raise (longjmp) for JS exceptions, which would skip any cleanup
|
|
747
|
-
// after it and leak JS GC objects.
|
|
748
|
-
JS_FreeValue(data->context, j_awaitedResult);
|
|
749
|
-
VALUE result = to_rb_value(data->context, j_returnedValue);
|
|
750
|
-
JS_FreeValue(data->context, j_returnedValue);
|
|
751
|
-
return result;
|
|
752
|
-
}
|
|
865
|
+
JS_FreeValue(data->context, j_awaitedResult);
|
|
866
|
+
return to_rb_return_value(data->context, j_returnedValue);
|
|
753
867
|
}
|
|
754
868
|
|
|
755
869
|
static VALUE vm_m_defineGlobalFunction(int argc, VALUE *argv, VALUE r_self)
|
|
@@ -761,33 +875,253 @@ static VALUE vm_m_defineGlobalFunction(int argc, VALUE *argv, VALUE r_self)
|
|
|
761
875
|
VALUE r_block;
|
|
762
876
|
rb_scan_args(argc, argv, "10*&", &r_name, &r_flags, &r_block);
|
|
763
877
|
|
|
764
|
-
|
|
878
|
+
VMData *data;
|
|
879
|
+
TypedData_Get_Struct(r_self, VMData, &vm_type, data);
|
|
880
|
+
|
|
881
|
+
if (RB_TYPE_P(r_name, T_ARRAY))
|
|
882
|
+
{
|
|
883
|
+
long path_len = RARRAY_LEN(r_name);
|
|
884
|
+
if (path_len < 1)
|
|
885
|
+
rb_raise(rb_eArgError, "function's path array must not be empty");
|
|
886
|
+
|
|
887
|
+
for (long i = 0; i < path_len; i++)
|
|
888
|
+
{
|
|
889
|
+
VALUE r_seg = RARRAY_AREF(r_name, i);
|
|
890
|
+
if (!(SYMBOL_P(r_seg) || RB_TYPE_P(r_seg, T_STRING)))
|
|
891
|
+
rb_raise(rb_eTypeError, "function's name should be a Symbol or a String");
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// Build internal lookup key by joining path segments with "."
|
|
895
|
+
// e.g. ["myLib", "hello"] -> :"myLib.hello"
|
|
896
|
+
VALUE r_segs = rb_ary_new();
|
|
897
|
+
for (long i = 0; i < path_len; i++)
|
|
898
|
+
rb_ary_push(r_segs, rb_funcall(RARRAY_AREF(r_name, i), rb_intern("to_s"), 0));
|
|
899
|
+
VALUE r_key_str = rb_funcall(r_segs, rb_intern("join"), 1, rb_str_new2("."));
|
|
900
|
+
VALUE r_key_sym = rb_funcall(r_key_str, rb_intern("to_sym"), 0);
|
|
901
|
+
rb_hash_aset(data->defined_functions, r_key_sym, r_block);
|
|
902
|
+
|
|
903
|
+
VALUE r_func_seg_str = rb_funcall(RARRAY_AREF(r_name, path_len - 1), rb_intern("to_s"), 0);
|
|
904
|
+
char *funcName = StringValueCStr(r_func_seg_str);
|
|
905
|
+
|
|
906
|
+
JSValueConst ruby_data[2];
|
|
907
|
+
ruby_data[0] = JS_NewString(data->context, StringValueCStr(r_key_str));
|
|
908
|
+
ruby_data[1] = JS_NewBool(data->context, RTEST(rb_funcall(r_flags, rb_intern("include?"), 1, ID2SYM(rb_intern("async")))));
|
|
909
|
+
|
|
910
|
+
// Resolve the parent object to attach the function to.
|
|
911
|
+
// For a single-element array, parent is the global object.
|
|
912
|
+
// For multi-element arrays, traverse path[0..n-2] using JS_Eval for the first
|
|
913
|
+
// segment (so lexical const/let bindings are resolved, not just global properties)
|
|
914
|
+
// and JS_GetPropertyStr for subsequent segments.
|
|
915
|
+
JSValue j_parent;
|
|
916
|
+
if (path_len == 1)
|
|
917
|
+
{
|
|
918
|
+
j_parent = JS_GetGlobalObject(data->context);
|
|
919
|
+
}
|
|
920
|
+
else
|
|
921
|
+
{
|
|
922
|
+
VALUE r_first_str = rb_funcall(RARRAY_AREF(r_name, 0), rb_intern("to_s"), 0);
|
|
923
|
+
const char *first_seg = StringValueCStr(r_first_str);
|
|
924
|
+
j_parent = JS_Eval(data->context, first_seg, strlen(first_seg), "<vm>", JS_EVAL_TYPE_GLOBAL);
|
|
925
|
+
|
|
926
|
+
if (JS_IsException(j_parent) || !JS_IsObject(j_parent))
|
|
927
|
+
{
|
|
928
|
+
JS_FreeValue(data->context, j_parent);
|
|
929
|
+
JS_FreeValue(data->context, ruby_data[0]);
|
|
930
|
+
JS_FreeValue(data->context, ruby_data[1]);
|
|
931
|
+
rb_raise(rb_eArgError, "cannot define function: '%s' is not an object", first_seg);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
for (long i = 1; i < path_len - 1; i++)
|
|
935
|
+
{
|
|
936
|
+
VALUE r_seg_str = rb_funcall(RARRAY_AREF(r_name, i), rb_intern("to_s"), 0);
|
|
937
|
+
JSValue j_next = JS_GetPropertyStr(data->context, j_parent, StringValueCStr(r_seg_str));
|
|
938
|
+
JS_FreeValue(data->context, j_parent);
|
|
939
|
+
|
|
940
|
+
if (JS_IsException(j_next) || !JS_IsObject(j_next))
|
|
941
|
+
{
|
|
942
|
+
JS_FreeValue(data->context, j_next);
|
|
943
|
+
JS_FreeValue(data->context, ruby_data[0]);
|
|
944
|
+
JS_FreeValue(data->context, ruby_data[1]);
|
|
945
|
+
rb_raise(rb_eArgError, "cannot define function: '%s' is not an object", StringValueCStr(r_seg_str));
|
|
946
|
+
}
|
|
947
|
+
j_parent = j_next;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
JS_SetPropertyStr(
|
|
952
|
+
data->context, j_parent, funcName,
|
|
953
|
+
JS_NewCFunctionData(data->context, js_quickjsrb_call_global, 1, 0, 2, ruby_data));
|
|
954
|
+
JS_FreeValue(data->context, j_parent);
|
|
955
|
+
JS_FreeValue(data->context, ruby_data[0]);
|
|
956
|
+
JS_FreeValue(data->context, ruby_data[1]);
|
|
957
|
+
|
|
958
|
+
VALUE r_result = rb_ary_new();
|
|
959
|
+
for (long i = 0; i < path_len; i++)
|
|
960
|
+
rb_ary_push(r_result, rb_funcall(RARRAY_AREF(r_name, i), rb_intern("to_sym"), 0));
|
|
961
|
+
return r_result;
|
|
962
|
+
}
|
|
963
|
+
else if (SYMBOL_P(r_name) || RB_TYPE_P(r_name, T_STRING))
|
|
964
|
+
{
|
|
965
|
+
VALUE r_name_sym = rb_funcall(r_name, rb_intern("to_sym"), 0);
|
|
966
|
+
|
|
967
|
+
rb_hash_aset(data->defined_functions, r_name_sym, r_block);
|
|
968
|
+
VALUE r_name_str = rb_funcall(r_name, rb_intern("to_s"), 0);
|
|
969
|
+
char *funcName = StringValueCStr(r_name_str);
|
|
970
|
+
|
|
971
|
+
JSValueConst ruby_data[2];
|
|
972
|
+
ruby_data[0] = JS_NewString(data->context, funcName);
|
|
973
|
+
ruby_data[1] = JS_NewBool(data->context, RTEST(rb_funcall(r_flags, rb_intern("include?"), 1, ID2SYM(rb_intern("async")))));
|
|
974
|
+
|
|
975
|
+
JSValue j_global = JS_GetGlobalObject(data->context);
|
|
976
|
+
JS_SetPropertyStr(
|
|
977
|
+
data->context, j_global, funcName,
|
|
978
|
+
JS_NewCFunctionData(data->context, js_quickjsrb_call_global, 1, 0, 2, ruby_data));
|
|
979
|
+
JS_FreeValue(data->context, j_global);
|
|
980
|
+
JS_FreeValue(data->context, ruby_data[0]);
|
|
981
|
+
JS_FreeValue(data->context, ruby_data[1]);
|
|
982
|
+
|
|
983
|
+
return r_name_sym;
|
|
984
|
+
}
|
|
985
|
+
else
|
|
765
986
|
{
|
|
766
987
|
rb_raise(rb_eTypeError, "function's name should be a Symbol or a String");
|
|
767
988
|
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
static VALUE vm_m_callGlobalFunction(int argc, VALUE *argv, VALUE r_self)
|
|
992
|
+
{
|
|
993
|
+
if (argc < 1)
|
|
994
|
+
rb_raise(rb_eArgError, "wrong number of arguments (given 0, expected 1+)");
|
|
995
|
+
|
|
996
|
+
VALUE r_name = argv[0];
|
|
768
997
|
|
|
769
998
|
VMData *data;
|
|
770
999
|
TypedData_Get_Struct(r_self, VMData, &vm_type, data);
|
|
771
1000
|
|
|
772
|
-
|
|
1001
|
+
JSValue j_this = JS_UNDEFINED;
|
|
1002
|
+
JSValue j_func;
|
|
773
1003
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
1004
|
+
VALUE r_path;
|
|
1005
|
+
if (SYMBOL_P(r_name) || RB_TYPE_P(r_name, T_STRING))
|
|
1006
|
+
{
|
|
1007
|
+
VALUE r_name_str = rb_funcall(r_name, rb_intern("to_s"), 0);
|
|
1008
|
+
const char *name_str = StringValueCStr(r_name_str);
|
|
1009
|
+
size_t name_len = strlen(name_str);
|
|
1010
|
+
const char *last_bracket = strrchr(name_str, '[');
|
|
1011
|
+
const char *last_dot = strrchr(name_str, '.');
|
|
777
1012
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1013
|
+
if (last_bracket != NULL && last_bracket != name_str && name_str[name_len - 1] == ']')
|
|
1014
|
+
{
|
|
1015
|
+
// Bracket notation: 'a["key"]' or 'a.b["key"]' or 'a[0]'
|
|
1016
|
+
// Split into parent expression and the bracketed key
|
|
1017
|
+
VALUE r_parent = rb_str_new(name_str, last_bracket - name_str);
|
|
1018
|
+
const char *key_start = last_bracket + 1;
|
|
1019
|
+
size_t key_len = name_len - (key_start - name_str) - 1; // exclude ']'
|
|
1020
|
+
// Strip surrounding quotes for string keys: 'a["b"]' → key = b
|
|
1021
|
+
if (key_len >= 2 &&
|
|
1022
|
+
((key_start[0] == '\'' && key_start[key_len - 1] == '\'') ||
|
|
1023
|
+
(key_start[0] == '"' && key_start[key_len - 1] == '"')))
|
|
1024
|
+
{
|
|
1025
|
+
key_start++;
|
|
1026
|
+
key_len -= 2;
|
|
1027
|
+
}
|
|
1028
|
+
VALUE r_key = rb_str_new(key_start, key_len);
|
|
1029
|
+
r_path = rb_ary_new3(2, r_parent, r_key);
|
|
1030
|
+
}
|
|
1031
|
+
else if (last_dot != NULL && last_dot != name_str)
|
|
1032
|
+
{
|
|
1033
|
+
// Dot notation: 'a.b.c' → ['a.b', 'c'] so the parent becomes `this`
|
|
1034
|
+
VALUE r_parent = rb_str_new(name_str, last_dot - name_str);
|
|
1035
|
+
VALUE r_key = rb_str_new2(last_dot + 1);
|
|
1036
|
+
r_path = rb_ary_new3(2, r_parent, r_key);
|
|
1037
|
+
}
|
|
1038
|
+
else
|
|
1039
|
+
{
|
|
1040
|
+
r_path = rb_ary_new3(1, r_name_str);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
else
|
|
1044
|
+
{
|
|
1045
|
+
rb_raise(rb_eTypeError, "function's name should be a Symbol or a String");
|
|
1046
|
+
}
|
|
781
1047
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
1048
|
+
{
|
|
1049
|
+
long path_len = RARRAY_LEN(r_path);
|
|
1050
|
+
|
|
1051
|
+
VALUE r_first = RARRAY_AREF(r_path, 0);
|
|
1052
|
+
if (!(SYMBOL_P(r_first) || RB_TYPE_P(r_first, T_STRING)))
|
|
1053
|
+
rb_raise(rb_eTypeError, "function path elements should be Symbols or Strings");
|
|
1054
|
+
|
|
1055
|
+
VALUE r_first_str = rb_funcall(r_first, rb_intern("to_s"), 0);
|
|
1056
|
+
const char *first_seg = StringValueCStr(r_first_str);
|
|
1057
|
+
|
|
1058
|
+
// JS_Eval accesses both global object properties and lexical (const/let) bindings
|
|
1059
|
+
JSValue j_cur = JS_Eval(data->context, first_seg, strlen(first_seg), "<vm>", JS_EVAL_TYPE_GLOBAL);
|
|
1060
|
+
if (JS_IsException(j_cur))
|
|
1061
|
+
return to_rb_value(data->context, j_cur); // raises
|
|
1062
|
+
|
|
1063
|
+
for (long i = 1; i < path_len; i++)
|
|
1064
|
+
{
|
|
1065
|
+
VALUE r_seg = RARRAY_AREF(r_path, i);
|
|
1066
|
+
if (!(SYMBOL_P(r_seg) || RB_TYPE_P(r_seg, T_STRING)))
|
|
1067
|
+
{
|
|
1068
|
+
JS_FreeValue(data->context, j_cur);
|
|
1069
|
+
JS_FreeValue(data->context, j_this);
|
|
1070
|
+
rb_raise(rb_eTypeError, "function path elements should be Symbols or Strings");
|
|
1071
|
+
}
|
|
1072
|
+
VALUE r_seg_str = rb_funcall(r_seg, rb_intern("to_s"), 0);
|
|
1073
|
+
const char *seg = StringValueCStr(r_seg_str);
|
|
1074
|
+
|
|
1075
|
+
JSValue j_next = JS_GetPropertyStr(data->context, j_cur, seg);
|
|
1076
|
+
if (JS_IsException(j_next))
|
|
1077
|
+
{
|
|
1078
|
+
JS_FreeValue(data->context, j_cur);
|
|
1079
|
+
JS_FreeValue(data->context, j_this);
|
|
1080
|
+
return to_rb_value(data->context, j_next); // raises
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
JS_FreeValue(data->context, j_this);
|
|
1084
|
+
j_this = j_cur;
|
|
1085
|
+
j_cur = j_next;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
j_func = j_cur;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
if (!JS_IsFunction(data->context, j_func))
|
|
1092
|
+
{
|
|
1093
|
+
JS_FreeValue(data->context, j_func);
|
|
1094
|
+
JS_FreeValue(data->context, j_this);
|
|
1095
|
+
VALUE r_error_message = rb_str_new2("given path is not a function");
|
|
1096
|
+
rb_exc_raise(rb_funcall(QUICKJSRB_ERROR_FOR(QUICKJSRB_ROOT_RUNTIME_ERROR), rb_intern("new"), 2, r_error_message, Qnil));
|
|
1097
|
+
return Qnil;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
int nargs = argc - 1;
|
|
1101
|
+
JSValue *j_args = NULL;
|
|
1102
|
+
if (nargs > 0)
|
|
1103
|
+
{
|
|
1104
|
+
j_args = (JSValue *)malloc(sizeof(JSValue) * nargs);
|
|
1105
|
+
for (int i = 0; i < nargs; i++)
|
|
1106
|
+
j_args[i] = to_js_value(data->context, argv[i + 1]);
|
|
1107
|
+
}
|
|
789
1108
|
|
|
790
|
-
|
|
1109
|
+
clock_gettime(CLOCK_MONOTONIC, &data->eval_time->started_at);
|
|
1110
|
+
JS_SetInterruptHandler(JS_GetRuntime(data->context), interrupt_handler, data->eval_time);
|
|
1111
|
+
|
|
1112
|
+
JSValue j_result = JS_Call(data->context, j_func, j_this, nargs, (JSValueConst *)j_args);
|
|
1113
|
+
|
|
1114
|
+
JS_FreeValue(data->context, j_func);
|
|
1115
|
+
JS_FreeValue(data->context, j_this);
|
|
1116
|
+
if (j_args)
|
|
1117
|
+
{
|
|
1118
|
+
for (int i = 0; i < nargs; i++)
|
|
1119
|
+
JS_FreeValue(data->context, j_args[i]);
|
|
1120
|
+
free(j_args);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// js_std_await handles both async (promise) and sync results; frees j_result
|
|
1124
|
+
return to_rb_return_value(data->context, js_std_await(data->context, j_result));
|
|
791
1125
|
}
|
|
792
1126
|
|
|
793
1127
|
static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
|
|
@@ -850,16 +1184,6 @@ static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
|
|
|
850
1184
|
return Qtrue;
|
|
851
1185
|
}
|
|
852
1186
|
|
|
853
|
-
static VALUE vm_m_logs(VALUE r_self)
|
|
854
|
-
{
|
|
855
|
-
rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, "Quickjs::VM#logs is deprecated; use Quickjs::VM#on_log instead");
|
|
856
|
-
|
|
857
|
-
VMData *data;
|
|
858
|
-
TypedData_Get_Struct(r_self, VMData, &vm_type, data);
|
|
859
|
-
|
|
860
|
-
return data->logs;
|
|
861
|
-
}
|
|
862
|
-
|
|
863
1187
|
RUBY_FUNC_EXPORTED void Init_quickjsrb(void)
|
|
864
1188
|
{
|
|
865
1189
|
rb_require("json");
|
|
@@ -873,9 +1197,9 @@ RUBY_FUNC_EXPORTED void Init_quickjsrb(void)
|
|
|
873
1197
|
rb_define_alloc_func(r_class_vm, vm_alloc);
|
|
874
1198
|
rb_define_method(r_class_vm, "initialize", vm_m_initialize, -1);
|
|
875
1199
|
rb_define_method(r_class_vm, "eval_code", vm_m_evalCode, 1);
|
|
1200
|
+
rb_define_method(r_class_vm, "call", vm_m_callGlobalFunction, -1);
|
|
876
1201
|
rb_define_method(r_class_vm, "define_function", vm_m_defineGlobalFunction, -1);
|
|
877
1202
|
rb_define_method(r_class_vm, "import", vm_m_import, -1);
|
|
878
|
-
rb_define_method(r_class_vm, "logs", vm_m_logs, 0);
|
|
879
1203
|
rb_define_method(r_class_vm, "on_log", vm_m_on_log, 0);
|
|
880
1204
|
r_define_log_class(r_class_vm);
|
|
881
1205
|
}
|
data/ext/quickjsrb/quickjsrb.h
CHANGED
|
@@ -18,6 +18,8 @@ extern const uint32_t qjsc_polyfill_file_min_size;
|
|
|
18
18
|
extern const uint8_t qjsc_polyfill_file_min;
|
|
19
19
|
extern const uint32_t qjsc_polyfill_encoding_min_size;
|
|
20
20
|
extern const uint8_t qjsc_polyfill_encoding_min;
|
|
21
|
+
extern const uint32_t qjsc_polyfill_url_min_size;
|
|
22
|
+
extern const uint8_t qjsc_polyfill_url_min;
|
|
21
23
|
|
|
22
24
|
extern const char *featureStdId;
|
|
23
25
|
extern const char *featureOsId;
|
|
@@ -25,6 +27,8 @@ extern const char *featureTimeoutId;
|
|
|
25
27
|
extern const char *featurePolyfillIntlId;
|
|
26
28
|
extern const char *featurePolyfillFileId;
|
|
27
29
|
extern const char *featurePolyfillEncodingId;
|
|
30
|
+
extern const char *featurePolyfillUrlId;
|
|
31
|
+
extern const char *featurePolyfillCryptoId;
|
|
28
32
|
|
|
29
33
|
extern const char *undefinedId;
|
|
30
34
|
extern const char *nanId;
|
|
@@ -48,7 +52,6 @@ typedef struct VMData
|
|
|
48
52
|
struct JSContext *context;
|
|
49
53
|
VALUE defined_functions;
|
|
50
54
|
struct EvalTime *eval_time;
|
|
51
|
-
VALUE logs;
|
|
52
55
|
VALUE log_listener;
|
|
53
56
|
VALUE alive_objects;
|
|
54
57
|
JSValue j_file_proxy_creator;
|
|
@@ -80,7 +83,6 @@ static void vm_mark(void *ptr)
|
|
|
80
83
|
{
|
|
81
84
|
VMData *data = (VMData *)ptr;
|
|
82
85
|
rb_gc_mark_movable(data->defined_functions);
|
|
83
|
-
rb_gc_mark_movable(data->logs);
|
|
84
86
|
rb_gc_mark_movable(data->log_listener);
|
|
85
87
|
rb_gc_mark_movable(data->alive_objects);
|
|
86
88
|
}
|
|
@@ -89,7 +91,6 @@ static void vm_compact(void *ptr)
|
|
|
89
91
|
{
|
|
90
92
|
VMData *data = (VMData *)ptr;
|
|
91
93
|
data->defined_functions = rb_gc_location(data->defined_functions);
|
|
92
|
-
data->logs = rb_gc_location(data->logs);
|
|
93
94
|
data->log_listener = rb_gc_location(data->log_listener);
|
|
94
95
|
data->alive_objects = rb_gc_location(data->alive_objects);
|
|
95
96
|
}
|
|
@@ -110,7 +111,6 @@ static VALUE vm_alloc(VALUE r_self)
|
|
|
110
111
|
VMData *data;
|
|
111
112
|
VALUE obj = TypedData_Make_Struct(r_self, VMData, &vm_type, data);
|
|
112
113
|
data->defined_functions = rb_hash_new();
|
|
113
|
-
data->logs = rb_ary_new();
|
|
114
114
|
data->log_listener = Qnil;
|
|
115
115
|
data->alive_objects = rb_hash_new();
|
|
116
116
|
data->j_file_proxy_creator = JS_UNDEFINED;
|
|
@@ -156,6 +156,8 @@ static void r_define_constants(VALUE r_parent_class)
|
|
|
156
156
|
rb_define_const(r_parent_class, "POLYFILL_INTL", QUICKJSRB_SYM(featurePolyfillIntlId));
|
|
157
157
|
rb_define_const(r_parent_class, "POLYFILL_FILE", QUICKJSRB_SYM(featurePolyfillFileId));
|
|
158
158
|
rb_define_const(r_parent_class, "POLYFILL_ENCODING", QUICKJSRB_SYM(featurePolyfillEncodingId));
|
|
159
|
+
rb_define_const(r_parent_class, "POLYFILL_URL", QUICKJSRB_SYM(featurePolyfillUrlId));
|
|
160
|
+
rb_define_const(r_parent_class, "POLYFILL_CRYPTO", QUICKJSRB_SYM(featurePolyfillCryptoId));
|
|
159
161
|
|
|
160
162
|
VALUE rb_cQuickjsValue = rb_define_class_under(r_parent_class, "Value", rb_cObject);
|
|
161
163
|
rb_define_const(rb_cQuickjsValue, "UNDEFINED", QUICKJSRB_SYM(undefinedId));
|