Title: [185739] trunk/Source/WebCore
Revision
185739
Author
[email protected]
Date
2015-06-19 00:33:35 -0700 (Fri, 19 Jun 2015)

Log 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:

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (185738 => 185739)


--- trunk/Source/WebCore/ChangeLog	2015-06-19 06:55:38 UTC (rev 185738)
+++ trunk/Source/WebCore/ChangeLog	2015-06-19 07:33:35 UTC (rev 185739)
@@ -1,3 +1,42 @@
+2015-06-19  Youenn Fablet  <[email protected]>
+
+        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:
+
 2015-06-18  Jeremy Jones  <[email protected]>
 
         Disable UIWindow for fullscreen video for selected clients.

Modified: trunk/Source/WebCore/bindings/js/JSDOMPromise.cpp (185738 => 185739)


--- trunk/Source/WebCore/bindings/js/JSDOMPromise.cpp	2015-06-19 06:55:38 UTC (rev 185738)
+++ trunk/Source/WebCore/bindings/js/JSDOMPromise.cpp	2015-06-19 07:33:35 UTC (rev 185739)
@@ -29,6 +29,7 @@
 #if ENABLE(PROMISES)
 
 #include "ExceptionCode.h"
+#include <runtime/Exception.h>
 
 using namespace JSC;
 
@@ -62,6 +63,17 @@
     m_deferred.clear();
 }
 
+void rejectPromiseWithExceptionIfAny(JSC::ExecState& state, JSDOMGlobalObject& globalObject, JSPromiseDeferred& promiseDeferred)
+{
+    if (!state.hadException())
+        return;
+
+    JSValue error = state.exception()->value();
+    state.clearException();
+
+    DeferredWrapper(&state, &globalObject, &promiseDeferred).reject(error);
 }
 
+}
+
 #endif // ENABLE(PROMISES)

Modified: trunk/Source/WebCore/bindings/js/JSDOMPromise.h (185738 => 185739)


--- trunk/Source/WebCore/bindings/js/JSDOMPromise.h	2015-06-19 06:55:38 UTC (rev 185738)
+++ trunk/Source/WebCore/bindings/js/JSDOMPromise.h	2015-06-19 07:33:35 UTC (rev 185739)
@@ -57,6 +57,19 @@
     JSC::Strong<JSC::JSPromiseDeferred> m_deferred;
 };
 
