⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 185739 in webkit


Ignore:
Timestamp:
Jun 19, 2015, 12:33:35 AM (11 years ago)
Author:
youenn.fablet@crf.canon.fr
Message:

Bindings generator should generate code to catch exception and reject promises for Promise-based APIs
https://bugs.webkit.org/show_bug.cgi?id=146060

Reviewed by Darin Adler.

The binding generator splits the function that binds JS to the DOM class implementation in two for functions returning promise.
The first function, called from JS, is responsible of casting this to the expected JSXXX class.
If casting fails, an exception is raised. Otherwise, it calls the second function.
After calling the second function, it checks whether an exception is raised, in which case it returns a rejected promise.
The second function is responsible of argument conversion and calling the DOM class function.

Covered by expectations and AudioContext promise still working.
A test case is added for a promise returning function taking a typed argument as input (if argument value cannot be typed, the promise is rejected).
A second test case is a promise-returning function that can raise an exception. In that case the DOMException is used as rejection value.

As can be seen from generated code, this generalized code adds a mandatory check (is there an exception?) at the end of the function.
This check is done even in cases we know there will be no exception.
This may be covered by another patch if this optimization is thought useful enough.

  • bindings/js/JSDOMPromise.cpp:

(WebCore::rejectPromiseWithExceptionIfAny): Utility method for the binding code.
(WebCore::callPromiseFunction): Ditto.

  • bindings/js/JSDOMPromise.h:
  • bindings/scripts/CodeGeneratorJS.pm:

(GenerateImplementation):
(GenerateFunctionCastedThis): Extracted from GenerateImplementationFunctionCall to reuse it in case of promise-returning functions.
(GenerateImplementationFunctionCall):
(GenerateCallbackImplementation): Deleted.

  • bindings/scripts/test/JS/JSTestObj.cpp:

(WebCore::jsTestObjPrototypeFunctionTestPromiseFunction):
(WebCore::jsTestObjPrototypeFunctionTestPromiseFunctionPromise):
(WebCore::jsTestObjPrototypeFunctionTestPromiseFunctionWithFloatArgument):
(WebCore::jsTestObjPrototypeFunctionTestPromiseFunctionWithFloatArgumentPromise):
(WebCore::jsTestObjPrototypeFunctionTestPromiseFunctionWithException):
(WebCore::jsTestObjPrototypeFunctionTestPromiseFunctionWithExceptionPromise):

  • bindings/scripts/test/TestObj.idl:
