Skip to content

Commit f4cbe7b

Browse files
tmaillartcarlosgcampos
authored andcommitted
[JSC][GLib] Provide deferred promise creation api
https://bugs.webkit.org/show_bug.cgi?id=283708 Reviewed by Carlos Garcia Campos. This commit implements an API to create promises in JSC GLib. It is a copy from the Objective-C valueWithNewPromiseInContext. An executor is required, it will be invoked during promise initialization and it receives the @resolve and @Reject objects than can be called to resolve or reject the promise. It is called like a js function, so exceptions raised during the executor invocation will not be propagated to the context, but handled by the promise causing a rejection. * Source/JavaScriptCore/API/glib/JSCValue.cpp: (jsc_value_new_promise): * Source/JavaScriptCore/API/glib/JSCValue.h.in: * Tools/TestWebKitAPI/Tests/JavaScriptCore/glib/TestJSC.cpp: (fooSuccessSource): (fooSuccessExecutor): (getAsyncResolvedPromise): (fooFailureSource): (fooFailureExecutor): (getAsyncRejectedPromise): (getRejectedPromise): (getExceptionPromise): (getResolvedPromise): (testJSCPromises): Canonical link: https://commits.webkit.org/288688@main
1 parent 8b17190 commit f4cbe7b

3 files changed

Lines changed: 199 additions & 0 deletions

File tree

‎Source/JavaScriptCore/API/glib/JSCValue.cpp‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2145,3 +2145,61 @@ char* jsc_value_to_json(JSCValue* value, unsigned indent)
21452145

21462146
return json;
21472147
}
2148+
2149+
/**
2150+
* JSCExecutor:
2151+
* @resolve: #JSCValue function to call to resolve the promise
2152+
* @reject: #JSCValue function to call to reject the promise
2153+
* @user_data: user data
2154+
*
2155+
* A function passed to @jsc_value_new_promise called during initialization
2156+
*
2157+
* It is called like a JavaScript function, so exceptions raised will not be propagated
2158+
* to the context, but handled by the promise causing a rejection.
2159+
* @resolve and @reject can be reffed for later use to handle async task completion.
2160+
*
2161+
* Since: 2.48
2162+
*/
2163+
2164+
/**
2165+
* jsc_value_new_promise:
2166+
* @context: a #JSCContext
2167+
* @executor: (scope call) (closure user_data): an initialization callback
2168+
* @user_data: (nullable): user data passed in @executor
2169+
*
2170+
* Creates a new Promise. @executor will be invoked during promise initialization
2171+
* and it receives the @resolve and @reject objects than can be called to resolve
2172+
* or reject the promise. It is called like a JavaScript function, so exceptions raised
2173+
* during the executor invocation will not be propagated to the context, but
2174+
* handled by the promise causing a rejection.
2175+
*
2176+
* Returns: (transfer full): a deferred promise object
2177+
*
2178+
* Since: 2.48
2179+
*/
2180+
JSCValue* jsc_value_new_promise(JSCContext* context, JSCExecutor executor, gpointer userData)
2181+
{
2182+
g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr);
2183+
auto* jsContext = jscContextGetJSContext(context);
2184+
JSObjectRef resolveObj = nullptr;
2185+
JSObjectRef rejectObj = nullptr;
2186+
JSValueRef exception = nullptr;
2187+
JSObjectRef promise = JSObjectMakeDeferredPromise(jsContext, &resolveObj, &rejectObj, &exception);
2188+
if (jscContextHandleExceptionIfNeeded(context, exception))
2189+
return nullptr;
2190+
2191+
const size_t argumentCount = 2;
2192+
JSValueRef arguments[argumentCount];
2193+
arguments[0] = resolveObj;
2194+
arguments[1] = rejectObj;
2195+
2196+
auto callbackData = jscContextPushCallback(context, nullptr, promise, argumentCount, arguments);
2197+
GRefPtr<JSCValue> resolve = jscContextGetOrCreateValue(context, resolveObj);
2198+
GRefPtr<JSCValue> reject = jscContextGetOrCreateValue(context, rejectObj);
2199+
executor(resolve.get(), reject.get(), userData);
2200+
if (JSCException* unhandledException = jsc_context_get_exception(context))
2201+
g_object_unref(jsc_value_function_call(reject.get(), JSC_TYPE_EXCEPTION, unhandledException, G_TYPE_NONE));
2202+
jscContextPopCallback(context, WTFMove(callbackData));
2203+
2204+
return jscContextGetOrCreateValue(context, promise).leakRef();
2205+
}

‎Source/JavaScriptCore/API/glib/JSCValue.h.in‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,15 @@ JSC_API char *
322322
jsc_value_to_json (JSCValue *value,
323323
guint indent);
324324

325+
typedef void (*JSCExecutor) (JSCValue *resolve,
326+
JSCValue *reject,
327+
gpointer user_data);
328+
329+
JSC_API JSCValue *
330+
jsc_value_new_promise (JSCContext *context,
331+
JSCExecutor executor,
332+
gpointer user_data);
333+
325334
G_END_DECLS
326335

