/* Copyright JS Foundation and other contributors, http://js.foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ecma-function-object.h" #include "ecma-globals.h" #include "ecma-helpers.h" #include "ecma-jobqueue.h" #include "ecma-objects.h" #include "ecma-promise-object.h" #include "jcontext.h" #if ENABLED (JERRY_ES2015_BUILTIN_PROMISE) /** \addtogroup ecma ECMA * @{ * * \addtogroup ecmajobqueue ECMA Job Queue related routines * @{ */ /** * Description of the PromiseReactionJob */ typedef struct { ecma_value_t reaction; /**< the PromiseReaction */ ecma_value_t argument; /**< argument for the reaction */ } ecma_job_promise_reaction_t; /** * Description of the PromiseResolveThenableJob */ typedef struct { ecma_value_t promise; /**< promise to be resolved */ ecma_value_t thenable; /**< thenbale object */ ecma_value_t then; /**< 'then' function */ } ecma_job_promise_resolve_thenable_t; /** * Initialize the jobqueue. */ void ecma_job_queue_init (void) { JERRY_CONTEXT (job_queue_head_p) = NULL; JERRY_CONTEXT (job_queue_tail_p) = NULL; } /* ecma_job_queue_init */ /** * Create a PromiseReactionJob. * * @return pointer to the PromiseReactionJob */ static ecma_job_promise_reaction_t * ecma_create_promise_reaction_job (ecma_value_t reaction, /**< PromiseReaction */ ecma_value_t argument) /**< argument for the reaction */ { JERRY_ASSERT (ecma_is_value_object (reaction)); ecma_job_promise_reaction_t *job_p; job_p = (ecma_job_promise_reaction_t *) jmem_heap_alloc_block (sizeof (ecma_job_promise_reaction_t)); job_p->reaction = ecma_copy_value (reaction); job_p->argument = ecma_copy_value (argument); return job_p; } /* ecma_create_promise_reaction_job */ /** * Free the heap and the member of the PromiseReactionJob. */ static void ecma_free_promise_reaction_job (ecma_job_promise_reaction_t *job_p) /**< points to the PromiseReactionJob */ { JERRY_ASSERT (job_p != NULL); ecma_free_value (job_p->reaction); ecma_free_value (job_p->argument); jmem_heap_free_block (job_p, sizeof (ecma_job_promise_reaction_t)); } /* ecma_free_promise_reaction_job */ /** * Create a PromiseResolveThenableJob * * @return pointer to the PromiseResolveThenableJob */ static ecma_job_promise_resolve_thenable_t * ecma_create_promise_resolve_thenable_job (ecma_value_t promise, /**< promise to be resolved */ ecma_value_t thenable, /**< thenable object */ ecma_value_t then) /**< 'then' function */ { JERRY_ASSERT (ecma_is_promise (ecma_get_object_from_value (promise))); JERRY_ASSERT (ecma_is_value_object (thenable)); JERRY_ASSERT (ecma_op_is_callable (then)); ecma_job_promise_resolve_thenable_t *job_p; job_p = (ecma_job_promise_resolve_thenable_t *) jmem_heap_alloc_block (sizeof (ecma_job_promise_resolve_thenable_t)); job_p->promise = ecma_copy_value (promise); job_p->thenable = ecma_copy_value (thenable); job_p->then = ecma_copy_value (then); return job_p; } /* ecma_create_promise_resolve_thenable_job */ /** * Free the heap and the member of the PromiseResolveThenableJob. */ static void ecma_free_promise_resolve_thenable_job (ecma_job_promise_resolve_thenable_t *job_p) /**< points to the * PromiseResolveThenableJob */ { JERRY_ASSERT (job_p != NULL); ecma_free_value (job_p->promise); ecma_free_value (job_p->thenable); ecma_free_value (job_p->then); jmem_heap_free_block (job_p, sizeof (ecma_job_promise_resolve_thenable_t)); } /* ecma_free_promise_resolve_thenable_job */ /** * The processor for PromiseReactionJob. * * See also: ES2015 25.4.2.1 * * @return ecma value * Returned value must be freed with ecma_free_value */ static ecma_value_t ecma_process_promise_reaction_job (void *obj_p) /**< the job to be operated */ { ecma_job_promise_reaction_t *job_p = (ecma_job_promise_reaction_t *) obj_p; ecma_object_t *reaction_p = ecma_get_object_from_value (job_p->reaction); ecma_string_t *capability_str_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_PROMISE_PROPERTY_CAPABILITY); ecma_string_t *handler_str_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_PROMISE_PROPERTY_HANDLER); ecma_string_t *resolve_str_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_PROMISE_PROPERTY_RESOLVE); ecma_string_t *reject_str_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_PROMISE_PROPERTY_REJECT); /* 2. */ ecma_value_t capability = ecma_op_object_get (reaction_p, capability_str_p); /* 3. */ ecma_value_t handler = ecma_op_object_get (reaction_p, handler_str_p); JERRY_ASSERT (ecma_is_value_boolean (handler) || ecma_op_is_callable (handler)); ecma_value_t handler_result; if (ecma_is_value_boolean (handler)) { /* 4-5. True indicates "identity" and false indicates "thrower" */ handler_result = ecma_copy_value (job_p->argument); } else { /* 6. */ handler_result = ecma_op_function_call (ecma_get_object_from_value (handler), ECMA_VALUE_UNDEFINED, &(job_p->argument), 1); } ecma_value_t status; if (ecma_is_value_false (handler) || ECMA_IS_VALUE_ERROR (handler_result)) { if (ECMA_IS_VALUE_ERROR (handler_result)) { handler_result = jcontext_take_exception (); } /* 7. */ ecma_value_t reject = ecma_op_object_get (ecma_get_object_from_value (capability), reject_str_p); JERRY_ASSERT (ecma_op_is_callable (reject)); status = ecma_op_function_call (ecma_get_object_from_value (reject), ECMA_VALUE_UNDEFINED, &handler_result, 1); ecma_free_value (reject); } else { /* 8. */ ecma_value_t resolve = ecma_op_object_get (ecma_get_object_from_value (capability), resolve_str_p); JERRY_ASSERT (ecma_op_is_callable (resolve)); status = ecma_op_function_call (ecma_get_object_from_value (resolve), ECMA_VALUE_UNDEFINED, &handler_result, 1); ecma_free_value (resolve); } ecma_free_value (handler_result); ecma_free_value (handler); ecma_free_value (capability); ecma_free_promise_reaction_job (job_p); return status; } /* ecma_process_promise_reaction_job */ /** * Process the PromiseResolveThenableJob. * * See also: ES2015 25.4.2.2 * * @return ecma value * Returned value must be freed with ecma_free_value */ static ecma_value_t ecma_process_promise_resolve_thenable_job (void *obj_p) /**< the job to be operated */ { ecma_job_promise_resolve_thenable_t *job_p = (ecma_job_promise_resolve_thenable_t *) obj_p; ecma_object_t *promise_p = ecma_get_object_from_value (job_p->promise); ecma_promise_resolving_functions_t *funcs = ecma_promise_create_resolving_functions (promise_p); ecma_string_t *str_resolve_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_RESOLVE_FUNCTION); ecma_string_t *str_reject_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_REJECT_FUNCTION); ecma_op_object_put (promise_p, str_resolve_p, funcs->resolve, false); ecma_op_object_put (promise_p, str_reject_p, funcs->reject, false); ecma_value_t argv[] = { funcs->resolve, funcs->reject }; ecma_value_t ret; ecma_value_t then_call_result = ecma_op_function_call (ecma_get_object_from_value (job_p->then), job_p->thenable, argv, 2); ret = then_call_result; if (ECMA_IS_VALUE_ERROR (then_call_result)) { then_call_result = jcontext_take_exception (); ret = ecma_op_function_call (ecma_get_object_from_value (funcs->reject), ECMA_VALUE_UNDEFINED, &then_call_result, 1); ecma_free_value (then_call_result); } ecma_promise_free_resolving_functions (funcs); ecma_free_promise_resolve_thenable_job (job_p); return ret; } /* ecma_process_promise_resolve_thenable_job */ /** * Enqueue a Promise job into the jobqueue. */ static void ecma_enqueue_job (ecma_job_handler_t handler, /**< the handler for the job */ void *job_p) /**< the job */ { ecma_job_queueitem_t *item_p = jmem_heap_alloc_block (sizeof (ecma_job_queueitem_t)); item_p->job_p = job_p; item_p->handler = handler; item_p->next_p = NULL; if (JERRY_CONTEXT (job_queue_head_p) == NULL) { JERRY_CONTEXT (job_queue_head_p) = item_p; JERRY_CONTEXT (job_queue_tail_p) = item_p; } else { JERRY_CONTEXT (job_queue_tail_p)->next_p = item_p; JERRY_CONTEXT (job_queue_tail_p) = item_p; } } /* ecma_enqueue_job */ /** * Enqueue a PromiseReactionJob into the jobqueue. */ void ecma_enqueue_promise_reaction_job (ecma_value_t reaction, /**< PromiseReaction */ ecma_value_t argument) /**< argument for the reaction */ { ecma_job_promise_reaction_t *job_p = ecma_create_promise_reaction_job (reaction, argument); ecma_enqueue_job (ecma_process_promise_reaction_job, job_p); } /* ecma_enqueue_promise_reaction_job */ /** * Enqueue a PromiseResolveThenableJob into the jobqueue. */ void ecma_enqueue_promise_resolve_thenable_job (ecma_value_t promise, /**< promise to be resolved */ ecma_value_t thenable, /**< thenable object */ ecma_value_t then) /**< 'then' function */ { ecma_job_promise_resolve_thenable_t *job_p = ecma_create_promise_resolve_thenable_job (promise, thenable, then); ecma_enqueue_job (ecma_process_promise_resolve_thenable_job, job_p); } /* ecma_enqueue_promise_resolve_thenable_job */ /** * Process enqueued Promise jobs until the first thrown error or until the * jobqueue becomes empty. * * @return result of the last processed job - if the jobqueue was non-empty, * undefined - otherwise. */ ecma_value_t ecma_process_all_enqueued_jobs (void) { ecma_value_t ret = ECMA_VALUE_UNDEFINED; while (JERRY_CONTEXT (job_queue_head_p) != NULL && !ECMA_IS_VALUE_ERROR (ret)) { ecma_job_queueitem_t *item_p = JERRY_CONTEXT (job_queue_head_p); JERRY_CONTEXT (job_queue_head_p) = JERRY_CONTEXT (job_queue_head_p)->next_p; void *job_p = item_p->job_p; ecma_job_handler_t handler = item_p->handler; jmem_heap_free_block (item_p, sizeof (ecma_job_queueitem_t)); ecma_free_value (ret); ret = handler (job_p); } return ret; } /* ecma_process_all_enqueued_jobs */ /** * Release enqueued Promise jobs. */ void ecma_free_all_enqueued_jobs (void) { while (JERRY_CONTEXT (job_queue_head_p) != NULL) { ecma_job_queueitem_t *item_p = JERRY_CONTEXT (job_queue_head_p); JERRY_CONTEXT (job_queue_head_p) = item_p->next_p; void *job_p = item_p->job_p; jmem_heap_free_block (item_p, sizeof (ecma_job_queueitem_t)); ecma_free_promise_reaction_job (job_p); } } /* ecma_free_all_enqueued_jobs */ /** * @} * @} */ #endif /* ENABLED (JERRY_ES2015_BUILTIN_PROMISE) */