+void rejectPromiseWithExceptionIfAny(JSC::ExecState&, JSDOMGlobalObject&, JSC::JSPromiseDeferred&);
+
+template<class JSClassName>
+inline JSC::JSValue callPromiseFunction(JSC::ExecState& state, JSClassName& jsObject, JSC::EncodedJSValue promiseFunction(JSC::ExecState*, JSClassName*, JSC::JSPromiseDeferred*))
+{
+    JSC::JSPromiseDeferred* promiseDeferred = JSC::JSPromiseDeferred::create(&state, jsObject.globalObject());
+    promiseFunction(&state, &jsObject, promiseDeferred);
+
+    rejectPromiseWithExceptionIfAny(state, *jsObject.globalObject(), *promiseDeferred);
+    ASSERT(!state.hadException());
+    return promiseDeferred->promise();
+}
+
 template <typename Value, typename Error>
 class DOMPromise {
 public:

Modified: trunk/Source/WebCore/bindings/scripts/CodeGeneratorJS.pm (185738 => 185739)


--- trunk/Source/WebCore/bindings/scripts/CodeGeneratorJS.pm	2015-06-19 06:55:38 UTC (rev 185738)
+++ trunk/Source/WebCore/bindings/scripts/CodeGeneratorJS.pm	2015-06-19 07:33:35 UTC (rev 185739)
@@ -2780,7 +2780,23 @@
 
             my $functionImplementationName = $function->signature->extendedAttributes->{"ImplementedAs"} || $codeGenerator->WK_lcfirst($function->signature->name);
 
-            push(@implContent, "EncodedJSValue JSC_HOST_CALL ${functionName}(ExecState* exec)\n");
+            if (IsReturningPromise($function) && !$isCustom) {
+                AddToImplIncludes("JSDOMPromise.h");
+
+                push(@implContent, "static inline EncodedJSValue ${functionName}Promise(ExecState*, " . $className . "*, JSPromiseDeferred*);\n");
+                push(@implContent, "EncodedJSValue JSC_HOST_CALL ${functionName}(ExecState* exec)\n");
+                push(@implContent, "{\n");
+
+                GenerateFunctionCastedThis($interface, $interfaceName, $className, $function);
+                push(@implContent, "    return JSValue::encode(callPromiseFunction(*exec, *castedThis, ${functionName}Promise));\n");
+
+                push(@implContent, "}\n");
+                push(@implContent, "\nstatic inline EncodedJSValue ${functionName}Promise(ExecState* exec, " . $className . "* castedThis, JSPromiseDeferred* promiseDeferred)\n");
+            }
+            else {
+                push(@implContent, "EncodedJSValue JSC_HOST_CALL ${functionName}(ExecState* exec)\n");
+            }
+
             push(@implContent, "{\n");
 
             $implIncludes{"<runtime/Error.h>"} = 1;
@@ -2799,24 +2815,8 @@
                     GenerateImplementationFunctionCall($function, $functionString, "    ", $svgPropertyType, $interfaceName);
                 }
             } else {
-                if ($interface->extendedAttributes->{"CustomProxyToJSObject"}) {
-                    push(@implContent, "    $className* castedThis = to${className}(exec->thisValue().toThis(exec, NotStrictMode));\n");
-                    push(@implContent, "    if (UNLIKELY(!castedThis))\n");
-                    push(@implContent, "        return throwVMTypeError(exec);\n");
-                } elsif ($interface->extendedAttributes->{"WorkerGlobalScope"}) {
-                    push(@implContent, "    $className* castedThis = to${className}(exec->thisValue().toThis(exec, NotStrictMode));\n");
-                    push(@implContent, "    if (UNLIKELY(!castedThis))\n");
-                    push(@implContent, "        return throwVMTypeError(exec);\n");
-                } else {
-                    push(@implContent, "    JSValue thisValue = exec->thisValue();\n");
-                    push(@implContent, "    $className* castedThis = " . GetCastingHelperForThisObject($interface) . "(thisValue);\n");
-                    my $domFunctionName = $function->signature->name;
-                    push(@implContent, "    if (UNLIKELY(!castedThis))\n");
-                    push(@implContent, "        return throwThisTypeError(*exec, \"$interfaceName\", \"$domFunctionName\");\n");
-                }
+                GenerateFunctionCastedThis($interface, $interfaceName, $className, $function) if not (IsReturningPromise($function) && !$isCustom);
 
-                push(@implContent, "    ASSERT_GC_OBJECT_INHERITS(castedThis, ${className}::info());\n");
-
                 if ($interface->extendedAttributes->{"CheckSecurity"} and
                     !$function->signature->extendedAttributes->{"DoNotCheckSecurity"}) {
                     push(@implContent, "    if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, castedThis->impl()))\n");
@@ -3111,6 +3111,31 @@
     push(@implContent, "\n#endif // ${conditionalString}\n") if $conditionalString;
 }
 
+sub GenerateFunctionCastedThis
+{
+    my $interface = shift;
+    my $interfaceName = shift;
+    my $className = shift;
+    my $function = shift;
+    if ($interface->extendedAttributes->{"CustomProxyToJSObject"}) {
+        push(@implContent, "    $className* castedThis = to${className}(exec->thisValue().toThis(exec, NotStrictMode));\n");
+        push(@implContent, "    if (UNLIKELY(!castedThis))\n");
+        push(@implContent, "        return throwVMTypeError(exec);\n");
+    } elsif ($interface->extendedAttributes->{"WorkerGlobalScope"}) {
+        push(@implContent, "    $className* castedThis = to${className}(exec->thisValue().toThis(exec, NotStrictMode));\n");
+        push(@implContent, "    if (UNLIKELY(!castedThis))\n");
+        push(@implContent, "        return throwVMTypeError(exec);\n");
+    } else {
+        push(@implContent, "    JSValue thisValue = exec->thisValue();\n");
+        push(@implContent, "    $className* castedThis = " . GetCastingHelperForThisObject($interface) . "(thisValue);\n");
+        my $domFunctionName = $function->signature->name;
+        push(@implContent, "    if (UNLIKELY(!castedThis))\n");
+        push(@implContent, "        return throwThisTypeError(*exec, \"$interfaceName\", \"$domFunctionName\");\n");
+    }
+
+    push(@implContent, "    ASSERT_GC_OBJECT_INHERITS(castedThis, ${className}::info());\n");
+}
+
 sub GenerateCallWith
 {
     my $callWith = shift;
@@ -3595,7 +3620,7 @@
     my $nondeterministic = $function->signature->extendedAttributes->{"Nondeterministic"};
     my $raisesException = $function->signature->extendedAttributes->{"RaisesException"};
 
-    if ($function->signature->type eq "void") {
+    if ($function->signature->type eq "void" || IsReturningPromise($function)) {
         if ($nondeterministic) {
             AddToImplIncludes("<replay/InputCursor.h>", "WEB_REPLAY");
             push(@implContent, "#if ENABLE(WEB_REPLAY)\n");
@@ -3656,17 +3681,9 @@
             push(@implContent, "#else\n");
             push(@implContent, $indent . "result = " . NativeToJSValue($function->signature, 1, $interfaceName, $functionString, $thisObject) . ";\n");
             push(@implContent, "#endif\n");
-        } elsif (IsReturningPromise($function)) {
-            AddToImplIncludes("JSDOMPromise.h");
-
-            push(@implContent, $indent . "JSPromiseDeferred* promiseDeferred = JSPromiseDeferred::create(exec, castedThis->globalObject());\n");
-            push(@implContent, $indent . $functionString . ";\n");
-            push(@implContent, $indent . "JSValue result = promiseDeferred->promise();\n");
-
         } else {
             push(@implContent, $indent . "JSValue result = " . NativeToJSValue($function->signature, 1, $interfaceName, $functionString, $thisObject) . ";\n");
         }
-        # FIXME: In case of IsReturningPromise($function), the function should not throw. Exception should be used to reject the promise callback.
         push(@implContent, "\n" . $indent . "setDOMException(exec, ec);\n") if $raisesException;
 
         if ($codeGenerator->ExtendedAttributeContains($function->signature->extendedAttributes->{"CallWith"}, "ScriptState")) {

Modified: trunk/Source/WebCore/bindings/scripts/test/JS/JSTestObj.cpp (185738 => 185739)


--- trunk/Source/WebCore/bindings/scripts/test/JS/JSTestObj.cpp	2015-06-19 06:55:38 UTC (rev 185738)
+++ trunk/Source/WebCore/bindings/scripts/test/JS/JSTestObj.cpp	2015-06-19 07:33:35 UTC (rev 185739)
@@ -156,6 +156,8 @@
 JSC::EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionVariadicNodeMethod(JSC::ExecState*);
 JSC::EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionAny(JSC::ExecState*);
 JSC::EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionTestPromiseFunction(JSC::ExecState*);
+JSC::EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionTestPromiseFunctionWithFloatArgument(JSC::ExecState*);
+JSC::EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionTestPromiseFunctionWithException(JSC::ExecState*);
 
 // Attributes
 
@@ -651,6 +653,8 @@
     { "variadicNodeMethod", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestObjPrototypeFunctionVariadicNodeMethod), (intptr_t) (2) },
     { "any", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestObjPrototypeFunctionAny), (intptr_t) (2) },
     { "testPromiseFunction", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestObjPrototypeFunctionTestPromiseFunction), (intptr_t) (0) },
+    { "testPromiseFunctionWithFloatArgument", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestObjPrototypeFunctionTestPromiseFunctionWithFloatArgument), (intptr_t) (1) },
+    { "testPromiseFunctionWithException", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestObjPrototypeFunctionTestPromiseFunctionWithException), (intptr_t) (0) },
 };
 
 const ClassInfo JSTestObjPrototype::s_info = { "TestObjectPrototype", &Base::s_info, 0, CREATE_METHOD_TABLE(JSTestObjPrototype) };
