-
-
Save xeioex/5cba197a6792e93acec114f1a2aff4b3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| commit 8bde90b077a71bc0b3ce9331ae094a333ea35383 | |
| Author: Dmitry Volyntsev <[email protected]> | |
| Date: Tue Aug 26 14:22:19 2025 -0700 | |
| QuickJS: added njs.on('exit') API support. | |
| diff --git a/external/njs_shell.c b/external/njs_shell.c | |
| index 75316339..dd4e08ba 100644 | |
| --- a/external/njs_shell.c | |
| +++ b/external/njs_shell.c | |
| @@ -2702,6 +2702,8 @@ njs_engine_qjs_destroy(njs_engine_t *engine) | |
| njs_queue_link_t *link; | |
| njs_rejected_promise_t *rejected_promise; | |
| + qjs_call_exit_hook(engine->u.qjs.ctx); | |
| + | |
| console = JS_GetRuntimeOpaque(engine->u.qjs.rt); | |
| if (console->rejected_promises != NULL) { | |
| diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c | |
| index 0678c3e6..b2be0f19 100644 | |
| --- a/nginx/ngx_js.c | |
| +++ b/nginx/ngx_js.c | |
| @@ -1142,6 +1142,8 @@ ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, | |
| cx = e->u.qjs.ctx; | |
| if (ctx != NULL) { | |
| + qjs_call_exit_hook(cx); | |
| + | |
| node = njs_rbtree_min(&ctx->waiting_events); | |
| while (njs_rbtree_is_there_successor(&ctx->waiting_events, node)) { | |
| diff --git a/nginx/t/stream_js_exit.t b/nginx/t/stream_js_exit.t | |
| index 01778f0f..41fbe1b3 100644 | |
| --- a/nginx/t/stream_js_exit.t | |
| +++ b/nginx/t/stream_js_exit.t | |
| @@ -108,8 +108,6 @@ EOF | |
| $t->try_run('no stream njs available'); | |
| -plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; | |
| - | |
| $t->plan(2); | |
| $t->run_daemon(\&stream_daemon, port(8090)); | |
| diff --git a/src/qjs.c b/src/qjs.c | |
| index 9c0fcdb4..677b1e5a 100644 | |
| --- a/src/qjs.c | |
| +++ b/src/qjs.c | |
| @@ -19,6 +19,12 @@ typedef struct { | |
| } qjs_signal_entry_t; | |
| +typedef struct { | |
| +#define QJS_NJS_HOOK_EXIT 0 | |
| + JSValue hooks[1]; | |
| +} qjs_njs_t; | |
| + | |
| + | |
| typedef enum { | |
| QJS_ENCODING_UTF8, | |
| } qjs_encoding_t; | |
| @@ -42,7 +48,13 @@ typedef struct { | |
| extern char **environ; | |
| -static JSValue qjs_njs_getter(JSContext *ctx, JSValueConst this_val); | |
| +static int qjs_add_intrinsic_njs(JSContext *cx, JSValueConst global); | |
| +static JSValue qjs_njs_on(JSContext *ctx, JSValueConst this_val, int argc, | |
| + JSValueConst *argv); | |
| +static void qjs_njs_mark(JSRuntime *rt, JSValueConst val, | |
| + JS_MarkFunc *mark_func); | |
| +static void qjs_njs_finalizer(JSRuntime *rt, JSValue val); | |
| + | |
| static JSValue qjs_process_env(JSContext *ctx, JSValueConst this_val); | |
| static JSValue qjs_process_kill(JSContext *ctx, JSValueConst this_val, | |
| int argc, JSValueConst *argv); | |
| @@ -99,10 +111,6 @@ static qjs_encoding_label_t qjs_encoding_labels[] = | |
| }; | |
| -static const JSCFunctionListEntry qjs_global_proto[] = { | |
| - JS_CGETSET_DEF("njs", qjs_njs_getter, NULL), | |
| -}; | |
| - | |
| static const JSCFunctionListEntry qjs_text_decoder_proto[] = { | |
| JS_PROP_STRING_DEF("[Symbol.toStringTag]", "TextDecoder", | |
| JS_PROP_CONFIGURABLE), | |
| @@ -126,6 +134,7 @@ static const JSCFunctionListEntry qjs_njs_proto[] = { | |
| JS_PROP_INT32_DEF("version_number", NJS_VERSION_NUMBER, | |
| JS_PROP_C_W_E), | |
| JS_PROP_STRING_DEF("engine", "QuickJS", JS_PROP_C_W_E), | |
| + JS_CFUNC_DEF("on", 2, qjs_njs_on), | |
| }; | |
| static const JSCFunctionListEntry qjs_process_proto[] = { | |
| @@ -137,6 +146,13 @@ static const JSCFunctionListEntry qjs_process_proto[] = { | |
| }; | |
| +static JSClassDef qjs_njs_class = { | |
| + "njs", | |
| + .finalizer = qjs_njs_finalizer, | |
| + .gc_mark = qjs_njs_mark, | |
| +}; | |
| + | |
| + | |
| static JSClassDef qjs_text_decoder_class = { | |
| "TextDecoder", | |
| .finalizer = qjs_text_decoder_finalizer, | |
| @@ -186,6 +202,10 @@ qjs_new_context(JSRuntime *rt, qjs_module_t **addons) | |
| global_obj = JS_GetGlobalObject(ctx); | |
| + if (qjs_add_intrinsic_njs(ctx, global_obj) < 0) { | |
| + return NULL; | |
| + } | |
| + | |
| if (qjs_add_intrinsic_text_decoder(ctx, global_obj) < 0) { | |
| return NULL; | |
| } | |
| @@ -194,9 +214,6 @@ qjs_new_context(JSRuntime *rt, qjs_module_t **addons) | |
| return NULL; | |
| } | |
| - JS_SetPropertyFunctionList(ctx, global_obj, qjs_global_proto, | |
| - njs_nitems(qjs_global_proto)); | |
| - | |
| prop = JS_NewAtom(ctx, "eval"); | |
| if (prop == JS_ATOM_NULL) { | |
| return NULL; | |
| @@ -225,20 +242,157 @@ qjs_new_context(JSRuntime *rt, qjs_module_t **addons) | |
| } | |
| -static JSValue | |
| -qjs_njs_getter(JSContext *ctx, JSValueConst this_val) | |
| +void | |
| +qjs_call_exit_hook(JSContext *ctx) | |
| { | |
| - JSValue obj; | |
| + JSValue global, obj, ret; | |
| + qjs_njs_t *njs; | |
| - obj = JS_NewObject(ctx); | |
| + global = JS_GetGlobalObject(ctx); | |
| + | |
| + obj = JS_GetPropertyStr(ctx, global, "njs"); | |
| + if (JS_IsException(obj) || JS_IsUndefined(obj)) { | |
| + goto done; | |
| + } | |
| + | |
| + njs = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_NJS); | |
| + if (njs != NULL && JS_IsFunction(ctx, njs->hooks[QJS_NJS_HOOK_EXIT])) { | |
| + ret = JS_Call(ctx, njs->hooks[QJS_NJS_HOOK_EXIT], JS_UNDEFINED, | |
| + 0, NULL); | |
| + | |
| + JS_FreeValue(ctx, ret); | |
| + } | |
| + | |
| +done: | |
| + | |
| + JS_FreeValue(ctx, obj); | |
| + JS_FreeValue(ctx, global); | |
| +} | |
| + | |
| + | |
| +static int | |
| +qjs_add_intrinsic_njs(JSContext *cx, JSValueConst global) | |
| +{ | |
| + JSValue obj, proto; | |
| + | |
| + if (JS_NewClass(JS_GetRuntime(cx), QJS_CORE_CLASS_ID_NJS, | |
| + &qjs_njs_class) < 0) | |
| + { | |
| + return -1; | |
| + } | |
| + | |
| + proto = JS_NewObject(cx); | |
| + if (JS_IsException(proto)) { | |
| + return -1; | |
| + } | |
| + | |
| + JS_SetPropertyFunctionList(cx, proto, qjs_njs_proto, | |
| + njs_nitems(qjs_njs_proto)); | |
| + | |
| + JS_SetClassProto(cx, QJS_CORE_CLASS_ID_NJS, proto); | |
| + | |
| + obj = JS_NewObjectClass(cx, QJS_CORE_CLASS_ID_NJS); | |
| if (JS_IsException(obj)) { | |
| + return -1; | |
| + } | |
| + | |
| + if (JS_SetPropertyStr(cx, global, "njs", obj) < 0) { | |
| + JS_FreeValue(cx, obj); | |
| + return -1; | |
| + } | |
| + | |
| + return 0; | |
| +} | |
| + | |
| + | |
| +static void | |
| +qjs_njs_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) | |
| +{ | |
| + unsigned i; | |
| + qjs_njs_t *njs; | |
| + | |
| + njs = JS_GetOpaque(val, QJS_CORE_CLASS_ID_NJS); | |
| + if (njs != NULL) { | |
| + for (i = 0; i < njs_nitems(njs->hooks); i++) { | |
| + JS_MarkValue(rt, njs->hooks[i], mark_func); | |
| + } | |
| + } | |
| +} | |
| + | |
| + | |
| +static void | |
| +qjs_njs_finalizer(JSRuntime *rt, JSValue val) | |
| +{ | |
| + unsigned i; | |
| + qjs_njs_t *njs; | |
| + | |
| + njs = JS_GetOpaque(val, QJS_CORE_CLASS_ID_NJS); | |
| + if (njs != NULL) { | |
| + for (i = 0; i < njs_nitems(njs->hooks); i++) { | |
| + JS_FreeValueRT(rt, njs->hooks[i]); | |
| + } | |
| + | |
| + js_free_rt(rt, njs); | |
| + } | |
| +} | |
| + | |
| + | |
| + | |
| +static JSValue | |
| +qjs_njs_on(JSContext *ctx, JSValueConst this_val, int argc, | |
| + JSValueConst *argv) | |
| +{ | |
| + unsigned i, n; | |
| + qjs_njs_t *njs; | |
| + njs_str_t name; | |
| + | |
| + static const njs_str_t hooks[] = { | |
| + njs_str("exit"), | |
| + }; | |
| + | |
| + njs = JS_GetOpaque(this_val, QJS_CORE_CLASS_ID_NJS); | |
| + if (njs == NULL) { | |
| + njs = js_mallocz(ctx, sizeof(qjs_njs_t)); | |
| + if (njs == NULL) { | |
| + return JS_ThrowOutOfMemory(ctx); | |
| + } | |
| + | |
| + JS_SetOpaque(this_val, njs); | |
| + } | |
| + | |
| + name.start = (u_char *) JS_ToCStringLen(ctx, &name.length, argv[0]); | |
| + if (name.start == NULL) { | |
| return JS_EXCEPTION; | |
| } | |
| - JS_SetPropertyFunctionList(ctx, obj, qjs_njs_proto, | |
| - njs_nitems(qjs_njs_proto)); | |
| + i = 0; | |
| + n = njs_nitems(hooks); | |
| - return obj; | |
| + while (i < n) { | |
| + if (njs_strstr_eq(&name, &hooks[i])) { | |
| + break; | |
| + } | |
| + | |
| + i++; | |
| + } | |
| + | |
| + if (i == n) { | |
| + JS_ThrowTypeError(ctx, "unknown hook \"%s\"", name.start); | |
| + JS_FreeCString(ctx, (const char *) name.start); | |
| + return JS_EXCEPTION; | |
| + } | |
| + | |
| + JS_FreeCString(ctx, (const char *) name.start); | |
| + | |
| + if (!JS_IsFunction(ctx, argv[1]) && !JS_IsNull(argv[1])) { | |
| + JS_ThrowTypeError(ctx, "callback is not a function or null"); | |
| + return JS_EXCEPTION; | |
| + } | |
| + | |
| + JS_FreeValue(ctx, njs->hooks[i]); | |
| + njs->hooks[i] = JS_DupValue(ctx, argv[1]); | |
| + | |
| + return JS_UNDEFINED; | |
| } | |
| diff --git a/src/qjs.h b/src/qjs.h | |
| index fe26a78a..7cc99023 100644 | |
| --- a/src/qjs.h | |
| +++ b/src/qjs.h | |
| @@ -48,6 +48,7 @@ enum { | |
| QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR, | |
| QJS_CORE_CLASS_ID_TEXT_DECODER, | |
| QJS_CORE_CLASS_ID_TEXT_ENCODER, | |
| + QJS_CORE_CLASS_ID_NJS, | |
| QJS_CORE_CLASS_ID_FS_STATS, | |
| QJS_CORE_CLASS_ID_FS_DIRENT, | |
| QJS_CORE_CLASS_ID_FS_FILEHANDLE, | |
| @@ -70,6 +71,7 @@ typedef struct { | |
| JSContext *qjs_new_context(JSRuntime *rt, qjs_module_t **addons); | |
| +void qjs_call_exit_hook(JSContext *ctx); | |
| JSValue qjs_new_uint8_array(JSContext *ctx, int argc, JSValueConst *argv); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment