@gjsify/napi 0.22.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.
@@ -0,0 +1,656 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // @gjsify/napi — minimal value surface for the P0.0 load gate.
3
+ //
4
+ // Reference: refs/node/src/js_native_api_v8.cc (Node.js contributors, MIT)
5
+ // for per-function argument checks, status codes and string semantics
6
+ // (napi_get_value_string_utf8: cc:2558-2568 WriteUtf8V2 kReplaceInvalidUtf8).
7
+ // Reimplemented for GJS/SpiderMonkey via JSAPI.
8
+
9
+ #include "common.h"
10
+
11
+ #include <algorithm>
12
+ #include <cmath>
13
+ #include <cstring>
14
+
15
+ #include <mozilla/Maybe.h>
16
+ #include <mozilla/Span.h>
17
+
18
+ napi_status NAPI_CDECL napi_get_undefined(napi_env env, napi_value* result) {
19
+ GJSIFY_NAPI_CHECK_ENV(env);
20
+ GJSIFY_NAPI_CHECK_ARG(env, result);
21
+ *result = gjsify_napi::arena_push(env, JS::UndefinedValue());
22
+ return gjsify_napi::clear_last_error(env);
23
+ }
24
+
25
+ napi_status NAPI_CDECL napi_get_null(napi_env env, napi_value* result) {
26
+ GJSIFY_NAPI_CHECK_ENV(env);
27
+ GJSIFY_NAPI_CHECK_ARG(env, result);
28
+ *result = gjsify_napi::arena_push(env, JS::NullValue());
29
+ return gjsify_napi::clear_last_error(env);
30
+ }
31
+
32
+ napi_status NAPI_CDECL napi_get_boolean(napi_env env, bool value,
33
+ napi_value* result) {
34
+ GJSIFY_NAPI_CHECK_ENV(env);
35
+ GJSIFY_NAPI_CHECK_ARG(env, result);
36
+ *result = gjsify_napi::arena_push(env, JS::BooleanValue(value));
37
+ return gjsify_napi::clear_last_error(env);
38
+ }
39
+
40
+ napi_status NAPI_CDECL napi_get_global(napi_env env, napi_value* result) {
41
+ GJSIFY_NAPI_CHECK_ENV(env);
42
+ GJSIFY_NAPI_CHECK_ARG(env, result);
43
+ // The realm global captured at env creation (Node: context()->Global()).
44
+ JSObject* global = env->global;
45
+ if (global == nullptr) {
46
+ return gjsify_napi::set_last_error(env, napi_generic_failure);
47
+ }
48
+ *result = gjsify_napi::arena_push(env, JS::ObjectValue(*global));
49
+ return gjsify_napi::clear_last_error(env);
50
+ }
51
+
52
+ napi_status NAPI_CDECL napi_create_object(napi_env env, napi_value* result) {
53
+ GJSIFY_NAPI_CHECK_ENV(env);
54
+ GJSIFY_NAPI_CHECK_ARG(env, result);
55
+ JSObject* obj = JS_NewPlainObject(env->cx);
56
+ if (obj == nullptr) {
57
+ return gjsify_napi::set_last_error(env, napi_generic_failure);
58
+ }
59
+ *result = gjsify_napi::arena_push(env, JS::ObjectValue(*obj));
60
+ return gjsify_napi::clear_last_error(env);
61
+ }
62
+
63
+ namespace gjsify_napi {
64
+
65
+ // Node's NewFromUtf8 is LOSSY (invalid sequences → U+FFFD) and never throws;
66
+ // SpiderMonkey's JS_NewStringCopyUTF8N throws on malformed input. Fast path
67
+ // the valid case, fall back to the lossy inflater on parse failure.
68
+ JSString* new_string_utf8_lossy(JSContext* cx, const char* str, size_t len) {
69
+ JSString* js_str = JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(str, len));
70
+ if (js_str != nullptr) {
71
+ return js_str;
72
+ }
73
+ if (!JS_IsExceptionPending(cx)) {
74
+ return nullptr; // OOM without exception — give up
75
+ }
76
+ JS_ClearPendingException(cx); // malformed UTF-8 → lossy fallback
77
+ size_t out_len = 0;
78
+ JS::UniqueTwoByteChars two_byte(
79
+ JS::LossyUTF8CharsToNewTwoByteCharsZ(cx, JS::UTF8Chars(str, len),
80
+ &out_len, js::MallocArena)
81
+ .get());
82
+ if (!two_byte) {
83
+ return nullptr;
84
+ }
85
+ return JS_NewUCStringCopyN(cx, two_byte.get(), out_len);
86
+ }
87
+
88
+ } // namespace gjsify_napi
89
+
90
+ napi_status NAPI_CDECL napi_create_string_utf8(napi_env env, const char* str,
91
+ size_t length,
92
+ napi_value* result) {
93
+ GJSIFY_NAPI_CHECK_ENV(env);
94
+ GJSIFY_NAPI_CHECK_ARG(env, result);
95
+ GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(env, str != nullptr || length == 0,
96
+ napi_invalid_arg);
97
+ GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(
98
+ env, length == NAPI_AUTO_LENGTH || length <= INT32_MAX,
99
+ napi_invalid_arg);
100
+ const size_t len = (length == NAPI_AUTO_LENGTH) ? strlen(str) : length;
101
+ JSString* js_str = gjsify_napi::new_string_utf8_lossy(env->cx, str, len);
102
+ if (js_str == nullptr) {
103
+ return gjsify_napi::set_last_error(env, napi_generic_failure);
104
+ }
105
+ *result = gjsify_napi::arena_push(env, JS::StringValue(js_str));
106
+ return gjsify_napi::clear_last_error(env);
107
+ }
108
+
109
+ // node_api_create_property_key_*: semantically an (optimized) string used as
110
+ // a property key — implemented as plain interned-content strings; SM
111
+ // atomizes ids at JS_StringToId/JS_ValueToId time anyway.
112
+
113
+ napi_status NAPI_CDECL node_api_create_property_key_utf8(napi_env env,
114
+ const char* str,
115
+ size_t length,
116
+ napi_value* result) {
117
+ return napi_create_string_utf8(env, str, length, result);
118
+ }
119
+
120
+ napi_status NAPI_CDECL node_api_create_property_key_latin1(
121
+ napi_env env, const char* str, size_t length, napi_value* result) {
122
+ return napi_create_string_latin1(env, str, length, result);
123
+ }
124
+
125
+ napi_status NAPI_CDECL node_api_create_property_key_utf16(
126
+ napi_env env, const char16_t* str, size_t length, napi_value* result) {
127
+ return napi_create_string_utf16(env, str, length, result);
128
+ }
129
+
130
+ napi_status NAPI_CDECL napi_get_value_string_utf8(napi_env env,
131
+ napi_value value, char* buf,
132
+ size_t bufsize,
133
+ size_t* result) {
134
+ GJSIFY_NAPI_CHECK_ENV(env);
135
+ GJSIFY_NAPI_CHECK_ARG(env, value);
136
+ JS::Value v = gjsify_napi::napi_value_to_js(value);
137
+ GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(env, v.isString(), napi_string_expected);
138
+ JSString* str = v.toString();
139
+
140
+ if (buf == nullptr) {
141
+ // Query: UTF-8 byte length, excluding the NUL terminator.
142
+ GJSIFY_NAPI_CHECK_ARG(env, result);
143
+ JSLinearString* linear = JS_EnsureLinearString(env->cx, str);
144
+ if (linear == nullptr) {
145
+ return gjsify_napi::set_last_error(env, napi_generic_failure);
146
+ }
147
+ *result = JS::GetDeflatedUTF8StringLength(linear);
148
+ } else if (bufsize != 0) {
149
+ // Write at most bufsize-1 bytes + NUL, truncating at code-point
150
+ // boundaries, lone surrogates → U+FFFD. Node semantics
151
+ // (WriteUtf8V2 kReplaceInvalidUtf8 | kNullTerminate) match
152
+ // JS_EncodeStringToUTF8BufferPartial (TextEncoder.encodeInto).
153
+ mozilla::Maybe<std::tuple<size_t, size_t>> encoded =
154
+ JS_EncodeStringToUTF8BufferPartial(
155
+ env->cx, str, mozilla::Span<char>(buf, bufsize - 1));
156
+ if (encoded.isNothing()) {
157
+ return gjsify_napi::set_last_error(env, napi_generic_failure);
158
+ }
159
+ const size_t written = std::get<1>(*encoded);
160
+ buf[written] = '\0';
161
+ if (result != nullptr) {
162
+ *result = written;
163
+ }
164
+ } else if (result != nullptr) {
165
+ *result = 0;
166
+ }
167
+ return gjsify_napi::clear_last_error(env);
168
+ }
169
+
170
+ napi_status NAPI_CDECL napi_set_named_property(napi_env env, napi_value object,
171
+ const char* utf8name,
172
+ napi_value value) {
173
+ GJSIFY_NAPI_PREAMBLE(env); // property sets can run JS (setters/proxies)
174
+ GJSIFY_NAPI_CHECK_ARG(env, object);
175
+ GJSIFY_NAPI_CHECK_ARG(env, utf8name);
176
+ GJSIFY_NAPI_CHECK_ARG(env, value);
177
+ JS::Value target = gjsify_napi::napi_value_to_js(object);
178
+ GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(env, target.isObject(),
179
+ napi_object_expected);
180
+ JS::RootedObject obj(env->cx, &target.toObject());
181
+ JS::RootedValue v(env->cx, gjsify_napi::napi_value_to_js(value));
182
+ if (!JS_SetProperty(env->cx, obj, utf8name, v)) {
183
+ // §6 must-not-abort contract: a JS throw stays pending; a false
184
+ // return without a pending exception (OOM/over-recursion) is the SM
185
+ // analog of V8 termination.
186
+ return gjsify_napi::set_last_error(
187
+ env, JS_IsExceptionPending(env->cx) ? napi_pending_exception
188
+ : napi_generic_failure);
189
+ }
190
+ return napi_ok; // preamble already cleared last_error
191
+ }
192
+
193
+ napi_status NAPI_CDECL napi_get_named_property(napi_env env, napi_value object,
194
+ const char* utf8name,
195
+ napi_value* result) {
196
+ GJSIFY_NAPI_PREAMBLE(env); // property gets can run JS (getters/proxies)
197
+ GJSIFY_NAPI_CHECK_ARG(env, object);
198
+ GJSIFY_NAPI_CHECK_ARG(env, utf8name);
199
+ GJSIFY_NAPI_CHECK_ARG(env, result);
200
+ JS::Value target = gjsify_napi::napi_value_to_js(object);
201
+ GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(env, target.isObject(),
202
+ napi_object_expected);
203
+ JS::RootedObject obj(env->cx, &target.toObject());
204
+ JS::RootedValue v(env->cx);
205
+ if (!JS_GetProperty(env->cx, obj, utf8name, &v)) {
206
+ return gjsify_napi::set_last_error(
207
+ env, JS_IsExceptionPending(env->cx) ? napi_pending_exception
208
+ : napi_generic_failure);
209
+ }
210
+ *result = gjsify_napi::arena_push(env, v);
211
+ return napi_ok;
212
+ }
213
+
214
+ // ---- number create (js_native_api_v8.cc: plain CHECK_ENV/ARG, no
215
+ // preamble — number creation cannot run JS) ----
216
+
217
+ napi_status NAPI_CDECL napi_create_double(napi_env env, double value,
218
+ napi_value* result) {
219
+ GJSIFY_NAPI_CHECK_ENV(env);
220
+ GJSIFY_NAPI_CHECK_ARG(env, result);
221
+ *result = gjsify_napi::arena_push(env, JS::NumberValue(value));
222
+ return gjsify_napi::clear_last_error(env);
223
+ }
224
+
225
+ napi_status NAPI_CDECL napi_create_int32(napi_env env, int32_t value,
226
+ napi_value* result) {
227
+ GJSIFY_NAPI_CHECK_ENV(env);
228
+ GJSIFY_NAPI_CHECK_ARG(env, result);
229
+ *result = gjsify_napi::arena_push(env, JS::Int32Value(value));
230
+ return gjsify_napi::clear_last_error(env);
231
+ }
232
+
233
+ napi_status NAPI_CDECL napi_create_uint32(napi_env env, uint32_t value,
234
+ napi_value* result) {
235
+ GJSIFY_NAPI_CHECK_ENV(env);
236
+ GJSIFY_NAPI_CHECK_ARG(env, result);
237
+ *result = gjsify_napi::arena_push(env, JS::NumberValue(value));
238
+ return gjsify_napi::clear_last_error(env);
239
+ }
240
+
241
+ napi_status NAPI_CDECL napi_create_int64(napi_env env, int64_t value,
242
+ napi_value* result) {
243
+ GJSIFY_NAPI_CHECK_ENV(env);
244
+ GJSIFY_NAPI_CHECK_ARG(env, result);
245
+ // Node: v8::Number::New(double) — precision loss beyond 2^53 is the
246
+ // documented napi_create_int64 behavior.
247
+ *result = gjsify_napi::arena_push(
248
+ env, JS::NumberValue(static_cast<double>(value)));
249
+ return gjsify_napi::clear_last_error(env);
250
+ }
251
+
252
+ napi_status NAPI_CDECL napi_create_bigint_int64(napi_env env, int64_t value,
253
+ napi_value* result) {
254
+ GJSIFY_NAPI_CHECK_ENV(env);
255
+ GJSIFY_NAPI_CHECK_ARG(env, result);
256
+ // Public wrapper over JS::detail::BigIntFromInt64 (js/BigInt.h:124-127).
257
+ JS::BigInt* bi = JS::NumberToBigInt(env->cx, value);
258
+ if (bi == nullptr) {
259
+ return gjsify_napi::set_last_error(env, napi_generic_failure);
260
+ }
261
+ *result = gjsify_napi::arena_push(env, JS::BigIntValue(bi));
262
+ return gjsify_napi::clear_last_error(env);
263
+ }
264
+
265
+ // ---- number extract (exact V8-Node truncation semantics) ----
266
+
267
+ napi_status NAPI_CDECL napi_get_value_double(napi_env env, napi_value value,
268
+ double* result) {
269
+ GJSIFY_NAPI_CHECK_ENV(env);
270
+ GJSIFY_NAPI_CHECK_ARG(env, value);
271
+ GJSIFY_NAPI_CHECK_ARG(env, result);
272
+ JS::Value v = gjsify_napi::napi_value_to_js(value);
273
+ GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(env, v.isNumber(),
274
+ napi_number_expected);
275
+ *result = v.toNumber();
276
+ return gjsify_napi::clear_last_error(env);
277
+ }
278
+
279
+ napi_status NAPI_CDECL napi_get_value_int32(napi_env env, napi_value value,
280
+ int32_t* result) {
281
+ GJSIFY_NAPI_CHECK_ENV(env);
282
+ GJSIFY_NAPI_CHECK_ARG(env, value);
283
+ GJSIFY_NAPI_CHECK_ARG(env, result);
284
+ JS::Value v = gjsify_napi::napi_value_to_js(value);
285
+ if (v.isInt32()) {
286
+ *result = v.toInt32();
287
+ } else {
288
+ GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(env, v.isNumber(),
289
+ napi_number_expected);
290
+ // ECMA ToInt32 on the double: modulo 2^32, NaN/±Inf → 0 — exactly
291
+ // V8's Int32Value (js_native_api_v8.cc:2340-2360).
292
+ *result = JS::ToInt32(v.toDouble());
293
+ }
294
+ return gjsify_napi::clear_last_error(env);
295
+ }
296
+
297
+ napi_status NAPI_CDECL napi_get_value_uint32(napi_env env, napi_value value,
298
+ uint32_t* result) {
299
+ GJSIFY_NAPI_CHECK_ENV(env);
300
+ GJSIFY_NAPI_CHECK_ARG(env, value);
301
+ GJSIFY_NAPI_CHECK_ARG(env, result);
302
+ JS::Value v = gjsify_napi::napi_value_to_js(value);
303
+ if (v.isInt32()) {
304
+ *result = static_cast<uint32_t>(v.toInt32());
305
+ } else {
306
+ GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(env, v.isNumber(),
307
+ napi_number_expected);
308
+ *result = JS::ToUint32(v.toDouble()); // ECMA ToUint32
309
+ }
310
+ return gjsify_napi::clear_last_error(env);
311
+ }
312
+
313
+ napi_status NAPI_CDECL napi_get_value_int64(napi_env env, napi_value value,
314
+ int64_t* result) {
315
+ GJSIFY_NAPI_CHECK_ENV(env);
316
+ GJSIFY_NAPI_CHECK_ARG(env, value);
317
+ GJSIFY_NAPI_CHECK_ARG(env, result);
318
+ JS::Value v = gjsify_napi::napi_value_to_js(value);
319
+ if (v.isInt32()) {
320
+ *result = v.toInt32();
321
+ return gjsify_napi::clear_last_error(env);
322
+ }
323
+ GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(env, v.isNumber(),
324
+ napi_number_expected);
325
+ // V8-Node semantics (js_native_api_v8.cc:2383-2413): NaN/±Inf → 0
326
+ // (special-cased), finite → v8 IntegerValue = truncate toward zero,
327
+ // SATURATING at the int64 range (not ECMA-modular like JS::ToInt64).
328
+ const double d = v.toDouble();
329
+ if (!std::isfinite(d)) {
330
+ *result = 0;
331
+ } else if (d >= static_cast<double>(INT64_MAX)) {
332
+ *result = INT64_MAX;
333
+ } else if (d <= static_cast<double>(INT64_MIN)) {
334
+ *result = INT64_MIN;
335
+ } else {
336
+ *result = static_cast<int64_t>(d);
337
+ }
338
+ return gjsify_napi::clear_last_error(env);
339
+ }
340
+
341
+ napi_status NAPI_CDECL napi_get_value_bool(napi_env env, napi_value value,
342
+ bool* result) {
343
+ GJSIFY_NAPI_CHECK_ENV(env);
344
+ GJSIFY_NAPI_CHECK_ARG(env, value);
345
+ GJSIFY_NAPI_CHECK_ARG(env, result);
346
+ JS::Value v = gjsify_napi::napi_value_to_js(value);
347
+ GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(env, v.isBoolean(),
348
+ napi_boolean_expected);
349
+ *result = v.toBoolean();
350
+ return gjsify_napi::clear_last_error(env);
351
+ }
352
+
353
+ napi_status NAPI_CDECL napi_get_value_bigint_int64(napi_env env,
354
+ napi_value value,
355
+ int64_t* result,
356
+ bool* lossless) {
357
+ GJSIFY_NAPI_CHECK_ENV(env);
358
+ GJSIFY_NAPI_CHECK_ARG(env, value);
359
+ GJSIFY_NAPI_CHECK_ARG(env, result);
360
+ GJSIFY_NAPI_CHECK_ARG(env, lossless);
361
+ JS::Value v = gjsify_napi::napi_value_to_js(value);
362
+ GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(env, v.isBigInt(),
363
+ napi_bigint_expected);
364
+ JS::BigInt* bi = v.toBigInt();
365
+ // Public wrapper over JS::detail::BigIntIsInt64 (js/BigInt.h:208).
366
+ if (JS::BigIntFits(bi, result)) {
367
+ *lossless = true;
368
+ } else {
369
+ // v8::BigInt::Int64Value: two's-complement truncation, lossless=false.
370
+ *lossless = false;
371
+ *result = JS::ToBigInt64(bi);
372
+ }
373
+ return gjsify_napi::clear_last_error(env);
374
+ }
375
+
376
+ // ---- more string creates (§8 exact Node semantics) ----
377
+
378
+ napi_status NAPI_CDECL napi_create_string_latin1(napi_env env, const char* str,
379
+ size_t length,
380
+ napi_value* result) {
381
+ GJSIFY_NAPI_CHECK_ENV(env);
382
+ GJSIFY_NAPI_CHECK_ARG(env, result);
383
+ GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(
384
+ env, str != nullptr || length == 0, napi_invalid_arg);
385
+ GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(
386
+ env, length == NAPI_AUTO_LENGTH || length <= INT32_MAX,
387
+ napi_invalid_arg);
388
+ const size_t len = (length == NAPI_AUTO_LENGTH) ? strlen(str) : length;
389
+ // Narrow copy = Latin-1 storage in SpiderMonkey (js/String.h:63) —
390
+ // exactly the byte-preserving semantic v8::String::NewFromOneByte has.
391
+ JSString* js_str = JS_NewStringCopyN(env->cx, str, len);
392
+ if (js_str == nullptr) {
393
+ return gjsify_napi::set_last_error(env, napi_generic_failure);
394
+ }
395
+ *result = gjsify_napi::arena_push(env, JS::StringValue(js_str));
396
+ return gjsify_napi::clear_last_error(env);
397
+ }
398
+
399
+ napi_status NAPI_CDECL napi_create_string_utf16(napi_env env,
400
+ const char16_t* str,
401
+ size_t length,
402
+ napi_value* result) {
403
+ GJSIFY_NAPI_CHECK_ENV(env);
404
+ GJSIFY_NAPI_CHECK_ARG(env, result);
405
+ GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(
406
+ env, str != nullptr || length == 0, napi_invalid_arg);
407
+ GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(
408
+ env, length == NAPI_AUTO_LENGTH || length <= INT32_MAX,
409
+ napi_invalid_arg);
410
+ const size_t len = (length == NAPI_AUTO_LENGTH)
411
+ ? std::char_traits<char16_t>::length(str)
412
+ : length;
413
+ JSString* js_str = JS_NewUCStringCopyN(env->cx, str, len);
414
+ if (js_str == nullptr) {
415
+ return gjsify_napi::set_last_error(env, napi_generic_failure);
416
+ }
417
+ *result = gjsify_napi::arena_push(env, JS::StringValue(js_str));
418
+ return gjsify_napi::clear_last_error(env);
419
+ }
420
+
421
+ napi_status NAPI_CDECL napi_create_symbol(napi_env env, napi_value description,
422
+ napi_value* result) {
423
+ GJSIFY_NAPI_CHECK_ENV(env);
424
+ GJSIFY_NAPI_CHECK_ARG(env, result);
425
+ JS::RootedString desc(env->cx);
426
+ if (description != nullptr) {
427
+ JS::Value d = gjsify_napi::napi_value_to_js(description);
428
+ GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(env, d.isString(),
429
+ napi_string_expected);
430
+ desc = d.toString();
431
+ }
432
+ JS::Symbol* sym = JS::NewSymbol(env->cx, desc);
433
+ if (sym == nullptr) {
434
+ return gjsify_napi::set_last_error(env, napi_generic_failure);
435
+ }
436
+ *result = gjsify_napi::arena_push(env, JS::SymbolValue(sym));
437
+ return gjsify_napi::clear_last_error(env);
438
+ }
439
+
440
+ // The registered-symbol counterpart of napi_create_symbol: `Symbol.for(desc)`.
441
+ // Non-experimental since NAPI_VERSION 9 (js_native_api.h). SpiderMonkey's
442
+ // JS::GetSymbolFor is the exact analogue of V8's Symbol::For that Node uses.
443
+ napi_status NAPI_CDECL node_api_symbol_for(napi_env env,
444
+ const char* utf8description,
445
+ size_t length, napi_value* result) {
446
+ GJSIFY_NAPI_CHECK_ENV(env);
447
+ GJSIFY_NAPI_CHECK_ARG(env, result);
448
+ GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(
449
+ env, utf8description != nullptr || length == 0, napi_invalid_arg);
450
+ const size_t len =
451
+ (length == NAPI_AUTO_LENGTH) ? strlen(utf8description) : length;
452
+ JS::RootedString key(
453
+ env->cx, gjsify_napi::new_string_utf8_lossy(env->cx, utf8description, len));
454
+ if (key == nullptr) {
455
+ return gjsify_napi::set_last_error(env, napi_generic_failure);
456
+ }
457
+ JS::Symbol* sym = JS::GetSymbolFor(env->cx, key);
458
+ if (sym == nullptr) {
459
+ return gjsify_napi::set_last_error(env, napi_generic_failure);
460
+ }
461
+ *result = gjsify_napi::arena_push(env, JS::SymbolValue(sym));
462
+ return gjsify_napi::clear_last_error(env);
463
+ }
464
+
465
+ // ---- more string extracts (exact Node write/NUL/length semantics,
466
+ // js_native_api_v8.cc:2508-2536 latin1, 2584-2617 utf16) ----
467
+
468
+ napi_status NAPI_CDECL napi_get_value_string_latin1(napi_env env,
469
+ napi_value value,
470
+ char* buf, size_t bufsize,
471
+ size_t* result) {
472
+ GJSIFY_NAPI_CHECK_ENV(env);
473
+ GJSIFY_NAPI_CHECK_ARG(env, value);
474
+ JS::Value v = gjsify_napi::napi_value_to_js(value);
475
+ GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(env, v.isString(),
476
+ napi_string_expected);
477
+ JSString* str = v.toString();
478
+
479
+ if (buf == nullptr) {
480
+ GJSIFY_NAPI_CHECK_ARG(env, result);
481
+ *result = JS_GetStringLength(str); // code units, like str->Length()
482
+ } else if (bufsize != 0) {
483
+ const size_t length =
484
+ std::min(bufsize - 1, JS_GetStringLength(str));
485
+ JSLinearString* linear = JS_EnsureLinearString(env->cx, str);
486
+ if (linear == nullptr) {
487
+ return gjsify_napi::set_last_error(env, napi_generic_failure);
488
+ }
489
+ {
490
+ JS::AutoCheckCannotGC nogc;
491
+ if (JS::LinearStringHasLatin1Chars(linear)) {
492
+ const JS::Latin1Char* chars =
493
+ JS::GetLatin1LinearStringChars(nogc, linear);
494
+ memcpy(buf, chars, length);
495
+ } else {
496
+ // WriteOneByteV2 semantics: UTF-16 units truncated to the
497
+ // low byte.
498
+ const char16_t* chars =
499
+ JS::GetTwoByteLinearStringChars(nogc, linear);
500
+ for (size_t i = 0; i < length; i++) {
501
+ buf[i] = static_cast<char>(chars[i] & 0xFF);
502
+ }
503
+ }
504
+ }
505
+ buf[length] = '\0';
506
+ if (result != nullptr) {
507
+ *result = length;
508
+ }
509
+ } else if (result != nullptr) {
510
+ *result = 0;
511
+ }
512
+ return gjsify_napi::clear_last_error(env);
513
+ }
514
+
515
+ napi_status NAPI_CDECL napi_get_value_string_utf16(napi_env env,
516
+ napi_value value,
517
+ char16_t* buf,
518
+ size_t bufsize,
519
+ size_t* result) {
520
+ GJSIFY_NAPI_CHECK_ENV(env);
521
+ GJSIFY_NAPI_CHECK_ARG(env, value);
522
+ JS::Value v = gjsify_napi::napi_value_to_js(value);
523
+ GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(env, v.isString(),
524
+ napi_string_expected);
525
+ JSString* str = v.toString();
526
+
527
+ if (buf == nullptr) {
528
+ GJSIFY_NAPI_CHECK_ARG(env, result);
529
+ *result = JS_GetStringLength(str); // 2-byte code units
530
+ } else if (bufsize != 0) {
531
+ const size_t length =
532
+ std::min(bufsize - 1, JS_GetStringLength(str));
533
+ JSLinearString* linear = JS_EnsureLinearString(env->cx, str);
534
+ if (linear == nullptr) {
535
+ return gjsify_napi::set_last_error(env, napi_generic_failure);
536
+ }
537
+ {
538
+ JS::AutoCheckCannotGC nogc;
539
+ if (JS::LinearStringHasLatin1Chars(linear)) {
540
+ const JS::Latin1Char* chars =
541
+ JS::GetLatin1LinearStringChars(nogc, linear);
542
+ for (size_t i = 0; i < length; i++) {
543
+ buf[i] = static_cast<char16_t>(chars[i]);
544
+ }
545
+ } else {
546
+ const char16_t* chars =
547
+ JS::GetTwoByteLinearStringChars(nogc, linear);
548
+ memcpy(buf, chars, length * sizeof(char16_t));
549
+ }
550
+ }
551
+ buf[length] = u'\0';
552
+ if (result != nullptr) {
553
+ *result = length;
554
+ }
555
+ } else if (result != nullptr) {
556
+ *result = 0;
557
+ }
558
+ return gjsify_napi::clear_last_error(env);
559
+ }
560
+
561
+ // ---- coercions (NAPI_PREAMBLE like Node — all but ToBoolean can run JS,
562
+ // js_native_api_v8.cc:2619-2651) ----
563
+
564
+ napi_status NAPI_CDECL napi_coerce_to_bool(napi_env env, napi_value value,
565
+ napi_value* result) {
566
+ GJSIFY_NAPI_PREAMBLE(env);
567
+ GJSIFY_NAPI_CHECK_ARG(env, value);
568
+ GJSIFY_NAPI_CHECK_ARG(env, result);
569
+ JS::RootedValue v(env->cx, gjsify_napi::napi_value_to_js(value));
570
+ *result =
571
+ gjsify_napi::arena_push(env, JS::BooleanValue(JS::ToBoolean(v)));
572
+ return napi_ok; // preamble already cleared last_error
573
+ }
574
+
575
+ napi_status NAPI_CDECL napi_coerce_to_number(napi_env env, napi_value value,
576
+ napi_value* result) {
577
+ GJSIFY_NAPI_PREAMBLE(env);
578
+ GJSIFY_NAPI_CHECK_ARG(env, value);
579
+ GJSIFY_NAPI_CHECK_ARG(env, result);
580
+ JS::RootedValue v(env->cx, gjsify_napi::napi_value_to_js(value));
581
+ double d;
582
+ if (!JS::ToNumber(env->cx, v, &d)) {
583
+ // valueOf/toString threw, or ToNumber(symbol/bigint) TypeError.
584
+ return gjsify_napi::set_last_error(
585
+ env, JS_IsExceptionPending(env->cx) ? napi_pending_exception
586
+ : napi_generic_failure);
587
+ }
588
+ *result = gjsify_napi::arena_push(env, JS::NumberValue(d));
589
+ return napi_ok;
590
+ }
591
+
592
+ napi_status NAPI_CDECL napi_coerce_to_object(napi_env env, napi_value value,
593
+ napi_value* result) {
594
+ GJSIFY_NAPI_PREAMBLE(env);
595
+ GJSIFY_NAPI_CHECK_ARG(env, value);
596
+ GJSIFY_NAPI_CHECK_ARG(env, result);
597
+ JS::RootedValue v(env->cx, gjsify_napi::napi_value_to_js(value));
598
+ JSObject* obj = JS::ToObject(env->cx, v);
599
+ if (obj == nullptr) {
600
+ return gjsify_napi::set_last_error(
601
+ env, JS_IsExceptionPending(env->cx) ? napi_pending_exception
602
+ : napi_generic_failure);
603
+ }
604
+ *result = gjsify_napi::arena_push(env, JS::ObjectValue(*obj));
605
+ return napi_ok;
606
+ }
607
+
608
+ napi_status NAPI_CDECL napi_coerce_to_string(napi_env env, napi_value value,
609
+ napi_value* result) {
610
+ GJSIFY_NAPI_PREAMBLE(env);
611
+ GJSIFY_NAPI_CHECK_ARG(env, value);
612
+ GJSIFY_NAPI_CHECK_ARG(env, result);
613
+ JS::RootedValue v(env->cx, gjsify_napi::napi_value_to_js(value));
614
+ JSString* str = JS::ToString(env->cx, v);
615
+ if (str == nullptr) {
616
+ return gjsify_napi::set_last_error(
617
+ env, JS_IsExceptionPending(env->cx) ? napi_pending_exception
618
+ : napi_generic_failure);
619
+ }
620
+ *result = gjsify_napi::arena_push(env, JS::StringValue(str));
621
+ return napi_ok;
622
+ }
623
+
624
+ napi_status NAPI_CDECL napi_typeof(napi_env env, napi_value value,
625
+ napi_valuetype* result) {
626
+ GJSIFY_NAPI_CHECK_ENV(env);
627
+ GJSIFY_NAPI_CHECK_ARG(env, value);
628
+ GJSIFY_NAPI_CHECK_ARG(env, result);
629
+ JS::Value v = gjsify_napi::napi_value_to_js(value);
630
+ if (v.isNumber()) {
631
+ *result = napi_number;
632
+ } else if (v.isBigInt()) {
633
+ *result = napi_bigint;
634
+ } else if (v.isString()) {
635
+ *result = napi_string;
636
+ } else if (v.isBoolean()) {
637
+ *result = napi_boolean;
638
+ } else if (v.isUndefined()) {
639
+ *result = napi_undefined;
640
+ } else if (v.isSymbol()) {
641
+ *result = napi_symbol;
642
+ } else if (v.isNull()) {
643
+ *result = napi_null;
644
+ } else if (v.isObject()) {
645
+ JSObject* obj = &v.toObject();
646
+ if (JS::GetClass(obj) == &gjsify_napi::external_class) {
647
+ *result = napi_external;
648
+ } else {
649
+ *result = JS::IsCallable(obj) ? napi_function : napi_object;
650
+ }
651
+ } else {
652
+ // Mirror the V8 impl's defensive default (js_native_api_v8.cc).
653
+ return gjsify_napi::set_last_error(env, napi_invalid_arg);
654
+ }
655
+ return gjsify_napi::clear_last_error(env);
656
+ }