@@ -4426,6 +4430,7 @@
     return JSValue::encode(jsUndefined());
 }
 
+static inline EncodedJSValue jsTestObjPrototypeFunctionTestPromiseFunctionPromise(ExecState*, JSTestObj*, JSPromiseDeferred*);
 EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionTestPromiseFunction(ExecState* exec)
 {
     JSValue thisValue = exec->thisValue();
@@ -4433,13 +4438,63 @@
     if (UNLIKELY(!castedThis))
         return throwThisTypeError(*exec, "TestObj", "testPromiseFunction");
     ASSERT_GC_OBJECT_INHERITS(castedThis, JSTestObj::info());
+    return JSValue::encode(callPromiseFunction(*exec, *castedThis, jsTestObjPrototypeFunctionTestPromiseFunctionPromise));
+}
+
+static inline EncodedJSValue jsTestObjPrototypeFunctionTestPromiseFunctionPromise(ExecState* exec, JSTestObj* castedThis, JSPromiseDeferred* promiseDeferred)
+{
     auto& impl = castedThis->impl();
-    JSPromiseDeferred* promiseDeferred = JSPromiseDeferred::create(exec, castedThis->globalObject());
     impl.testPromiseFunction(DeferredWrapper(exec, castedThis->globalObject(), promiseDeferred));
-    JSValue result = promiseDeferred->promise();
-    return JSValue::encode(result);
+    return JSValue::encode(jsUndefined());
 }
 