327336
#endif /* JSCValue_h */

‎Tools/TestWebKitAPI/Tests/JavaScriptCore/glib/TestJSC.cpp‎

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3905,6 +3905,78 @@ static void testJSCExceptions()
39053905
}
39063906
}
39073907

3908+
struct AsyncPromiseTestData {
3909+
GRefPtr<GMainLoop> mainLoop;
3910+
GRefPtr<JSCValue> value;
3911+
};
3912+
3913+
static gboolean fooSuccessSource(gpointer userData)
3914+
{
3915+
AsyncPromiseTestData* foo = static_cast<AsyncPromiseTestData*>(userData);
3916+
g_object_unref(jsc_value_function_call(foo->value.get(), G_TYPE_NONE));
3917+
foo->value = nullptr;
3918+
g_main_loop_quit(foo->mainLoop.get());
3919+
return FALSE;
3920+
}
3921+
3922+
static void fooSuccessExecutor(JSCValue* resolve, JSCValue*, gpointer userData)
3923+
{
3924+
AsyncPromiseTestData* foo = static_cast<AsyncPromiseTestData*>(userData);
3925+
foo->value = resolve;
3926+
}
3927+
3928+
static JSCValue* getAsyncResolvedPromise(AsyncPromiseTestData* foo)
3929+
{
3930+
JSCValue* promise = jsc_value_new_promise(jsc_context_get_current(), fooSuccessExecutor, foo);
3931+
g_assert_nonnull(promise);
3932+
g_idle_add(fooSuccessSource, foo);
3933+
return promise;
3934+
}
3935+
3936+
static gboolean fooFailureSource(gpointer userData)
3937+
{
3938+
AsyncPromiseTestData* foo = static_cast<AsyncPromiseTestData*>(userData);
3939+
g_object_unref(jsc_value_function_call(foo->value.get(), G_TYPE_NONE));
3940+
foo->value = nullptr;
3941+
g_main_loop_quit(foo->mainLoop.get());
3942+
return FALSE;
3943+
}
3944+
3945+
static void fooFailureExecutor(JSCValue*, JSCValue* reject, gpointer userData)
3946+
{
3947+
AsyncPromiseTestData* foo = static_cast<AsyncPromiseTestData*>(userData);
3948+
foo->value = reject;
3949+
}
3950+
3951+
static JSCValue* getAsyncRejectedPromise(AsyncPromiseTestData* foo)
3952+
{
3953+
JSCValue* promise = jsc_value_new_promise(jsc_context_get_current(), fooFailureExecutor, foo);
3954+
g_assert_nonnull(promise);
3955+
g_idle_add(fooFailureSource, foo);
3956+
return promise;
3957+
}
3958+
3959+
static JSCValue* getRejectedPromise(AsyncPromiseTestData* foo)
3960+
{
3961+
return jsc_value_new_promise(jsc_context_get_current(), +[](JSCValue*, JSCValue* reject, gpointer) {
3962+
g_object_unref(jsc_value_function_call(reject, G_TYPE_BOOLEAN, TRUE, G_TYPE_NONE));
3963+
}, nullptr);
3964+
}
3965+
3966+
static JSCValue* getExceptionPromise(AsyncPromiseTestData* foo)
3967+
{
3968+
return jsc_value_new_promise(jsc_context_get_current(), +[](JSCValue*, JSCValue*, gpointer) {
3969+
jsc_context_throw(jsc_context_get_current(), "exception in executor");
3970+
}, nullptr);
3971+
}
3972+
3973+
static JSCValue* getResolvedPromise(AsyncPromiseTestData* foo)
3974+
{
3975+
return jsc_value_new_promise(jsc_context_get_current(), +[](JSCValue* resolve, JSCValue*, gpointer) {
3976+
g_object_unref(jsc_value_function_call(resolve, G_TYPE_BOOLEAN, TRUE, G_TYPE_NONE));
3977+
}, nullptr);
3978+
}
3979+
39083980
static void testJSCPromises()
39093981
{
39103982
{
@@ -3963,6 +4035,66 @@ static void testJSCPromises()
39634035
g_assert_true(jsc_value_is_number(value.get()));
39644036
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, -1);
39654037
}
4038+
4039+
{
4040+
AsyncPromiseTestData foo = { .mainLoop = adoptGRef(g_main_loop_new(nullptr, FALSE)), .value = nullptr };
4041+
4042+
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
4043+
4044+
GRefPtr<JSCValue> function = adoptGRef(jsc_value_new_function(context.get(), "getAsyncResolvedPromise", G_CALLBACK(getAsyncResolvedPromise), &foo, nullptr, JSC_TYPE_VALUE, 0, G_TYPE_NONE));
4045+
jsc_context_set_value(context.get(), "getAsyncResolvedPromise", function.get());
4046+
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "result = -1; getAsyncResolvedPromise().then(function (value) { result = 1; }, function (error) { result = 2; });", -1));
4047+
g_main_loop_run(foo.mainLoop.get());
4048+
result = adoptGRef(jsc_context_get_value(context.get(), "result"));
4049+
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 1);
4050+
4051+
function = adoptGRef(jsc_value_new_function(context.get(), "getAsyncRejectedPromise", G_CALLBACK(getAsyncRejectedPromise), &foo, nullptr, JSC_TYPE_VALUE, 0, G_TYPE_NONE));
4052+
jsc_context_set_value(context.get(), "getAsyncRejectedPromise", function.get());
4053+
result = adoptGRef(jsc_context_evaluate(context.get(), "result = -1; getAsyncRejectedPromise().then(function (value) { result = 1; }, function (error) { result = 2; });", -1));
4054+
g_main_loop_run(foo.mainLoop.get());
4055+
result = adoptGRef(jsc_context_get_value(context.get(), "result"));
4056+
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 2);
4057+
4058+
function = adoptGRef(jsc_value_new_function(context.get(), "getResolvedPromise", G_CALLBACK(getResolvedPromise), &foo, nullptr, JSC_TYPE_VALUE, 0, G_TYPE_NONE));
4059+
jsc_context_set_value(context.get(), "getResolvedPromise", function.get());
4060+
adoptGRef(jsc_context_evaluate(context.get(), "result = -1; getResolvedPromise().then(function (value) { result = 1; }, function (error) { result = 2; });", -1));
4061+
result = adoptGRef(jsc_context_get_value(context.get(), "result"));
4062+
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 1);
4063+
4064+
function = adoptGRef(jsc_value_new_function(context.get(), "getRejectedPromise", G_CALLBACK(getRejectedPromise), &foo, nullptr, JSC_TYPE_VALUE, 0, G_TYPE_NONE));
4065+
jsc_context_set_value(context.get(), "getRejectedPromise", function.get());
4066+
adoptGRef(jsc_context_evaluate(context.get(), "result = -1; getRejectedPromise().then(function (value) { result = 1; }, function (error) { result = 2; });", -1));
4067+
result = adoptGRef(jsc_context_get_value(context.get(), "result"));
4068+
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 2);
4069+
4070+
function = adoptGRef(jsc_value_new_function(context.get(), "getExceptionPromise", G_CALLBACK(getExceptionPromise), &foo, nullptr, JSC_TYPE_VALUE, 0, G_TYPE_NONE));
4071+
jsc_context_set_value(context.get(), "getExceptionPromise", function.get());
4072+
result = adoptGRef(jsc_context_evaluate(context.get(), "result = -1; getExceptionPromise().catch( (e) => { result = 3; }) instanceof Promise;", -1));
4073+
g_assert_true(jsc_value_to_boolean(result.get()));
4074+
result = adoptGRef(jsc_context_get_value(context.get(), "result"));
4075+
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 3);
4076+
4077+
result = adoptGRef(jsc_context_evaluate(context.get(), "result = -1; getExceptionPromise().then((value) => { result = 1; }, (e) => { result = 3; });", -1));
4078+
result = adoptGRef(jsc_context_get_value(context.get(), "result"));
4079+
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 3);
4080+
4081+
result = adoptGRef(jsc_context_evaluate(context.get(), "result = -1; getResolvedPromise().then((value) => { throw new Error(\"reject from js\"); }, (e) => { result = 3; }).then((value) => { result = 1; }, (e) => { result = 2; });", -1));
4082+
result = adoptGRef(jsc_context_get_value(context.get(), "result"));
4083+
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 2);
4084+
4085+
GRefPtr<JSCValue> promise = adoptGRef(jsc_context_evaluate(context.get(), "result = -1; p = getResolvedPromise();", -1));
4086+
GRefPtr<JSCValue> resolveFunc = adoptGRef(jsc_context_evaluate(context.get(), "res = function (a) { result = 1; }", -1));
4087+
GRefPtr<JSCValue> rejectFunc = adoptGRef(jsc_context_evaluate(context.get(), "rej = function (a) { result = 2; }", -1));
4088+
result = jsc_value_object_invoke_method(promise.get(), "then", JSC_TYPE_VALUE, resolveFunc.get(), JSC_TYPE_VALUE, rejectFunc.get(), G_TYPE_NONE);
4089+
result = adoptGRef(jsc_context_get_value(context.get(), "result"));
4090+
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 1);
4091+
4092+
promise = adoptGRef(jsc_context_evaluate(context.get(), "result = -1; p = getExceptionPromise();", -1));
4093+
GRefPtr<JSCValue> catch_func = adoptGRef(jsc_context_evaluate(context.get(), "cat = function (a) { result = 3; }", -1));
4094+
result = jsc_value_object_invoke_method(promise.get(), "catch", JSC_TYPE_VALUE, catch_func.get(), G_TYPE_NONE);
4095+
result = adoptGRef(jsc_context_get_value(context.get(), "result"));
4096+
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 3);
4097+
}
39664098
}
39674099

39684100
static bool s_fooWasFreed;

0 commit comments

Comments
 (0)