diff --git a/docs/02.API-REFERENCE.md b/docs/02.API-REFERENCE.md index a66915469..26f025415 100644 --- a/docs/02.API-REFERENCE.md +++ b/docs/02.API-REFERENCE.md @@ -908,6 +908,19 @@ Possible values: - JERRY_PROMISE_EVENT_REJECT - Called when a Promise is about to be rejected. - object - the Promise object - value - value for rejecting. +- JERRY_PROMISE_EVENT_RESOLVE_FULFILLED - Called when a resolve is called on a fulfilled Promise. + - object - the Promise object + - value - value for resolving +- JERRY_PROMISE_EVENT_REJECT_FULFILLED - Called when a reject is called on a fulfilled Promise. + - object - the Promise object + - value - value for rejecting +- JERRY_PROMISE_EVENT_REJECT_WITHOUT_HANDLER - Called when a Promise is rejected without a handler. + - object - the Promise object + - value - value for rejecting +- JERRY_PROMISE_EVENT_CATCH_HANDLER_ADDED - Called when a catch handler is added to a rejected + Promise which did not have a catch handler before. + - object - the Promise object + - value - undefined - JERRY_PROMISE_EVENT_BEFORE_REACTION_JOB - Called before executing a Promise reaction job. - object - the Promise object - value - undefined @@ -938,6 +951,43 @@ Possible values: - [jerry_promise_set_callback](#jerry_promise_set_callback) +## jerry_promise_event_filter_t + +Filter types for [jerry_promise_set_callback](#jerry_promise_set_callback) callback function. +The callback is only called for those events which are enabled by the filters. The events are +described in [jerry_promise_event_type_t](#jerry_promise_event_type_t). + +Possible values: + +- JERRY_PROMISE_EVENT_FILTER_DISABLE - Disable reporting of all events. +- JERRY_PROMISE_EVENT_FILTER_MAIN - Enables the following events: + - JERRY_PROMISE_EVENT_CREATE + - JERRY_PROMISE_EVENT_RESOLVE + - JERRY_PROMISE_EVENT_REJECT +- JERRY_PROMISE_EVENT_FILTER_ERROR - Enables the following events: + - JERRY_PROMISE_EVENT_RESOLVE_FULFILLED + - JERRY_PROMISE_EVENT_REJECT_FULFILLED + - JERRY_PROMISE_EVENT_REJECT_WITHOUT_HANDLER + - JERRY_PROMISE_EVENT_CATCH_HANDLER_ADDED +- JERRY_PROMISE_EVENT_FILTER_REACTION_JOB - Enables the following events: + - JERRY_PROMISE_EVENT_BEFORE_REACTION_JOB + - JERRY_PROMISE_EVENT_AFTER_REACTION_JOB +- JERRY_PROMISE_EVENT_FILTER_ASYNC_MAIN - Enables the following events: + - JERRY_PROMISE_EVENT_ASYNC_AWAIT +- JERRY_PROMISE_EVENT_FILTER_ASYNC_REACTION_JOB - Enables the following events: + - JERRY_PROMISE_EVENT_ASYNC_BEFORE_RESOLVE + - JERRY_PROMISE_EVENT_ASYNC_BEFORE_REJECT + - JERRY_PROMISE_EVENT_ASYNC_AFTER_RESOLVE + - JERRY_PROMISE_EVENT_ASYNC_AFTER_REJECT + +*New in version [[NEXT_RELEASE]]*. + +**See also** + +- [jerry_promise_event_type_t](#jerry_promise_event_type_t) +- [jerry_promise_set_callback](#jerry_promise_set_callback) + + ## jerry_promise_callback_t **Summary** @@ -4311,9 +4361,11 @@ Sets a callback for tracking Promise and async operations. **Prototype** ```c -void jerry_promise_set_callback (jerry_promise_callback_t callback, void *user_p); +void jerry_promise_set_callback (jerry_promise_event_filter_t filters, jerry_promise_callback_t callback, + void *user_p); ``` +- `filters` - combination of [jerry_promise_event_filter_t](#jerry_promise_event_filter_t) options - `callback` - callback function, the previously set value is overwritten, and setting NULL disables the tracking - `user_p` - pointer passed to the callback function, can be NULL @@ -4351,7 +4403,7 @@ main (void) { jerry_init (JERRY_INIT_EMPTY); - jerry_promise_set_callback (promise_callback, NULL); + jerry_promise_set_callback (JERRY_PROMISE_EVENT_FILTER_MAIN, promise_callback, NULL); const char *source_p = "var p = Promise.resolve(0)\n" "p.then(function (v) { return v; })"; diff --git a/jerry-core/api/jerry.c b/jerry-core/api/jerry.c index 4abc3da80..368e42101 100644 --- a/jerry-core/api/jerry.c +++ b/jerry-core/api/jerry.c @@ -4358,15 +4358,24 @@ jerry_get_promise_state (const jerry_value_t promise) /**< promise object to get * Note: * the previous callback is overwritten */ -void jerry_promise_set_callback (jerry_promise_callback_t callback, /**< notification callback */ +void jerry_promise_set_callback (jerry_promise_event_filter_t filters, /**< combination of event filters */ + jerry_promise_callback_t callback, /**< notification callback */ void *user_p) /**< user pointer passed to the callback */ { jerry_assert_api_available (); #if JERRY_BUILTIN_PROMISE && JERRY_PROMISE_CALLBACK + if (filters == JERRY_PROMISE_EVENT_FILTER_DISABLE || callback == NULL) + { + JERRY_CONTEXT (promise_callback_filters) = JERRY_PROMISE_EVENT_FILTER_DISABLE; + return; + } + + JERRY_CONTEXT (promise_callback_filters) = (uint32_t) filters; JERRY_CONTEXT (promise_callback) = callback; JERRY_CONTEXT (promise_callback_user_p) = user_p; #else /* !JERRY_BUILTIN_PROMISE && !JERRY_PROMISE_CALLBACK */ + JERRY_UNUSED (filters); JERRY_UNUSED (callback); JERRY_UNUSED (user_p); #endif /* JERRY_BUILTIN_PROMISE && JERRY_PROMISE_CALLBACK */ diff --git a/jerry-core/ecma/operations/ecma-jobqueue.c b/jerry-core/ecma/operations/ecma-jobqueue.c index 05a324208..2bc03cd01 100644 --- a/jerry-core/ecma/operations/ecma-jobqueue.c +++ b/jerry-core/ecma/operations/ecma-jobqueue.c @@ -188,8 +188,9 @@ ecma_process_promise_reaction_job (ecma_job_promise_reaction_t *job_p) /**< the capability_p = (ecma_promise_capabality_t *) ecma_get_object_from_value (job_p->capability); #if JERRY_PROMISE_CALLBACK - if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback) != NULL)) + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback_filters) & JERRY_PROMISE_EVENT_FILTER_REACTION_JOB)) { + JERRY_ASSERT (JERRY_CONTEXT (promise_callback) != NULL); JERRY_CONTEXT (promise_callback) (JERRY_PROMISE_EVENT_BEFORE_REACTION_JOB, capability_p->header.u.class_prop.u.promise, ECMA_VALUE_UNDEFINED, @@ -245,8 +246,9 @@ ecma_process_promise_reaction_job (ecma_job_promise_reaction_t *job_p) /**< the ecma_free_value (handler_result); #if JERRY_PROMISE_CALLBACK - if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback) != NULL)) + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback_filters) & JERRY_PROMISE_EVENT_FILTER_REACTION_JOB)) { + JERRY_ASSERT (JERRY_CONTEXT (promise_callback) != NULL); JERRY_CONTEXT (promise_callback) (JERRY_PROMISE_EVENT_AFTER_REACTION_JOB, capability_p->header.u.class_prop.u.promise, ECMA_VALUE_UNDEFINED, @@ -269,7 +271,7 @@ static ecma_value_t ecma_process_promise_async_reaction_job (ecma_job_promise_async_reaction_t *job_p) /**< the job to be operated */ { #if JERRY_PROMISE_CALLBACK - if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback) != NULL)) + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback_filters) & JERRY_PROMISE_EVENT_FILTER_ASYNC_REACTION_JOB)) { jerry_promise_event_type_t type = JERRY_PROMISE_EVENT_ASYNC_BEFORE_RESOLVE; @@ -278,6 +280,7 @@ ecma_process_promise_async_reaction_job (ecma_job_promise_async_reaction_t *job_ type = JERRY_PROMISE_EVENT_ASYNC_BEFORE_REJECT; } + JERRY_ASSERT (JERRY_CONTEXT (promise_callback) != NULL); JERRY_CONTEXT (promise_callback) (type, job_p->executable_object, job_p->argument, @@ -370,7 +373,7 @@ ecma_process_promise_async_reaction_job (ecma_job_promise_async_reaction_t *job_ free_job: #if JERRY_PROMISE_CALLBACK - if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback) != NULL)) + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback_filters) & JERRY_PROMISE_EVENT_FILTER_ASYNC_REACTION_JOB)) { jerry_promise_event_type_t type = JERRY_PROMISE_EVENT_ASYNC_AFTER_RESOLVE; @@ -379,6 +382,7 @@ free_job: type = JERRY_PROMISE_EVENT_ASYNC_AFTER_REJECT; } + JERRY_ASSERT (JERRY_CONTEXT (promise_callback) != NULL); JERRY_CONTEXT (promise_callback) (type, job_p->executable_object, job_p->argument, diff --git a/jerry-core/ecma/operations/ecma-promise-object.c b/jerry-core/ecma/operations/ecma-promise-object.c index 5f88e676a..5cff9bc46 100644 --- a/jerry-core/ecma/operations/ecma-promise-object.c +++ b/jerry-core/ecma/operations/ecma-promise-object.c @@ -115,14 +115,18 @@ ecma_promise_set_state (ecma_object_t *obj_p, /**< points to promise object */ * Take a collection of Reactions and enqueue a new PromiseReactionJob for each Reaction. * * See also: ES2015 25.4.1.8 + * + * @return true - if JERRY_PROMISE_CALLBACK define is 1, and at least one handler is found + * false - otherwise */ -static void +static bool ecma_promise_trigger_reactions (ecma_collection_t *reactions, /**< lists of reactions */ ecma_value_t value, /**< value for resolve or reject */ bool is_reject) /**< true if promise is rejected, false otherwise */ { ecma_value_t *buffer_p = reactions->buffer_p; ecma_value_t *buffer_end_p = buffer_p + reactions->item_count; + bool handler_found = false; while (buffer_p < buffer_end_p) { @@ -133,6 +137,9 @@ ecma_promise_trigger_reactions (ecma_collection_t *reactions, /**< lists of reac if (JMEM_CP_GET_THIRD_BIT_FROM_POINTER_TAG (object_with_tag)) { ecma_enqueue_promise_async_reaction_job (object, value, is_reject); +#if JERRY_PROMISE_CALLBACK + handler_found = true; +#endif /* JERRY_PROMISE_CALLBACK */ continue; } @@ -143,6 +150,9 @@ ecma_promise_trigger_reactions (ecma_collection_t *reactions, /**< lists of reac if (JMEM_CP_GET_FIRST_BIT_FROM_POINTER_TAG (object_with_tag)) { handler = *buffer_p++; +#if JERRY_PROMISE_CALLBACK + handler_found = true; +#endif /* JERRY_PROMISE_CALLBACK */ } ecma_enqueue_promise_reaction_job (object, handler, value); @@ -159,6 +169,9 @@ ecma_promise_trigger_reactions (ecma_collection_t *reactions, /**< lists of reac if (JMEM_CP_GET_SECOND_BIT_FROM_POINTER_TAG (object_with_tag)) { handler = *buffer_p++; +#if JERRY_PROMISE_CALLBACK + handler_found = true; +#endif /* JERRY_PROMISE_CALLBACK */ } ecma_enqueue_promise_reaction_job (object, handler, value); @@ -168,6 +181,8 @@ ecma_promise_trigger_reactions (ecma_collection_t *reactions, /**< lists of reac buffer_p++; } } + + return handler_found; } /* ecma_promise_trigger_reactions */ /** @@ -212,8 +227,9 @@ ecma_reject_promise (ecma_value_t promise, /**< promise */ JERRY_ASSERT (ecma_promise_get_flags (obj_p) & ECMA_PROMISE_IS_PENDING); #if JERRY_PROMISE_CALLBACK - if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback) != NULL)) + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback_filters) & JERRY_PROMISE_EVENT_FILTER_MAIN)) { + JERRY_ASSERT (JERRY_CONTEXT (promise_callback) != NULL); JERRY_CONTEXT (promise_callback) (JERRY_PROMISE_EVENT_REJECT, promise, reason, @@ -230,7 +246,23 @@ ecma_reject_promise (ecma_value_t promise, /**< promise */ ecma_collection_t *reactions = promise_p->reactions; /* Fulfill reactions will never be triggered. */ +#if JERRY_PROMISE_CALLBACK + if (!ecma_promise_trigger_reactions (reactions, reason, true)) + { + ((ecma_extended_object_t *) obj_p)->u.class_prop.extra_info |= ECMA_PROMISE_UNHANDLED_REJECT; + + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback_filters) & JERRY_PROMISE_EVENT_FILTER_ERROR)) + { + JERRY_ASSERT (JERRY_CONTEXT (promise_callback) != NULL); + JERRY_CONTEXT (promise_callback) (JERRY_PROMISE_EVENT_REJECT_WITHOUT_HANDLER, + promise, + reason, + JERRY_CONTEXT (promise_callback_user_p)); + } + } +#else /* !JERRY_PROMISE_CALLBACK */ ecma_promise_trigger_reactions (reactions, reason, true); +#endif /* JERRY_PROMISE_CALLBACK */ promise_p->reactions = ecma_new_collection (); @@ -283,8 +315,9 @@ ecma_fulfill_promise (ecma_value_t promise, /**< promise */ } #if JERRY_PROMISE_CALLBACK - if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback) != NULL)) + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback_filters) & JERRY_PROMISE_EVENT_FILTER_MAIN)) { + JERRY_ASSERT (JERRY_CONTEXT (promise_callback) != NULL); JERRY_CONTEXT (promise_callback) (JERRY_PROMISE_EVENT_RESOLVE, promise, value, @@ -324,15 +357,27 @@ ecma_reject_promise_with_checks (ecma_value_t promise, /**< promise */ JERRY_ASSERT (ecma_is_promise (promise_obj_p)); /* 3., 4. */ - if (!ecma_is_resolver_already_called (promise_obj_p)) + if (JERRY_UNLIKELY (ecma_is_resolver_already_called (promise_obj_p))) { - /* 5. */ - ((ecma_extended_object_t *) promise_obj_p)->u.class_prop.extra_info |= ECMA_PROMISE_ALREADY_RESOLVED; +#if JERRY_PROMISE_CALLBACK + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback_filters) & JERRY_PROMISE_EVENT_FILTER_ERROR)) + { + JERRY_ASSERT (JERRY_CONTEXT (promise_callback) != NULL); + JERRY_CONTEXT (promise_callback) (JERRY_PROMISE_EVENT_REJECT_FULFILLED, + promise, + reason, + JERRY_CONTEXT (promise_callback_user_p)); + } +#endif /* JERRY_PROMISE_CALLBACK */ - /* 6. */ - ecma_reject_promise (promise, reason); + return ECMA_VALUE_UNDEFINED; } + /* 5. */ + ((ecma_extended_object_t *) promise_obj_p)->u.class_prop.extra_info |= ECMA_PROMISE_ALREADY_RESOLVED; + + /* 6. */ + ecma_reject_promise (promise, reason); return ECMA_VALUE_UNDEFINED; } /* ecma_reject_promise_with_checks */ @@ -352,14 +397,26 @@ ecma_fulfill_promise_with_checks (ecma_value_t promise, /**< promise */ JERRY_ASSERT (ecma_is_promise (promise_obj_p)); /* 3., 4. */ - if (!ecma_is_resolver_already_called (promise_obj_p)) + if (JERRY_UNLIKELY (ecma_is_resolver_already_called (promise_obj_p))) { - /* 5. */ - ((ecma_extended_object_t *) promise_obj_p)->u.class_prop.extra_info |= ECMA_PROMISE_ALREADY_RESOLVED; +#if JERRY_PROMISE_CALLBACK + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback_filters) & JERRY_PROMISE_EVENT_FILTER_ERROR)) + { + JERRY_ASSERT (JERRY_CONTEXT (promise_callback) != NULL); + JERRY_CONTEXT (promise_callback) (JERRY_PROMISE_EVENT_RESOLVE_FULFILLED, + promise, + value, + JERRY_CONTEXT (promise_callback_user_p)); + } +#endif /* JERRY_PROMISE_CALLBACK */ - ecma_fulfill_promise (promise, value); + return ECMA_VALUE_UNDEFINED; } + /* 5. */ + ((ecma_extended_object_t *) promise_obj_p)->u.class_prop.extra_info |= ECMA_PROMISE_ALREADY_RESOLVED; + + ecma_fulfill_promise (promise, value); return ECMA_VALUE_UNDEFINED; } /* ecma_fulfill_promise_with_checks */ @@ -490,8 +547,9 @@ ecma_op_create_promise_object (ecma_value_t executor, /**< the executor function promise_object_p->reactions = reactions; #if JERRY_PROMISE_CALLBACK - if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback) != NULL)) + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback_filters) & JERRY_PROMISE_EVENT_FILTER_MAIN)) { + JERRY_ASSERT (JERRY_CONTEXT (promise_callback) != NULL); JERRY_CONTEXT (promise_callback) (JERRY_PROMISE_EVENT_CREATE, ecma_make_object_value (object_p), parent, @@ -886,6 +944,23 @@ ecma_promise_do_then (ecma_value_t promise, /**< the promise which call 'then' * ecma_track_promise_rejection (promise_obj_p, JERRY_PROMISE_REJECTION_OPERATION_HANDLE); ecma_enqueue_promise_reaction_job (ecma_make_object_value (result_capability_obj_p), on_rejected, reason); ecma_free_value (reason); + +#if JERRY_PROMISE_CALLBACK + if (on_rejected != ECMA_VALUE_FALSE + && ecma_promise_get_flags (promise_obj_p) & ECMA_PROMISE_UNHANDLED_REJECT) + { + promise_p->header.u.class_prop.extra_info &= (uint16_t) ~ECMA_PROMISE_UNHANDLED_REJECT; + + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback_filters) & JERRY_PROMISE_EVENT_FILTER_ERROR)) + { + JERRY_ASSERT (JERRY_CONTEXT (promise_callback) != NULL); + JERRY_CONTEXT (promise_callback) (JERRY_PROMISE_EVENT_CATCH_HANDLER_ADDED, + promise, + ECMA_VALUE_UNDEFINED, + JERRY_CONTEXT (promise_callback_user_p)); + } + } +#endif /* JERRY_PROMISE_CALLBACK */ } /* ES11: 11. */ @@ -1162,8 +1237,9 @@ ecma_promise_async_then (ecma_value_t promise, /**< promise object */ ecma_value_t executable_object) /**< executable object of the async function */ { #if JERRY_PROMISE_CALLBACK - if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback) != NULL)) + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback_filters) & JERRY_PROMISE_EVENT_FILTER_ASYNC_MAIN)) { + JERRY_ASSERT (JERRY_CONTEXT (promise_callback) != NULL); JERRY_CONTEXT (promise_callback) (JERRY_PROMISE_EVENT_ASYNC_AWAIT, executable_object, promise, @@ -1187,6 +1263,22 @@ ecma_promise_async_then (ecma_value_t promise, /**< promise object */ ecma_value_t value = ecma_promise_get_result (promise_obj_p); ecma_enqueue_promise_async_reaction_job (executable_object, value, !(flags & ECMA_PROMISE_IS_FULFILLED)); ecma_free_value (value); + +#if JERRY_PROMISE_CALLBACK + if (ecma_promise_get_flags (promise_obj_p) & ECMA_PROMISE_UNHANDLED_REJECT) + { + ((ecma_extended_object_t *) promise_obj_p)->u.class_prop.extra_info &= (uint16_t) ~ECMA_PROMISE_UNHANDLED_REJECT; + + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback_filters) & JERRY_PROMISE_EVENT_FILTER_ERROR)) + { + JERRY_ASSERT (JERRY_CONTEXT (promise_callback) != NULL); + JERRY_CONTEXT (promise_callback) (JERRY_PROMISE_EVENT_CATCH_HANDLER_ADDED, + promise, + ECMA_VALUE_UNDEFINED, + JERRY_CONTEXT (promise_callback_user_p)); + } + } +#endif /* JERRY_PROMISE_CALLBACK */ } /* ecma_promise_async_then */ /** diff --git a/jerry-core/ecma/operations/ecma-promise-object.h b/jerry-core/ecma/operations/ecma-promise-object.h index 814aa9d27..c8e8c0657 100644 --- a/jerry-core/ecma/operations/ecma-promise-object.h +++ b/jerry-core/ecma/operations/ecma-promise-object.h @@ -35,6 +35,9 @@ typedef enum ECMA_PROMISE_IS_FULFILLED = (1 << 1), /**< fulfilled state */ ECMA_PROMISE_ALREADY_RESOLVED = (1 << 2), /**< already resolved */ ECMA_PROMISE_HANDLED = (1 << 3), /**< ES11: 25.6.6 [[PromiseIsHandled]] internal slot */ +#if JERRY_PROMISE_CALLBACK + ECMA_PROMISE_UNHANDLED_REJECT = (1 << 4), /**< a Promise is rejected without a catch handler */ +#endif /* JERRY_PROMISE_CALLBACK */ } ecma_promise_flags_t; /** diff --git a/jerry-core/include/jerryscript-core.h b/jerry-core/include/jerryscript-core.h index 53797dfae..0e0f97d70 100644 --- a/jerry-core/include/jerryscript-core.h +++ b/jerry-core/include/jerryscript-core.h @@ -766,6 +766,19 @@ typedef enum JERRY_PROMISE_EVENT_REJECT, /**< called when a Promise is about to be rejected * object: the Promise object * value: value for rejecting */ + JERRY_PROMISE_EVENT_RESOLVE_FULFILLED, /**< called when a resolve is called on a fulfilled Promise + * object: the Promise object + * value: value for resolving */ + JERRY_PROMISE_EVENT_REJECT_FULFILLED, /**< called when a reject is called on a fulfilled Promise + * object: the Promise object + * value: value for rejecting */ + JERRY_PROMISE_EVENT_REJECT_WITHOUT_HANDLER, /**< called when a Promise is rejected without a handler + * object: the Promise object + * value: value for rejecting */ + JERRY_PROMISE_EVENT_CATCH_HANDLER_ADDED, /**< called when a catch handler is added to a rejected + * Promise which did not have a catch handler before + * object: the Promise object + * value: undefined */ JERRY_PROMISE_EVENT_BEFORE_REACTION_JOB, /**< called before executing a Promise reaction job * object: the Promise object * value: undefined */ @@ -789,6 +802,34 @@ typedef enum * value: value for rejecting */ } jerry_promise_event_type_t; +/** + * Filter types for jerry_promise_set_callback callback function. + * The callback is only called for those events which are enabled by the filters. + */ +typedef enum +{ + JERRY_PROMISE_EVENT_FILTER_DISABLE = 0, /**< disable reporting of all events */ + JERRY_PROMISE_EVENT_FILTER_MAIN = (1 << 0), /**< enables the following events: + * JERRY_PROMISE_EVENT_CREATE + * JERRY_PROMISE_EVENT_RESOLVE + * JERRY_PROMISE_EVENT_REJECT */ + JERRY_PROMISE_EVENT_FILTER_ERROR = (1 << 1), /**< enables the following events: + * JERRY_PROMISE_EVENT_RESOLVE_FULFILLED + * JERRY_PROMISE_EVENT_REJECT_FULFILLED + * JERRY_PROMISE_EVENT_REJECT_WITHOUT_HANDLER + * JERRY_PROMISE_EVENT_CATCH_HANDLER_ADDED */ + JERRY_PROMISE_EVENT_FILTER_REACTION_JOB = (1 << 2), /**< enables the following events: + * JERRY_PROMISE_EVENT_BEFORE_REACTION_JOB + * JERRY_PROMISE_EVENT_AFTER_REACTION_JOB */ + JERRY_PROMISE_EVENT_FILTER_ASYNC_MAIN = (1 << 3), /**< enables the following events: + * JERRY_PROMISE_EVENT_ASYNC_AWAIT */ + JERRY_PROMISE_EVENT_FILTER_ASYNC_REACTION_JOB = (1 << 4), /**< enables the following events: + * JERRY_PROMISE_EVENT_ASYNC_BEFORE_RESOLVE + * JERRY_PROMISE_EVENT_ASYNC_BEFORE_REJECT + * JERRY_PROMISE_EVENT_ASYNC_AFTER_RESOLVE + * JERRY_PROMISE_EVENT_ASYNC_AFTER_REJECT */ +} jerry_promise_event_filter_t; + /** * Notification callback for tracking Promise and async function operations. */ @@ -796,7 +837,8 @@ typedef void (*jerry_promise_callback_t) (jerry_promise_event_type_t event_type, const jerry_value_t object, const jerry_value_t value, void *user_p); -void jerry_promise_set_callback (jerry_promise_callback_t callback, void *user_p); +void jerry_promise_set_callback (jerry_promise_event_filter_t filters, jerry_promise_callback_t callback, + void *user_p); /** * Symbol functions. diff --git a/jerry-core/jcontext/jcontext.h b/jerry-core/jcontext/jcontext.h index 9d2b03d37..de8917d3d 100644 --- a/jerry-core/jcontext/jcontext.h +++ b/jerry-core/jcontext/jcontext.h @@ -184,6 +184,7 @@ struct jerry_context_t ecma_job_queue_item_t *job_queue_head_p; /**< points to the head item of the job queue */ ecma_job_queue_item_t *job_queue_tail_p; /**< points to the tail item of the job queue */ #if JERRY_PROMISE_CALLBACK + uint32_t promise_callback_filters; /**< reported event types for promise callback */ void *promise_callback_user_p; /**< user pointer for promise callback */ jerry_promise_callback_t promise_callback; /**< user function for tracking Promise object operations */ #endif /* JERRY_PROMISE_CALLBACK */ diff --git a/tests/unit-core/test-promise-callback.c b/tests/unit-core/test-promise-callback.c index 64e5bdef6..bee56be58 100644 --- a/tests/unit-core/test-promise-callback.c +++ b/tests/unit-core/test-promise-callback.c @@ -23,6 +23,10 @@ typedef enum C = JERRY_PROMISE_EVENT_CREATE, /**< same as JERRY_PROMISE_CALLBACK_CREATE with undefined value */ RS = JERRY_PROMISE_EVENT_RESOLVE, /**< same as JERRY_PROMISE_CALLBACK_RESOLVE */ RJ = JERRY_PROMISE_EVENT_REJECT, /**< same as JERRY_PROMISE_CALLBACK_REJECT */ + RSF = JERRY_PROMISE_EVENT_RESOLVE_FULFILLED, /**< same as JERRY_PROMISE_EVENT_RESOLVE_FULFILLED */ + RJF = JERRY_PROMISE_EVENT_REJECT_FULFILLED, /**< same as JERRY_PROMISE_EVENT_REJECT_FULFILLED */ + RWH = JERRY_PROMISE_EVENT_REJECT_WITHOUT_HANDLER, /**< same as JERRY_PROMISE_EVENT_REJECT_WITHOUT_HANDLER */ + CHA = JERRY_PROMISE_EVENT_CATCH_HANDLER_ADDED, /**< same as JERRY_PROMISE_EVENT_CATCH_HANDLER_ADDED */ BR = JERRY_PROMISE_EVENT_BEFORE_REACTION_JOB, /**< same as JERRY_PROMISE_CALLBACK_BEFORE_REACTION_JOB */ AR = JERRY_PROMISE_EVENT_AFTER_REACTION_JOB, /**< same as JERRY_PROMISE_CALLBACK_AFTER_REACTION_JOB */ A = JERRY_PROMISE_EVENT_ASYNC_AWAIT, /**< same as JERRY_PROMISE_CALLBACK_ASYNC_AWAIT */ @@ -61,10 +65,14 @@ promise_callback (jerry_promise_event_type_t event_type, /**< event type */ } case JERRY_PROMISE_EVENT_RESOLVE: case JERRY_PROMISE_EVENT_REJECT: + case JERRY_PROMISE_EVENT_RESOLVE_FULFILLED: + case JERRY_PROMISE_EVENT_REJECT_FULFILLED: + case JERRY_PROMISE_EVENT_REJECT_WITHOUT_HANDLER: { TEST_ASSERT (jerry_value_is_promise (object)); break; } + case JERRY_PROMISE_EVENT_CATCH_HANDLER_ADDED: case JERRY_PROMISE_EVENT_BEFORE_REACTION_JOB: case JERRY_PROMISE_EVENT_AFTER_REACTION_JOB: { @@ -126,7 +134,13 @@ main (void) jerry_init (JERRY_INIT_EMPTY); - jerry_promise_set_callback (promise_callback, (void *) &user); + jerry_promise_event_filter_t filters = (JERRY_PROMISE_EVENT_FILTER_MAIN + | JERRY_PROMISE_EVENT_FILTER_ERROR + | JERRY_PROMISE_EVENT_FILTER_REACTION_JOB + | JERRY_PROMISE_EVENT_FILTER_ASYNC_MAIN + | JERRY_PROMISE_EVENT_FILTER_ASYNC_REACTION_JOB); + + jerry_promise_set_callback (filters, promise_callback, (void *) &user); /* Test promise creation. */ static uint8_t events1[] = { C, C, C, E }; @@ -155,7 +169,7 @@ main (void) "promise.then(() => {})\n"); /* Test resolve and reject calls. */ - static uint8_t events4[] = { C, C, RS, RJ, E }; + static uint8_t events4[] = { C, C, RS, RJ, RWH, E }; run_eval (events4, "'use strict'\n" @@ -191,10 +205,10 @@ main (void) "Promise.resolve(4).then(() => {})\n"); /* Test Promise.reject. */ - static uint8_t events8[] = { C, RJ, CP, BR, RJ, AR, E }; + static uint8_t events8[] = { C, RJ, RWH, CP, CHA, BR, RJ, RWH, AR, E }; run_eval (events8, - "Promise.reject(4).then(() => {})\n"); + "Promise.reject(4).catch(() => { throw 'Error' })\n"); /* Test Promise.race without resolve */ static uint8_t events9[] = { C, C, C, CP, CP, E }; @@ -206,7 +220,7 @@ main (void) "Promise.race([p1,p2])\n"); /* Test Promise.race with resolve. */ - static uint8_t events10[] = { C, RS, C, RJ, C, CP, CP, BR, RS, RS, AR, BR, RS, AR, E }; + static uint8_t events10[] = { C, RS, C, RJ, RWH, C, CP, CP, CHA, BR, RS, RS, AR, BR, RJF, RS, AR, E }; run_eval (events10, "'use strict'\n" @@ -224,7 +238,7 @@ main (void) "Promise.all([p1,p2])\n"); /* Test Promise.all with resolve. */ - static uint8_t events12[] = { C, RS, C, RJ, C, CP, CP, BR, RS, AR, BR, RJ, RS, AR, E }; + static uint8_t events12[] = { C, RS, C, RJ, RWH, C, CP, CP, CHA, BR, RS, AR, BR, RJ, RWH, RS, AR, E }; run_eval (events12, "'use strict'\n" @@ -257,7 +271,7 @@ main (void) "f(Promise.resolve(1))\n"); /* Test await with rejected Promise. */ - static uint8_t events16[] = { C, RJ, A, C, BRJ, C, RS, RS, ARJ, E }; + static uint8_t events16[] = { C, RJ, RWH, A, CHA, C, BRJ, C, RS, RS, ARJ, E }; run_eval (events16, "'use strict'\n" @@ -281,6 +295,67 @@ main (void) "async function *g() { yield* f() }\n" "g().next()\n"); + /* Test multiple fulfill operations. */ + static uint8_t events19[] = { C, RS, RSF, RJF, E }; + + run_eval (events19, + "'use strict'\n" + "var resolve, reject\n" + "var p1 = new Promise((res, rej) => { resolve = res, reject = rej })\n" + "resolve(1)\n" + "resolve(2)\n" + "reject(3)\n"); + + /* Test multiple fulfill operations. */ + static uint8_t events20[] = { C, RJ, RWH, RSF, RJF, E }; + + run_eval (events20, + "'use strict'\n" + "var resolve, reject\n" + "var p1 = new Promise((res, rej) => { resolve = res, reject = rej })\n" + "reject(1)\n" + "resolve(2)\n" + "reject(3)\n"); + + /* Test catch handler added later is reported only once. */ + static uint8_t events21[] = { C, RJ, RWH, CP, CHA, CP, CP, BR, RS, AR, BR, RS, AR, BR, RS, AR, E }; + + run_eval (events21, + "'use strict'\n" + "var rej = Promise.reject(4)\n" + "rej.catch(() => {})\n" + "rej.catch(() => {})\n" + "rej.catch(() => {})\n"); + + /* Test catch handler added later is reported only once. */ + static uint8_t events22[] = { C, RJ, RWH, A, CHA, C, BRJ, A, ARJ, BRJ, RJ, RWH, ARJ, E }; + + run_eval (events22, + "'use strict'\n" + "async function f(p) { try { await p; } catch(e) { await p; } }" + "f(Promise.reject(4))\n"); + + /* Test disabled filters. */ + jerry_promise_set_callback (JERRY_PROMISE_EVENT_FILTER_DISABLE, promise_callback, (void *) &user); + + static uint8_t events23[] = { E }; + + run_eval (events23, + "'use strict'\n" + "async function f(p) { await p }" + "f(Promise.resolve(1))\n"); + + /* Test filtered events. */ + filters = JERRY_PROMISE_EVENT_FILTER_REACTION_JOB | JERRY_PROMISE_EVENT_FILTER_ASYNC_REACTION_JOB; + jerry_promise_set_callback (filters, promise_callback, (void *) &user); + + static uint8_t events24[] = { BR, AR, BRS, ARS, E }; + + run_eval (events24, + "'use strict'\n" + "async function f(p) { await p }" + "f(Promise.resolve(1).then(() => {}))\n"); + jerry_cleanup (); return 0; } /* main */