+static inline EncodedJSValue jsTestObjPrototypeFunctionTestPromiseFunctionWithFloatArgumentPromise(ExecState*, JSTestObj*, JSPromiseDeferred*);
+EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionTestPromiseFunctionWithFloatArgument(ExecState* exec)
+{
+    JSValue thisValue = exec->thisValue();
+    JSTestObj* castedThis = jsDynamicCast<JSTestObj*>(thisValue);
+    if (UNLIKELY(!castedThis))
+        return throwThisTypeError(*exec, "TestObj", "testPromiseFunctionWithFloatArgument");
+    ASSERT_GC_OBJECT_INHERITS(castedThis, JSTestObj::info());
+    return JSValue::encode(callPromiseFunction(*exec, *castedThis, jsTestObjPrototypeFunctionTestPromiseFunctionWithFloatArgumentPromise));
+}
+
+static inline EncodedJSValue jsTestObjPrototypeFunctionTestPromiseFunctionWithFloatArgumentPromise(ExecState* exec, JSTestObj* castedThis, JSPromiseDeferred* promiseDeferred)
+{
+    auto& impl = castedThis->impl();
+    if (UNLIKELY(exec->argumentCount() < 1))
+        return throwVMError(exec, createNotEnoughArgumentsError(exec));
+    float a = exec->argument(0).toFloat(exec);
+    if (UNLIKELY(exec->hadException()))
+        return JSValue::encode(jsUndefined());
+    if (!std::isfinite(a)) {
+        setDOMException(exec, TypeError);
+        return JSValue::encode(jsUndefined());
+    }
+    impl.testPromiseFunctionWithFloatArgument(a, DeferredWrapper(exec, castedThis->globalObject(), promiseDeferred));
+    return JSValue::encode(jsUndefined());
+}
+
+static inline EncodedJSValue jsTestObjPrototypeFunctionTestPromiseFunctionWithExceptionPromise(ExecState*, JSTestObj*, JSPromiseDeferred*);
+EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionTestPromiseFunctionWithException(ExecState* exec)
+{
+    JSValue thisValue = exec->thisValue();
+    JSTestObj* castedThis = jsDynamicCast<JSTestObj*>(thisValue);
+    if (UNLIKELY(!castedThis))
+        return throwThisTypeError(*exec, "TestObj", "testPromiseFunctionWithException");
+    ASSERT_GC_OBJECT_INHERITS(castedThis, JSTestObj::info());
+    return JSValue::encode(callPromiseFunction(*exec, *castedThis, jsTestObjPrototypeFunctionTestPromiseFunctionWithExceptionPromise));
+}
+
+static inline EncodedJSValue jsTestObjPrototypeFunctionTestPromiseFunctionWithExceptionPromise(ExecState* exec, JSTestObj* castedThis, JSPromiseDeferred* promiseDeferred)
+{
+    auto& impl = castedThis->impl();
+    ExceptionCode ec = 0;
+    impl.testPromiseFunctionWithException(DeferredWrapper(exec, castedThis->globalObject(), promiseDeferred), ec);
+    setDOMException(exec, ec);
+    return JSValue::encode(jsUndefined());
+}
+
 void JSTestObj::visitChildren(JSCell* cell, SlotVisitor& visitor)
 {
     auto* thisObject = jsCast<JSTestObj*>(cell);

Modified: trunk/Source/WebCore/bindings/scripts/test/TestObj.idl (185738 => 185739)


--- trunk/Source/WebCore/bindings/scripts/test/TestObj.idl	2015-06-19 06:55:38 UTC (rev 185738)
+++ trunk/Source/WebCore/bindings/scripts/test/TestObj.idl	2015-06-19 07:33:35 UTC (rev 185739)
@@ -276,6 +276,8 @@
 
     // Promise function
     Promise testPromiseFunction();
+    Promise testPromiseFunctionWithFloatArgument(float a);
+    [RaisesException] Promise testPromiseFunctionWithException();
 };
 
 // The following comment should not generate any code.
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to