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.