quickjs 0.13.0.pre → 0.14.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 +94 -69
- data/Rakefile +7 -0
- data/ext/quickjsrb/extconf.rb +7 -0
- data/ext/quickjsrb/quickjsrb.c +177 -43
- 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/subtle_crypto.rb +493 -0
- data/lib/quickjs/version.rb +1 -1
- data/lib/quickjs.rb +3 -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 +6 -1
- metadata +11 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 69b86d7c5040dba161c1d6f67cb3eab3fd8a3dc51b30eb6d355c2580dbc29602
|
|
4
|
+
data.tar.gz: 5e174baed18d79e0ac18a496f97cd580fd20830a98fb240351eeea46abe57683
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9f7abcc1733fcfe9c51aacf81c9e34b05e7e44142a9db79a8bd4525ba67570f897cd7c1fb200428507876dad35a67b9a3f383711a3b0cfc9ff1b46728cd1c125
|
|
7
|
+
data.tar.gz: a15abae6b482f0df7efc89e9b0a3b9d3ca3bab8bb8d04ce7844f60b195a0314d8e12d2b3bbdf8cf01dae763e75945d4e84cf3c1efbe2a99d4cfa525e2279625e
|
data/README.md
CHANGED
|
@@ -23,17 +23,9 @@ gem 'quickjs'
|
|
|
23
23
|
require 'quickjs'
|
|
24
24
|
|
|
25
25
|
Quickjs.eval_code('const fn = (n, pow) => n ** pow; fn(2,8);') # => 256
|
|
26
|
-
Quickjs.eval_code('const fn = (name) => `Hi, ${name}!`; fn("Itadori");') # => "Hi, Itadori!
|
|
27
|
-
Quickjs.eval_code("
|
|
28
|
-
Quickjs.eval_code("
|
|
29
|
-
|
|
30
|
-
# When code returns 'object' for `typeof`, the result is converted via JSON.stringify (JS) -> JSON.parse (Ruby)
|
|
31
|
-
Quickjs.eval_code("[1,2,3]") #=> [1, 2, 3] (Array)
|
|
32
|
-
Quickjs.eval_code("({ a: '1', b: 1 })") #=> { 'a' => '1', 'b' => 1 } (Hash)
|
|
33
|
-
|
|
34
|
-
Quickjs.eval_code("null") #=> nil
|
|
35
|
-
Quickjs.eval_code('const obj = {}; obj.missingKey;') # => :undefined (Quickjs::Value::Undefined)
|
|
36
|
-
Quickjs.eval_code("Number('whatever')") #=> :NaN (Quickjs::Value::NAN)
|
|
26
|
+
Quickjs.eval_code('const fn = (name) => `Hi, ${name}!`; fn("Itadori");') # => "Hi, Itadori!"
|
|
27
|
+
Quickjs.eval_code("[1,2,3]") #=> [1, 2, 3]
|
|
28
|
+
Quickjs.eval_code("({ a: '1', b: 1 })") #=> { 'a' => '1', 'b' => 1 }
|
|
37
29
|
```
|
|
38
30
|
|
|
39
31
|
<details>
|
|
@@ -42,36 +34,42 @@ Quickjs.eval_code("Number('whatever')") #=> :NaN (Quickjs::Value::NAN)
|
|
|
42
34
|
#### Resources
|
|
43
35
|
|
|
44
36
|
```rb
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Quickjs.eval_code(code, { max_stack_size: 1024 ** 2 })
|
|
37
|
+
Quickjs.eval_code(code,
|
|
38
|
+
memory_limit: 1024 ** 3, # 1GB memory limit
|
|
39
|
+
max_stack_size: 1024 ** 2, # 1MB max stack size
|
|
40
|
+
)
|
|
50
41
|
```
|
|
51
42
|
|
|
52
|
-
####
|
|
43
|
+
#### Timeout
|
|
53
44
|
|
|
54
45
|
```rb
|
|
55
|
-
#
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
# Enable `os` module by quickjs: https://bellard.org/quickjs/quickjs.html#os-module
|
|
59
|
-
vm = Quickjs.eval_code(features: [::Quickjs::MODULE_OS])
|
|
60
|
-
|
|
61
|
-
# Provide `setTimeout` managed by CRuby
|
|
62
|
-
vm = Quickjs.eval_code(features: [::Quickjs::FEATURE_TIMEOUT])
|
|
46
|
+
# eval_code will be interrupted after 1 sec (default: 100 msec)
|
|
47
|
+
Quickjs.eval_code(code, timeout_msec: 1_000)
|
|
48
|
+
```
|
|
63
49
|
|
|
64
|
-
|
|
65
|
-
vm = Quickjs.eval_code(features: [::Quickjs::POLYFILL_INTL])
|
|
50
|
+
#### Features
|
|
66
51
|
|
|
67
|
-
|
|
68
|
-
|
|
52
|
+
```rb
|
|
53
|
+
Quickjs.eval_code(code, features: [::Quickjs::MODULE_STD, ::Quickjs::POLYFILL_FILE])
|
|
69
54
|
```
|
|
70
55
|
|
|
56
|
+
| Constant | Description |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `MODULE_STD` | QuickJS [`std` module](https://bellard.org/quickjs/quickjs.html#std-module) |
|
|
59
|
+
| `MODULE_OS` | QuickJS [`os` module](https://bellard.org/quickjs/quickjs.html#os-module) |
|
|
60
|
+
| `FEATURE_TIMEOUT` | `setTimeout` / `setInterval` managed by CRuby |
|
|
61
|
+
| `POLYFILL_INTL` | Intl API (DateTimeFormat, NumberFormat, PluralRules, Locale) |
|
|
62
|
+
| `POLYFILL_FILE` | W3C File API (Blob and File) |
|
|
63
|
+
| `POLYFILL_ENCODING` | Encoding API (TextEncoder and TextDecoder) |
|
|
64
|
+
| `POLYFILL_URL` | URL API (URL and URLSearchParams) |
|
|
65
|
+
| `POLYFILL_CRYPTO` | Web Crypto API (`crypto.getRandomValues`, `crypto.randomUUID`, `crypto.subtle`); combine with `POLYFILL_ENCODING` for string↔buffer conversion |
|
|
66
|
+
|
|
71
67
|
</details>
|
|
72
68
|
|
|
73
69
|
### `Quickjs::VM`: Maintain a consistent VM/runtime
|
|
74
70
|
|
|
71
|
+
Accepts the same [options](#quickjseval_code-evaluate-javascript-code-instantly) as `Quickjs.eval_code`.
|
|
72
|
+
|
|
75
73
|
```rb
|
|
76
74
|
vm = Quickjs::VM.new
|
|
77
75
|
vm.eval_code('const a = { b: "c" };')
|
|
@@ -80,44 +78,28 @@ vm.eval_code('a.b = "d";')
|
|
|
80
78
|
vm.eval_code('a.b;') #=> "d"
|
|
81
79
|
```
|
|
82
80
|
|
|
83
|
-
|
|
84
|
-
<summary>Config VM/runtime</summary>
|
|
85
|
-
|
|
86
|
-
#### Resources
|
|
81
|
+
#### `Quickjs::VM#call`: ⚡ Call a JS function directly with Ruby arguments
|
|
87
82
|
|
|
88
83
|
```rb
|
|
89
|
-
vm = Quickjs::VM.new
|
|
90
|
-
|
|
91
|
-
max_stack_size: 1024 ** 2,
|
|
92
|
-
)
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
#### Toggle features
|
|
96
|
-
|
|
97
|
-
```rb
|
|
98
|
-
# Enable `std` module by quickjs: https://bellard.org/quickjs/quickjs.html#std-module
|
|
99
|
-
vm = Quickjs::VM.new(features: [::Quickjs::MODULE_STD])
|
|
100
|
-
|
|
101
|
-
# Enable `os` module by quickjs: https://bellard.org/quickjs/quickjs.html#os-module
|
|
102
|
-
vm = Quickjs::VM.new(features: [::Quickjs::MODULE_OS])
|
|
84
|
+
vm = Quickjs::VM.new
|
|
85
|
+
vm.eval_code('function add(a, b) { return a + b; }')
|
|
103
86
|
|
|
104
|
-
|
|
105
|
-
vm
|
|
87
|
+
vm.call('add', 1, 2) #=> 3
|
|
88
|
+
vm.call(:add, 1, 2) #=> 3 (Symbol also works)
|
|
106
89
|
|
|
107
|
-
#
|
|
108
|
-
vm =
|
|
90
|
+
# Nested functions — preserves `this` binding
|
|
91
|
+
vm.eval_code('const counter = { n: 0, inc() { return ++this.n; } }')
|
|
92
|
+
vm.call('counter.inc') #=> 1
|
|
93
|
+
vm.call('counter.inc') #=> 2
|
|
109
94
|
|
|
110
|
-
#
|
|
111
|
-
vm =
|
|
112
|
-
|
|
95
|
+
# Keys with special characters via bracket notation
|
|
96
|
+
vm.eval_code("const obj = {}; obj['my-fn'] = x => x * 2;")
|
|
97
|
+
vm.call('obj["my-fn"]', 21) #=> 42
|
|
113
98
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
# `eval_code` will be interrupted after 1 sec (default: 100 msec)
|
|
118
|
-
vm = Quickjs::VM.new(timeout_msec: 1_000)
|
|
99
|
+
# Async functions are automatically awaited
|
|
100
|
+
vm.eval_code('async function fetchVal() { return 42; }')
|
|
101
|
+
vm.call('fetchVal') #=> 42
|
|
119
102
|
```
|
|
120
|
-
</details>
|
|
121
103
|
|
|
122
104
|
#### `Quickjs::VM#import`: 🔌 Import ESM from a source code
|
|
123
105
|
|
|
@@ -151,32 +133,75 @@ end
|
|
|
151
133
|
vm.eval_code("greetingTo('Rick')") #=> 'Hello! Rick'
|
|
152
134
|
```
|
|
153
135
|
|
|
154
|
-
|
|
136
|
+
A Ruby exception raised inside the block is catchable in JS as an `Error`, and propagates back to Ruby as the original exception type if uncaught in JS.
|
|
137
|
+
|
|
138
|
+
```rb
|
|
139
|
+
vm.define_function("fail") { raise IOError, "something went wrong" }
|
|
140
|
+
|
|
141
|
+
vm.eval_code('try { fail() } catch (e) { e.message }') #=> "something went wrong"
|
|
142
|
+
vm.eval_code("fail()") #=> raise IOError transparently
|
|
143
|
+
```
|
|
155
144
|
|
|
156
|
-
|
|
145
|
+
With `POLYFILL_FILE` enabled, a Ruby `::File` returned from the block becomes a JS `File`-compatible proxy. Passing it back to Ruby from JS returns the original `::File` object.
|
|
146
|
+
|
|
147
|
+
```rb
|
|
148
|
+
vm = Quickjs::VM.new(features: [::Quickjs::POLYFILL_FILE])
|
|
149
|
+
vm.define_function(:get_file) { File.open('report.pdf') }
|
|
150
|
+
|
|
151
|
+
vm.eval_code("get_file().name") #=> "report.pdf"
|
|
152
|
+
vm.eval_code("get_file().size") #=> Integer (byte size)
|
|
153
|
+
vm.eval_code("await get_file().text()") #=> file content as String
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### `Quickjs::VM#on_log`: 📡 Handle console logs in real time
|
|
157
|
+
|
|
158
|
+
Register a block to be called for each `console.(log|info|debug|warn|error)` call.
|
|
157
159
|
|
|
158
160
|
```rb
|
|
159
161
|
vm = Quickjs::VM.new
|
|
160
|
-
vm.
|
|
162
|
+
vm.on_log { |log| puts "#{log.severity}: #{log.to_s}" }
|
|
161
163
|
|
|
162
|
-
vm.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
vm.eval_code('console.log("hello", 42)')
|
|
165
|
+
# => prints: info: hello 42
|
|
166
|
+
|
|
167
|
+
# log.severity #=> :info / :verbose / :warning / :error
|
|
168
|
+
# log.to_s #=> space-joined string of all arguments
|
|
169
|
+
# log.raw #=> Array of raw Ruby values
|
|
166
170
|
```
|
|
167
171
|
|
|
172
|
+
### Value Conversion
|
|
173
|
+
|
|
174
|
+
| JavaScript | | Ruby | Note |
|
|
175
|
+
|---|:---:|---|---|
|
|
176
|
+
| `number` (integer / float) | ↔ | `Integer` / `Float` | |
|
|
177
|
+
| `string` | ↔ | `String` | |
|
|
178
|
+
| `true` / `false` | ↔ | `true` / `false` | |
|
|
179
|
+
| `null` | ↔ | `nil` | |
|
|
180
|
+
| `Array` | ↔ | `Array` | via JSON |
|
|
181
|
+
| `Object` | ↔ | `Hash` | via JSON |
|
|
182
|
+
| `undefined` | → | `Quickjs::Value::UNDEFINED` | |
|
|
183
|
+
| `NaN` | → | `Quickjs::Value::NAN` | |
|
|
184
|
+
| `Blob` | → | `Quickjs::Blob` — `.size`, `.type`, `.content` | requires `POLYFILL_FILE` |
|
|
185
|
+
| `File` | → | `Quickjs::File` — `.name`, `.last_modified` + Blob attrs | requires `POLYFILL_FILE` |
|
|
186
|
+
| `File` proxy | ← | `::File` | requires `POLYFILL_FILE`; applies to `define_function` return values |
|
|
187
|
+
|
|
168
188
|
## License
|
|
169
189
|
|
|
170
190
|
- `ext/quickjsrb/quickjs`
|
|
171
191
|
- [MIT License Copyright (c) 2017-2021 by Fabrice Bellard and Charlie Gordon](https://github.com/bellard/quickjs/blob/6e2e68fd0896957f92eb6c242a2e048c1ef3cae0/LICENSE).
|
|
172
192
|
- `ext/quickjsrb/vendor/polyfill-intl-en.min.js` ([bundled and minified from `polyfills/`](https://github.com/hmsk/quickjs.rb/tree/main/polyfills))
|
|
193
|
+
- MIT License Copyright (c) 2022 FormatJS
|
|
194
|
+
- [@formatjs/intl-supportedvaluesof](https://github.com/formatjs/formatjs/blob/main/packages/intl-supportedvaluesof/LICENSE.md)
|
|
173
195
|
- MIT License Copyright (c) 2023 FormatJS
|
|
174
196
|
- [@formatjs/intl-getcanonicallocales](https://github.com/formatjs/formatjs/blob/main/packages/intl-getcanonicallocales/LICENSE.md)
|
|
175
197
|
- [@formatjs/intl-locale](https://github.com/formatjs/formatjs/blob/main/packages/intl-locale/LICENSE.md)
|
|
176
198
|
- [@formatjs/intl-pluralrules](https://github.com/formatjs/formatjs/blob/main/packages/intl-pluralrules/LICENSE.md)
|
|
177
199
|
- [@formatjs/intl-numberformat](https://github.com/formatjs/formatjs/blob/main/packages/intl-numberformat/LICENSE.md)
|
|
178
200
|
- [@formatjs/intl-datetimeformat](https://github.com/formatjs/formatjs/blob/main/packages/intl-datetimeformat/LICENSE.md)
|
|
179
|
-
|
|
180
|
-
- [
|
|
201
|
+
- [@formatjs/ecma402-abstract](https://github.com/formatjs/formatjs/blob/main/packages/ecma402-abstract/LICENSE.md)
|
|
202
|
+
- [@formatjs/fast-memoize](https://github.com/formatjs/formatjs/blob/main/packages/fast-memoize/LICENSE.md)
|
|
203
|
+
- [@formatjs/intl-localematcher](https://github.com/formatjs/formatjs/blob/main/packages/intl-localematcher/LICENSE.md)
|
|
204
|
+
- MIT License Copyright (c) 2026 FormatJS
|
|
205
|
+
- [@formatjs/bigdecimal](https://github.com/formatjs/formatjs/blob/main/packages/bigdecimal/LICENSE.md)
|
|
181
206
|
|
|
182
207
|
Otherwise, [the MIT License, Copyright 2024 by Kengo Hamasaki](/LICENSE).
|
data/Rakefile
CHANGED
|
@@ -54,6 +54,13 @@ namespace :polyfills do
|
|
|
54
54
|
Rake::Task[:compile].invoke
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
desc 'Check licenses of bundled polyfill dependencies'
|
|
58
|
+
task :check_licenses do
|
|
59
|
+
Dir.chdir(File.expand_path('polyfills', __dir__)) do
|
|
60
|
+
sh 'npm run check-licenses'
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
57
64
|
namespace :version do
|
|
58
65
|
task :check do
|
|
59
66
|
check_polyfill_version! do |pkg_v, gem_v|
|
data/ext/quickjsrb/extconf.rb
CHANGED
|
@@ -14,8 +14,11 @@ $srcs = [
|
|
|
14
14
|
'polyfill-intl-en.min.c',
|
|
15
15
|
'polyfill-file.min.c',
|
|
16
16
|
'polyfill-encoding.min.c',
|
|
17
|
+
'polyfill-url.min.c',
|
|
17
18
|
'quickjsrb.c',
|
|
18
19
|
'quickjsrb_file.c',
|
|
20
|
+
'quickjsrb_crypto.c',
|
|
21
|
+
'quickjsrb_crypto_subtle.c',
|
|
19
22
|
]
|
|
20
23
|
|
|
21
24
|
append_cflags('-I$(srcdir)/quickjs')
|
|
@@ -72,6 +75,10 @@ polyfill-encoding.min.js:
|
|
|
72
75
|
$(COPY) $(srcdir)/vendor/$@ $@
|
|
73
76
|
polyfill-encoding.min.c: ./qjsc polyfill-encoding.min.js
|
|
74
77
|
./qjsc -fno-string-normalize -fno-eval -fno-proxy -fno-module-loader -c -M polyfill/encoding.so,encoding -m -o $@ polyfill-encoding.min.js
|
|
78
|
+
polyfill-url.min.js:
|
|
79
|
+
$(COPY) $(srcdir)/vendor/$@ $@
|
|
80
|
+
polyfill-url.min.c: ./qjsc polyfill-url.min.js
|
|
81
|
+
./qjsc -fno-string-normalize -fno-eval -fno-proxy -fno-module-loader -c -M polyfill/url.so,url -m -o $@ polyfill-url.min.js
|
|
75
82
|
COMPILE_POLYFILL
|
|
76
83
|
conf
|
|
77
84
|
end
|
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";
|
|
@@ -486,20 +489,14 @@ static VALUE r_try_call_listener(VALUE r_args)
|
|
|
486
489
|
|
|
487
490
|
static int dispatch_log(VMData *data, const char *severity, VALUE r_row)
|
|
488
491
|
{
|
|
492
|
+
if (NIL_P(data->log_listener))
|
|
493
|
+
return 0;
|
|
494
|
+
|
|
489
495
|
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;
|
|
496
|
+
VALUE r_args = rb_ary_new3(2, data->log_listener, r_log);
|
|
497
|
+
int error;
|
|
498
|
+
rb_protect(r_try_call_listener, r_args, &error);
|
|
499
|
+
return error;
|
|
503
500
|
}
|
|
504
501
|
|
|
505
502
|
static VALUE vm_m_on_log(VALUE r_self)
|
|
@@ -680,6 +677,18 @@ static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE r_self)
|
|
|
680
677
|
JS_FreeValue(data->context, j_polyfillEncodingResult);
|
|
681
678
|
}
|
|
682
679
|
|
|
680
|
+
if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, QUICKJSRB_SYM(featurePolyfillUrlId))))
|
|
681
|
+
{
|
|
682
|
+
JSValue j_polyfillUrlObject = JS_ReadObject(data->context, &qjsc_polyfill_url_min, qjsc_polyfill_url_min_size, JS_READ_OBJ_BYTECODE);
|
|
683
|
+
JSValue j_polyfillUrlResult = JS_EvalFunction(data->context, j_polyfillUrlObject);
|
|
684
|
+
JS_FreeValue(data->context, j_polyfillUrlResult);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, QUICKJSRB_SYM(featurePolyfillCryptoId))))
|
|
688
|
+
{
|
|
689
|
+
quickjsrb_init_crypto(data->context, j_global);
|
|
690
|
+
}
|
|
691
|
+
|
|
683
692
|
JSValue j_console = JS_NewObject(data->context);
|
|
684
693
|
JS_SetPropertyStr(
|
|
685
694
|
data->context, j_console, "log",
|
|
@@ -713,6 +722,20 @@ static int interrupt_handler(JSRuntime *runtime, void *opaque)
|
|
|
713
722
|
return elapsed_ms >= eval_time->limit_ms ? 1 : 0;
|
|
714
723
|
}
|
|
715
724
|
|
|
725
|
+
static VALUE to_rb_return_value(JSContext *ctx, JSValue j_val)
|
|
726
|
+
{
|
|
727
|
+
if (JS_VALUE_GET_NORM_TAG(j_val) == JS_TAG_OBJECT && JS_PromiseState(ctx, j_val) != -1)
|
|
728
|
+
{
|
|
729
|
+
JS_FreeValue(ctx, j_val);
|
|
730
|
+
VALUE r_error_message = rb_str_new2("An unawaited Promise was returned to the top-level");
|
|
731
|
+
rb_exc_raise(rb_funcall(QUICKJSRB_ERROR_FOR(QUICKJSRB_NO_AWAIT_ERROR), rb_intern("new"), 2, r_error_message, Qnil));
|
|
732
|
+
return Qnil;
|
|
733
|
+
}
|
|
734
|
+
VALUE result = to_rb_value(ctx, j_val);
|
|
735
|
+
JS_FreeValue(ctx, j_val);
|
|
736
|
+
return result;
|
|
737
|
+
}
|
|
738
|
+
|
|
716
739
|
static VALUE vm_m_evalCode(VALUE r_self, VALUE r_code)
|
|
717
740
|
{
|
|
718
741
|
VMData *data;
|
|
@@ -730,26 +753,11 @@ static VALUE vm_m_evalCode(VALUE r_self, VALUE r_code)
|
|
|
730
753
|
char *code = StringValueCStr(r_code);
|
|
731
754
|
JSValue j_codeResult = JS_Eval(data->context, code, strlen(code), "<code>", JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_ASYNC);
|
|
732
755
|
JSValue j_awaitedResult = js_std_await(data->context, j_codeResult); // This frees j_codeResult
|
|
756
|
+
// JS_EVAL_FLAG_ASYNC wraps the result in {value, done} — extract the actual value
|
|
757
|
+
// Free j_awaitedResult before to_rb_return_value because it may raise (longjmp), which would skip cleanup
|
|
733
758
|
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
|
-
}
|
|
759
|
+
JS_FreeValue(data->context, j_awaitedResult);
|
|
760
|
+
return to_rb_return_value(data->context, j_returnedValue);
|
|
753
761
|
}
|
|
754
762
|
|
|
755
763
|
static VALUE vm_m_defineGlobalFunction(int argc, VALUE *argv, VALUE r_self)
|
|
@@ -790,6 +798,142 @@ static VALUE vm_m_defineGlobalFunction(int argc, VALUE *argv, VALUE r_self)
|
|
|
790
798
|
return r_name_sym;
|
|
791
799
|
}
|
|
792
800
|
|
|
801
|
+
static VALUE vm_m_callGlobalFunction(int argc, VALUE *argv, VALUE r_self)
|
|
802
|
+
{
|
|
803
|
+
if (argc < 1)
|
|
804
|
+
rb_raise(rb_eArgError, "wrong number of arguments (given 0, expected 1+)");
|
|
805
|
+
|
|
806
|
+
VALUE r_name = argv[0];
|
|
807
|
+
|
|
808
|
+
VMData *data;
|
|
809
|
+
TypedData_Get_Struct(r_self, VMData, &vm_type, data);
|
|
810
|
+
|
|
811
|
+
JSValue j_this = JS_UNDEFINED;
|
|
812
|
+
JSValue j_func;
|
|
813
|
+
|
|
814
|
+
VALUE r_path;
|
|
815
|
+
if (SYMBOL_P(r_name) || RB_TYPE_P(r_name, T_STRING))
|
|
816
|
+
{
|
|
817
|
+
VALUE r_name_str = rb_funcall(r_name, rb_intern("to_s"), 0);
|
|
818
|
+
const char *name_str = StringValueCStr(r_name_str);
|
|
819
|
+
size_t name_len = strlen(name_str);
|
|
820
|
+
const char *last_bracket = strrchr(name_str, '[');
|
|
821
|
+
const char *last_dot = strrchr(name_str, '.');
|
|
822
|
+
|
|
823
|
+
if (last_bracket != NULL && last_bracket != name_str && name_str[name_len - 1] == ']')
|
|
824
|
+
{
|
|
825
|
+
// Bracket notation: 'a["key"]' or 'a.b["key"]' or 'a[0]'
|
|
826
|
+
// Split into parent expression and the bracketed key
|
|
827
|
+
VALUE r_parent = rb_str_new(name_str, last_bracket - name_str);
|
|
828
|
+
const char *key_start = last_bracket + 1;
|
|
829
|
+
size_t key_len = name_len - (key_start - name_str) - 1; // exclude ']'
|
|
830
|
+
// Strip surrounding quotes for string keys: 'a["b"]' → key = b
|
|
831
|
+
if (key_len >= 2 &&
|
|
832
|
+
((key_start[0] == '\'' && key_start[key_len - 1] == '\'') ||
|
|
833
|
+
(key_start[0] == '"' && key_start[key_len - 1] == '"')))
|
|
834
|
+
{
|
|
835
|
+
key_start++;
|
|
836
|
+
key_len -= 2;
|
|
837
|
+
}
|
|
838
|
+
VALUE r_key = rb_str_new(key_start, key_len);
|
|
839
|
+
r_path = rb_ary_new3(2, r_parent, r_key);
|
|
840
|
+
}
|
|
841
|
+
else if (last_dot != NULL && last_dot != name_str)
|
|
842
|
+
{
|
|
843
|
+
// Dot notation: 'a.b.c' → ['a.b', 'c'] so the parent becomes `this`
|
|
844
|
+
VALUE r_parent = rb_str_new(name_str, last_dot - name_str);
|
|
845
|
+
VALUE r_key = rb_str_new2(last_dot + 1);
|
|
846
|
+
r_path = rb_ary_new3(2, r_parent, r_key);
|
|
847
|
+
}
|
|
848
|
+
else
|
|
849
|
+
{
|
|
850
|
+
r_path = rb_ary_new3(1, r_name_str);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
else
|
|
854
|
+
{
|
|
855
|
+
rb_raise(rb_eTypeError, "function's name should be a Symbol or a String");
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
{
|
|
859
|
+
long path_len = RARRAY_LEN(r_path);
|
|
860
|
+
|
|
861
|
+
VALUE r_first = RARRAY_AREF(r_path, 0);
|
|
862
|
+
if (!(SYMBOL_P(r_first) || RB_TYPE_P(r_first, T_STRING)))
|
|
863
|
+
rb_raise(rb_eTypeError, "function path elements should be Symbols or Strings");
|
|
864
|
+
|
|
865
|
+
VALUE r_first_str = rb_funcall(r_first, rb_intern("to_s"), 0);
|
|
866
|
+
const char *first_seg = StringValueCStr(r_first_str);
|
|
867
|
+
|
|
868
|
+
// JS_Eval accesses both global object properties and lexical (const/let) bindings
|
|
869
|
+
JSValue j_cur = JS_Eval(data->context, first_seg, strlen(first_seg), "<vm>", JS_EVAL_TYPE_GLOBAL);
|
|
870
|
+
if (JS_IsException(j_cur))
|
|
871
|
+
return to_rb_value(data->context, j_cur); // raises
|
|
872
|
+
|
|
873
|
+
for (long i = 1; i < path_len; i++)
|
|
874
|
+
{
|
|
875
|
+
VALUE r_seg = RARRAY_AREF(r_path, i);
|
|
876
|
+
if (!(SYMBOL_P(r_seg) || RB_TYPE_P(r_seg, T_STRING)))
|
|
877
|
+
{
|
|
878
|
+
JS_FreeValue(data->context, j_cur);
|
|
879
|
+
JS_FreeValue(data->context, j_this);
|
|
880
|
+
rb_raise(rb_eTypeError, "function path elements should be Symbols or Strings");
|
|
881
|
+
}
|
|
882
|
+
VALUE r_seg_str = rb_funcall(r_seg, rb_intern("to_s"), 0);
|
|
883
|
+
const char *seg = StringValueCStr(r_seg_str);
|
|
884
|
+
|
|
885
|
+
JSValue j_next = JS_GetPropertyStr(data->context, j_cur, seg);
|
|
886
|
+
if (JS_IsException(j_next))
|
|
887
|
+
{
|
|
888
|
+
JS_FreeValue(data->context, j_cur);
|
|
889
|
+
JS_FreeValue(data->context, j_this);
|
|
890
|
+
return to_rb_value(data->context, j_next); // raises
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
JS_FreeValue(data->context, j_this);
|
|
894
|
+
j_this = j_cur;
|
|
895
|
+
j_cur = j_next;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
j_func = j_cur;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
if (!JS_IsFunction(data->context, j_func))
|
|
902
|
+
{
|
|
903
|
+
JS_FreeValue(data->context, j_func);
|
|
904
|
+
JS_FreeValue(data->context, j_this);
|
|
905
|
+
VALUE r_error_message = rb_str_new2("given path is not a function");
|
|
906
|
+
rb_exc_raise(rb_funcall(QUICKJSRB_ERROR_FOR(QUICKJSRB_ROOT_RUNTIME_ERROR), rb_intern("new"), 2, r_error_message, Qnil));
|
|
907
|
+
return Qnil;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
int nargs = argc - 1;
|
|
911
|
+
JSValue *j_args = NULL;
|
|
912
|
+
if (nargs > 0)
|
|
913
|
+
{
|
|
914
|
+
j_args = (JSValue *)malloc(sizeof(JSValue) * nargs);
|
|
915
|
+
for (int i = 0; i < nargs; i++)
|
|
916
|
+
j_args[i] = to_js_value(data->context, argv[i + 1]);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
clock_gettime(CLOCK_MONOTONIC, &data->eval_time->started_at);
|
|
920
|
+
JS_SetInterruptHandler(JS_GetRuntime(data->context), interrupt_handler, data->eval_time);
|
|
921
|
+
|
|
922
|
+
JSValue j_result = JS_Call(data->context, j_func, j_this, nargs, (JSValueConst *)j_args);
|
|
923
|
+
|
|
924
|
+
JS_FreeValue(data->context, j_func);
|
|
925
|
+
JS_FreeValue(data->context, j_this);
|
|
926
|
+
if (j_args)
|
|
927
|
+
{
|
|
928
|
+
for (int i = 0; i < nargs; i++)
|
|
929
|
+
JS_FreeValue(data->context, j_args[i]);
|
|
930
|
+
free(j_args);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// js_std_await handles both async (promise) and sync results; frees j_result
|
|
934
|
+
return to_rb_return_value(data->context, js_std_await(data->context, j_result));
|
|
935
|
+
}
|
|
936
|
+
|
|
793
937
|
static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
|
|
794
938
|
{
|
|
795
939
|
VALUE r_import_string, r_opts;
|
|
@@ -850,16 +994,6 @@ static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
|
|
|
850
994
|
return Qtrue;
|
|
851
995
|
}
|
|
852
996
|
|
|
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
997
|
RUBY_FUNC_EXPORTED void Init_quickjsrb(void)
|
|
864
998
|
{
|
|
865
999
|
rb_require("json");
|
|
@@ -873,9 +1007,9 @@ RUBY_FUNC_EXPORTED void Init_quickjsrb(void)
|
|
|
873
1007
|
rb_define_alloc_func(r_class_vm, vm_alloc);
|
|
874
1008
|
rb_define_method(r_class_vm, "initialize", vm_m_initialize, -1);
|
|
875
1009
|
rb_define_method(r_class_vm, "eval_code", vm_m_evalCode, 1);
|
|
1010
|
+
rb_define_method(r_class_vm, "call", vm_m_callGlobalFunction, -1);
|
|
876
1011
|
rb_define_method(r_class_vm, "define_function", vm_m_defineGlobalFunction, -1);
|
|
877
1012
|
rb_define_method(r_class_vm, "import", vm_m_import, -1);
|
|
878
|
-
rb_define_method(r_class_vm, "logs", vm_m_logs, 0);
|
|
879
1013
|
rb_define_method(r_class_vm, "on_log", vm_m_on_log, 0);
|
|
880
1014
|
r_define_log_class(r_class_vm);
|
|
881
1015
|
}
|
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));
|