Location:
trunk/Source/WebCore
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r185733 r185739  
     12015-06-19  Youenn Fablet  <youenn.fablet@crf.canon.fr>
     2
     3        Bindings generator should generate code to catch exception and reject promises for Promise-based APIs
     4        https://bugs.webkit.org/show_bug.cgi?id=146060
     5
     6        Reviewed by Darin Adler.
     7
     8        The binding generator splits the function that binds JS to the DOM class implementation in two for functions returning promise.
     9        The first function, called from JS, is responsible of casting this to the expected JSXXX class.
     10        If casting fails, an exception is raised. Otherwise, it calls the second function.
     11        After calling the second function, it checks whether an exception is raised, in which case it returns a rejected promise.
     12        The second function is responsible of argument conversion and calling the DOM class function.
     13
     14        Covered by expectations and AudioContext promise still working.
     15        A test case is added for a promise returning function taking a typed argument as input (if argument value cannot be typed, the promise is rejected).
     16        A second test case is a promise-returning function that can raise an exception. In that case the DOMException is used as rejection value.
     17
     18        As can be seen from generated code, this generalized code adds a mandatory check (is there an exception?) at the end of the function.
     19        This check is done even in cases we know there will be no exception.
     20        This may be covered by another patch if this optimization is thought useful enough.
     21
     22        * bindings/js/JSDOMPromise.cpp:
     23        (WebCore::rejectPromiseWithExceptionIfAny): Utility method for the binding code.
     24        (WebCore::callPromiseFunction): Ditto.
     25        * bindings/js/JSDOMPromise.h:
     26        * bindings/scripts/CodeGeneratorJS.pm:
     27        (GenerateImplementation):
     28        (GenerateFunctionCastedThis): Extracted from GenerateImplementationFunctionCall to reuse it in case of promise-returning functions.
     29        (GenerateImplementationFunctionCall):
     30        (GenerateCallbackImplementation): Deleted.
     31        * bindings/scripts/test/JS/JSTestObj.cpp:
     32        (WebCore::jsTestObjPrototypeFunctionTestPromiseFunction):
     33        (WebCore::jsTestObjPrototypeFunctionTestPromiseFunctionPromise):
     34        (WebCore::jsTestObjPrototypeFunctionTestPromiseFunctionWithFloatArgument):
     35        (WebCore::jsTestObjPrototypeFunctionTestPromiseFunctionWithFloatArgumentPromise):
     36        (WebCore::jsTestObjPrototypeFunctionTestPromiseFunctionWithException):
     37        (WebCore::jsTestObjPrototypeFunctionTestPromiseFunctionWithExceptionPromise):
     38        * bindings/scripts/test/TestObj.idl:
     39
    1402015-06-18  Jeremy Jones  <jeremyj@apple.com>
    241
  • trunk/Source/WebCore/bindings/js/JSDOMPromise.cpp

    r185407 r185739  
    3030
    3131#include "ExceptionCode.h"
     32#include <runtime/Exception.h>
    3233
    3334using namespace JSC;
     
    6364}
    6465
     66void rejectPromiseWithExceptionIfAny(JSC::ExecState& state, JSDOMGlobalObject& globalObject, JSPromiseDeferred& promiseDeferred)
     67{
     68    if (!state.hadException())
     69        return;
     70
     71    JSValue error = state.exception()->value();
     72    state.clearException();
     73
     74    DeferredWrapper(&state, &globalObject, &promiseDeferred).reject(error);
     75}
     76
    6577}
    6678
  • trunk/Source/WebCore/bindings/js/JSDOMPromise.h

    r185407 r185739  
    5858};
    5959
     60void rejectPromiseWithExceptionIfAny(JSC::ExecState&, JSDOMGlobalObject&, JSC::JSPromiseDeferred&);
     61
     62template<class JSClassName>
     63inline JSC::JSValue callPromiseFunction(JSC::ExecState& state, JSClassName& jsObject, JSC::EncodedJSValue promiseFunction(JSC::ExecState*, JSClassName*, JSC::JSPromiseDeferred*))
     64{
     65    JSC::JSPromiseDeferred* promiseDeferred = JSC::JSPromiseDeferred::create(&state, jsObject.globalObject());
     66    promiseFunction(&state, &jsObject, promiseDeferred);
     67
     68    rejectPromiseWithExceptionIfAny(state, *jsObject.globalObject(), *promiseDeferred);
     69    ASSERT(!state.hadException());
     70    return promiseDeferred->promise();
     71}
     72
    6073template <typename Value, typename Error>
    6174class DOMPromise {
  • trunk/Source/WebCore/bindings/scripts/CodeGeneratorJS.pm

    r185493 r185739  
    27812781            my $functionImplementationName = $function->signature->extendedAttributes->{"ImplementedAs"} || $codeGenerator->WK_lcfirst($function->signature->name);
    27822782
    2783             push(@implContent, "EncodedJSValue JSC_HOST_CALL ${functionName}(ExecState* exec)\n");
     2783            if (IsReturningPromise($function) && !$isCustom) {
     2784                AddToImplIncludes("JSDOMPromise.h");
     2785
     2786                push(@implContent, "static inline EncodedJSValue ${functionName}Promise(ExecState*, " . $className . "*, JSPromiseDeferred*);\n");
     2787                push(@implContent, "EncodedJSValue JSC_HOST_CALL ${functionName}(ExecState* exec)\n");
     2788                push(@implContent, "{\n");
     2789
     2790                GenerateFunctionCastedThis($interface, $interfaceName, $className, $function);
     2791                push(@implContent, "    return JSValue::encode(callPromiseFunction(*exec, *castedThis, ${functionName}Promise));\n");
     2792
     2793                push(@implContent, "}\n");
     2794                push(@implContent, "\nstatic inline EncodedJSValue ${functionName}Promise(ExecState* exec, " . $className . "* castedThis, JSPromiseDeferred* promiseDeferred)\n");
     2795            }
     2796            else {
     2797                push(@implContent, "EncodedJSValue JSC_HOST_CALL ${functionName}(ExecState* exec)\n");
     2798            }
     2799
    27842800            push(@implContent, "{\n");
    27852801
     
    28002816                }
    28012817            } else {
    2802                 if ($interface->extendedAttributes->{"CustomProxyToJSObject"}) {
    2803                     push(@implContent, "    $className* castedThis = to${className}(exec->thisValue().toThis(exec, NotStrictMode));\n");
    2804                     push(@implContent, "    if (UNLIKELY(!castedThis))\n");
    2805                     push(@implContent, "        return throwVMTypeError(exec);\n");
    2806                 } elsif ($interface->extendedAttributes->{"WorkerGlobalScope"}) {
    2807                     push(@implContent, "    $className* castedThis = to${className}(exec->thisValue().toThis(exec, NotStrictMode));\n");
    2808                     push(@implContent, "    if (UNLIKELY(!castedThis))\n");
    2809                     push(@implContent, "        return throwVMTypeError(exec);\n");
    2810                 } else {
    2811                     push(@implContent, "    JSValue thisValue = exec->thisValue();\n");
    2812                     push(@implContent, "    $className* castedThis = " . GetCastingHelperForThisObject($interface) . "(thisValue);\n");
    2813                     my $domFunctionName = $function->signature->name;
    2814                     push(@implContent, "    if (UNLIKELY(!castedThis))\n");
    2815                     push(@implContent, "        return throwThisTypeError(*exec, \"$interfaceName\", \"$domFunctionName\");\n");
    2816                 }
    2817 
    2818                 push(@implContent, "    ASSERT_GC_OBJECT_INHERITS(castedThis, ${className}::info());\n");
     2818                GenerateFunctionCastedThis($interface, $interfaceName, $className, $function) if not (IsReturningPromise($function) && !$isCustom);
    28192819
    28202820                if ($interface->extendedAttributes->{"CheckSecurity"} and
     
    31103110    my $conditionalString = $codeGenerator->GenerateConditionalString($interface);
    31113111    push(@implContent, "\n#endif // ${conditionalString}\n") if $conditionalString;
     3112}
     3113
     3114sub GenerateFunctionCastedThis
     3115{
     3116    my $interface = shift;
     3117    my $interfaceName = shift;
     3118    my $className = shift;
     3119    my $function = shift;
     3120    if ($interface->extendedAttributes->{"CustomProxyToJSObject"}) {
     3121        push(@implContent, "    $className* castedThis = to${className}(exec->thisValue().toThis(exec, NotStrictMode));\n");
     3122        push(@implContent, "    if (UNLIKELY(!castedThis))\n");
     3123        push(@implContent, "        return throwVMTypeError(exec);\n");
     3124    } elsif ($interface->extendedAttributes->{"WorkerGlobalScope"}) {
     3125        push(@implContent, "    $className* castedThis = to${className}(exec->thisValue().toThis(exec, NotStrictMode));\n");
     3126        push(@implContent, "    if (UNLIKELY(!castedThis))\n");
     3127        push(@implContent, "        return throwVMTypeError(exec);\n");
     3128    } else {
     3129        push(@implContent, "    JSValue thisValue = exec->thisValue();\n");
     3130        push(@implContent, "    $className* castedThis = " . GetCastingHelperForThisObject($interface) . "(thisValue);\n");
     3131        my $domFunctionName = $function->signature->name;
     3132        push(@implContent, "    if (UNLIKELY(!castedThis))\n");
     3133        push(@implContent, "        return throwThisTypeError(*exec, \"$interfaceName\", \"$domFunctionName\");\n");
     3134    }
     3135
     3136    push(@implContent, "    ASSERT_GC_OBJECT_INHERITS(castedThis, ${className}::info());\n");
    31123137}
    31133138
     
    35963621    my $raisesException = $function->signature->extendedAttributes->{"RaisesException"};
    35973622
    3598     if ($function->signature->type eq "void") {
     3623    if ($function->signature->type eq "void" || IsReturningPromise($function)) {
    35993624        if ($nondeterministic) {
    36003625            AddToImplIncludes("<replay/InputCursor.h>", "WEB_REPLAY");
     
    36573682            push(@implContent, $indent . "result = " . NativeToJSValue($function->signature, 1, $interfaceName, $functionString, $thisObject) . ";\n");
    36583683            push(@implContent, "#endif\n");
    3659         } elsif (IsReturningPromise($function)) {
    3660             AddToImplIncludes("JSDOMPromise.h");
    3661 
    3662             push(@implContent, $indent . "JSPromiseDeferred* promiseDeferred = JSPromiseDeferred::create(exec, castedThis->globalObject());\n");
    3663             push(@implContent, $indent . $functionString . ";\n");
    3664             push(@implContent, $indent . "JSValue result = promiseDeferred->promise();\n");
    3665 
    36663684        } else {
    36673685            push(@implContent, $indent . "JSValue result = " . NativeToJSValue($function->signature, 1, $interfaceName, $functionString, $thisObject) . ";\n");
    36683686        }
    3669         # FIXME: In case of IsReturningPromise($function), the function should not throw. Exception should be used to reject the promise callback.
    36703687        push(@implContent, "\n" . $indent . "setDOMException(exec, ec);\n") if $raisesException;
    36713688
  • trunk/Source/WebCore/bindings/scripts/test/JS/JSTestObj.cpp

    r185493 r185739  
    157157JSC::EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionAny(JSC::ExecState*);
    158158JSC::EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionTestPromiseFunction(JSC::ExecState*);
     159JSC::EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionTestPromiseFunctionWithFloatArgument(JSC::ExecState*);
     160JSC::EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionTestPromiseFunctionWithException(JSC::ExecState*);
    159161
    160162// Attributes
     
    652654    { "any", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestObjPrototypeFunctionAny), (intptr_t) (2) },
    653655    { "testPromiseFunction", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestObjPrototypeFunctionTestPromiseFunction), (intptr_t) (0) },
     656    { "testPromiseFunctionWithFloatArgument", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestObjPrototypeFunctionTestPromiseFunctionWithFloatArgument), (intptr_t) (1) },
     657    { "testPromiseFunctionWithException", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestObjPrototypeFunctionTestPromiseFunctionWithException), (intptr_t) (0) },
    654658};
    655659
     
    44274431}
    44284432
     4433static inline EncodedJSValue jsTestObjPrototypeFunctionTestPromiseFunctionPromise(ExecState*, JSTestObj*, JSPromiseDeferred*);
    44294434EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionTestPromiseFunction(ExecState* exec)
    44304435{
     
    44344439        return throwThisTypeError(*exec, "TestObj", "testPromiseFunction");
    44354440    ASSERT_GC_OBJECT_INHERITS(castedThis, JSTestObj::info());
    4436     auto& impl = castedThis->impl();
    4437     JSPromiseDeferred* promiseDeferred = JSPromiseDeferred::create(exec, castedThis->globalObject());
     4441    return JSValue::encode(callPromiseFunction(*exec, *castedThis, jsTestObjPrototypeFunctionTestPromiseFunctionPromise));
     4442}
     4443
     4444static inline EncodedJSValue jsTestObjPrototypeFunctionTestPromiseFunctionPromise(ExecState* exec, JSTestObj* castedThis, JSPromiseDeferred* promiseDeferred)
     4445{
     4446    auto& impl = castedThis->impl();
    44384447    impl.testPromiseFunction(DeferredWrapper(exec, castedThis->globalObject(), promiseDeferred));
    4439     JSValue result = promiseDeferred->promise();
    4440     return JSValue::encode(result);
     4448    return JSValue::encode(jsUndefined());
     4449}
     4450
     4451static inline EncodedJSValue jsTestObjPrototypeFunctionTestPromiseFunctionWithFloatArgumentPromise(ExecState*, JSTestObj*, JSPromiseDeferred*);
     4452EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionTestPromiseFunctionWithFloatArgument(ExecState* exec)
     4453{
     4454    JSValue thisValue = exec->thisValue();
     4455    JSTestObj* castedThis = jsDynamicCast<JSTestObj*>(thisValue);
     4456    if (UNLIKELY(!castedThis))
     4457        return throwThisTypeError(*exec, "TestObj", "testPromiseFunctionWithFloatArgument");
     4458    ASSERT_GC_OBJECT_INHERITS(castedThis, JSTestObj::info());
     4459    return JSValue::encode(callPromiseFunction(*exec, *castedThis, jsTestObjPrototypeFunctionTestPromiseFunctionWithFloatArgumentPromise));
     4460}
     4461
     4462static inline EncodedJSValue jsTestObjPrototypeFunctionTestPromiseFunctionWithFloatArgumentPromise(ExecState* exec, JSTestObj* castedThis, JSPromiseDeferred* promiseDeferred)
     4463{
     4464    auto& impl = castedThis->impl();
     4465    if (UNLIKELY(exec->argumentCount() < 1))
     4466        return throwVMError(exec, createNotEnoughArgumentsError(exec));
     4467    float a = exec->argument(0).toFloat(exec);
     4468    if (UNLIKELY(exec->hadException()))
     4469        return JSValue::encode(jsUndefined());
     4470    if (!std::isfinite(a)) {
     4471        setDOMException(exec, TypeError);
     4472        return JSValue::encode(jsUndefined());
     4473    }
     4474    impl.testPromiseFunctionWithFloatArgument(a, DeferredWrapper(exec, castedThis->globalObject(), promiseDeferred));
     4475    return JSValue::encode(jsUndefined());
     4476}
     4477
     4478static inline EncodedJSValue jsTestObjPrototypeFunctionTestPromiseFunctionWithExceptionPromise(ExecState*, JSTestObj*, JSPromiseDeferred*);
     4479EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionTestPromiseFunctionWithException(ExecState* exec)
     4480{
     4481    JSValue thisValue = exec->thisValue();
     4482    JSTestObj* castedThis = jsDynamicCast<JSTestObj*>(thisValue);
     4483    if (UNLIKELY(!castedThis))
     4484        return throwThisTypeError(*exec, "TestObj", "testPromiseFunctionWithException");
     4485    ASSERT_GC_OBJECT_INHERITS(castedThis, JSTestObj::info());
     4486    return JSValue::encode(callPromiseFunction(*exec, *castedThis, jsTestObjPrototypeFunctionTestPromiseFunctionWithExceptionPromise));
     4487}
     4488
     4489static inline EncodedJSValue jsTestObjPrototypeFunctionTestPromiseFunctionWithExceptionPromise(ExecState* exec, JSTestObj* castedThis, JSPromiseDeferred* promiseDeferred)
     4490{
     4491    auto& impl = castedThis->impl();
     4492    ExceptionCode ec = 0;
     4493    impl.testPromiseFunctionWithException(DeferredWrapper(exec, castedThis->globalObject(), promiseDeferred), ec);
     4494    setDOMException(exec, ec);
     4495    return JSValue::encode(jsUndefined());
    44414496}
    44424497
  • trunk/Source/WebCore/bindings/scripts/test/TestObj.idl

    r185493 r185739  
    277277    // Promise function
    278278    Promise testPromiseFunction();
     279    Promise testPromiseFunctionWithFloatArgument(float a);
     280    [RaisesException] Promise testPromiseFunctionWithException();
    279281};
    280282
Note: See TracChangeset for help on using the changeset viewer.