Commit e181bc53 authored by Yuki Shiino's avatar Yuki Shiino Committed by Commit Bot

v8binding: Support "construct a callback function type value".

https://heycam.github.io/webidl/#construct-a-callback-function
This patch supports "construct" algorithm of IDL callback functions.

The following WIP patch will be going to use this algorithm.
https://chromium-review.googlesource.com/c/chromium/src/+/1170731

Bug: 873942
Change-Id: I3d58db6cb4cb23662593e2c96080668346a399a2
Reviewed-on: https://chromium-review.googlesource.com/1174079Reviewed-by: default avatarHitoshi Yoshida <peria@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Commit-Queue: Yuki Shiino <yukishiino@chromium.org>
Cr-Commit-Position: refs/heads/master@{#583192}
parent 0ecac343
...@@ -146,6 +146,110 @@ v8::Maybe<{{return_cpp_type}}> {{cpp_class}}::Invoke({{argument_declarations | j ...@@ -146,6 +146,110 @@ v8::Maybe<{{return_cpp_type}}> {{cpp_class}}::Invoke({{argument_declarations | j
{% endif %} {% endif %}
} }
{% if idl_type == 'any' %}
v8::Maybe<{{return_cpp_type}}> {{cpp_class}}::Construct({{argument_declarations[1:] | join(', ')}}) {
// This function implements "construct" algorithm defined in
// "3.10. Invoking callback functions".
// https://heycam.github.io/webidl/#construct-a-callback-function
if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
IncumbentScriptState())) {
// Wrapper-tracing for the callback function makes the function object and
// its creation context alive. Thus it's safe to use the creation context
// of the callback function here.
v8::HandleScope handle_scope(GetIsolate());
CHECK(!CallbackFunction().IsEmpty());
v8::Context::Scope context_scope(CallbackFunction()->CreationContext());
V8ThrowException::ThrowError(
GetIsolate(),
ExceptionMessages::FailedToExecute(
"construct",
"{{callback_function_name}}",
"The provided callback is no longer runnable."));
return v8::Nothing<{{return_cpp_type}}>();
}
// step 7. Prepare to run script with relevant settings.
ScriptState::Scope callback_relevant_context_scope(
CallbackRelevantScriptState());
// step 8. Prepare to run a callback with stored settings.
v8::Context::BackupIncumbentScope backup_incumbent_scope(
IncumbentScriptState()->GetContext());
// step 3. If ! IsConstructor(F) is false, throw a TypeError exception.
//
// Note that step 7. and 8. are side effect free (except for a very rare
// exception due to no incumbent realm), so it's okay to put step 3. after
// step 7. and 8.
if (!CallbackFunction()->IsConstructor()) {
V8ThrowException::ThrowTypeError(
GetIsolate(),
ExceptionMessages::FailedToExecute(
"construct",
"{{callback_function_name}}",
"The provided callback is not a constructor."));
return v8::Nothing<{{return_cpp_type}}>();
}
// step 9. Let esArgs be the result of converting args to an ECMAScript
// arguments list. If this throws an exception, set completion to the
// completion value representing the thrown exception and jump to the step
// labeled return.
{% if arguments %}
v8::Local<v8::Object> argument_creation_context =
CallbackRelevantScriptState()->GetContext()->Global();
ALLOW_UNUSED_LOCAL(argument_creation_context);
{% set has_variadic_argument = arguments[-1].is_variadic %}
{% set non_variadic_arguments = arguments | rejectattr('is_variadic') | list %}
{% set variadic_argument = arguments[-1] if has_variadic_argument else None %}
{% set arguments_length = '%d + %s.size()' % (non_variadic_arguments|length, variadic_argument.name) if has_variadic_argument else non_variadic_arguments|length %}
{% for argument in non_variadic_arguments %}
v8::Local<v8::Value> {{argument.v8_name}} = {{argument.cpp_value_to_v8_value}};
{% endfor %}
{% if has_variadic_argument %}
const int argc = {{arguments_length}};
v8::Local<v8::Value> argv[argc];
{% for argument in non_variadic_arguments %}
argv[{{loop.index0}}] = {{argument.v8_name}};
{% endfor %}
for (wtf_size_t i = 0; i < {{variadic_argument.name}}.size(); ++i) {
argv[{{non_variadic_arguments|length}} + i] = ToV8({{variadic_argument.name}}[i], argument_creation_context, GetIsolate());
}
{% else %}{# if has_variadic_argument #}
constexpr int argc = {{arguments_length}};
v8::Local<v8::Value> argv[] = { {{non_variadic_arguments | join(', ', 'v8_name')}} };
static_assert(static_cast<size_t>(argc) == base::size(argv), "size mismatch");
{% endif %}
{% else %}{# if arguments #}
const int argc = 0;
{# Zero-length arrays are ill-formed in C++. #}
v8::Local<v8::Value> *argv = nullptr;
{% endif %}
// step 10. Let callResult be Construct(F, esArgs).
v8::Local<v8::Value> call_result;
if (!V8ScriptRunner::CallAsConstructor(
GetIsolate(),
CallbackFunction(),
ExecutionContext::From(CallbackRelevantScriptState()),
argc,
argv).ToLocal(&call_result)) {
// step 11. If callResult is an abrupt completion, set completion to
// callResult and jump to the step labeled return.
return v8::Nothing<{{return_cpp_type}}>();
}
// step 12. Set completion to the result of converting callResult.[[Value]] to
// an IDL value of the same type as the operation's return type.
ExceptionState exceptionState(GetIsolate(),
ExceptionState::kExecutionContext,
"{{callback_function_name}}",
"construct");
{{v8_value_to_local_cpp_value(return_value_conversion) | trim | indent(2)}}
return v8::Just<{{return_cpp_type}}>(native_result);
}
{% endif %}
{% if idl_type == 'void' or callback_function_name == 'Function' %} {% if idl_type == 'void' or callback_function_name == 'Function' %}
void {{cpp_class}}::InvokeAndReportException({{argument_declarations | join(', ')}}) { void {{cpp_class}}::InvokeAndReportException({{argument_declarations | join(', ')}}) {
v8::TryCatch try_catch(GetIsolate()); v8::TryCatch try_catch(GetIsolate());
......
...@@ -30,6 +30,15 @@ class {{exported}}{{cpp_class}} final : public CallbackFunctionBase { ...@@ -30,6 +30,15 @@ class {{exported}}{{cpp_class}} final : public CallbackFunctionBase {
// https://heycam.github.io/webidl/#es-invoking-callback-functions // https://heycam.github.io/webidl/#es-invoking-callback-functions
v8::Maybe<{{return_cpp_type}}> Invoke({{argument_declarations | join(', ')}}) WARN_UNUSED_RESULT; v8::Maybe<{{return_cpp_type}}> Invoke({{argument_declarations | join(', ')}}) WARN_UNUSED_RESULT;
{# Web IDL does not distinguish callback constructors from callback functions.
If the return type is 'any', then it\'s likely to be used as a callback
constructor. #}
{% if idl_type == 'any' %}
// Performs "construct".
// https://heycam.github.io/webidl/#construct-a-callback-function
v8::Maybe<{{return_cpp_type}}> Construct({{argument_declarations[1:] | join(', ')}}) WARN_UNUSED_RESULT;
{% endif %}
{# Type Function is often used as a sort of wild cards, and its return value is {# Type Function is often used as a sort of wild cards, and its return value is
often discarded. So, this provides some convenience. #} often discarded. So, this provides some convenience. #}
{% if idl_type == 'void' or callback_function_name == 'Function' %} {% if idl_type == 'void' or callback_function_name == 'Function' %}
......
...@@ -108,6 +108,85 @@ v8::Maybe<ScriptValue> V8AnyCallbackFunctionOptionalAnyArg::Invoke(ScriptWrappab ...@@ -108,6 +108,85 @@ v8::Maybe<ScriptValue> V8AnyCallbackFunctionOptionalAnyArg::Invoke(ScriptWrappab
} }
} }
v8::Maybe<ScriptValue> V8AnyCallbackFunctionOptionalAnyArg::Construct(ScriptValue optionalAnyArg) {
// This function implements "construct" algorithm defined in
// "3.10. Invoking callback functions".
// https://heycam.github.io/webidl/#construct-a-callback-function
if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
IncumbentScriptState())) {
// Wrapper-tracing for the callback function makes the function object and
// its creation context alive. Thus it's safe to use the creation context
// of the callback function here.
v8::HandleScope handle_scope(GetIsolate());
CHECK(!CallbackFunction().IsEmpty());
v8::Context::Scope context_scope(CallbackFunction()->CreationContext());
V8ThrowException::ThrowError(
GetIsolate(),
ExceptionMessages::FailedToExecute(
"construct",
"AnyCallbackFunctionOptionalAnyArg",
"The provided callback is no longer runnable."));
return v8::Nothing<ScriptValue>();
}
// step 7. Prepare to run script with relevant settings.
ScriptState::Scope callback_relevant_context_scope(
CallbackRelevantScriptState());
// step 8. Prepare to run a callback with stored settings.
v8::Context::BackupIncumbentScope backup_incumbent_scope(
IncumbentScriptState()->GetContext());
// step 3. If ! IsConstructor(F) is false, throw a TypeError exception.
//
// Note that step 7. and 8. are side effect free (except for a very rare
// exception due to no incumbent realm), so it's okay to put step 3. after
// step 7. and 8.
if (!CallbackFunction()->IsConstructor()) {
V8ThrowException::ThrowTypeError(
GetIsolate(),
ExceptionMessages::FailedToExecute(
"construct",
"AnyCallbackFunctionOptionalAnyArg",
"The provided callback is not a constructor."));
return v8::Nothing<ScriptValue>();
}
// step 9. Let esArgs be the result of converting args to an ECMAScript
// arguments list. If this throws an exception, set completion to the
// completion value representing the thrown exception and jump to the step
// labeled return.
v8::Local<v8::Object> argument_creation_context =
CallbackRelevantScriptState()->GetContext()->Global();
ALLOW_UNUSED_LOCAL(argument_creation_context);
v8::Local<v8::Value> v8_optionalAnyArg = optionalAnyArg.V8Value();
constexpr int argc = 1;
v8::Local<v8::Value> argv[] = { v8_optionalAnyArg };
static_assert(static_cast<size_t>(argc) == base::size(argv), "size mismatch");
// step 10. Let callResult be Construct(F, esArgs).
v8::Local<v8::Value> call_result;
if (!V8ScriptRunner::CallAsConstructor(
GetIsolate(),
CallbackFunction(),
ExecutionContext::From(CallbackRelevantScriptState()),
argc,
argv).ToLocal(&call_result)) {
// step 11. If callResult is an abrupt completion, set completion to
// callResult and jump to the step labeled return.
return v8::Nothing<ScriptValue>();
}
// step 12. Set completion to the result of converting callResult.[[Value]] to
// an IDL value of the same type as the operation's return type.
ExceptionState exceptionState(GetIsolate(),
ExceptionState::kExecutionContext,
"AnyCallbackFunctionOptionalAnyArg",
"construct");
ScriptValue native_result = ScriptValue(ScriptState::Current(GetIsolate()), call_result);
return v8::Just<ScriptValue>(native_result);
}
v8::Maybe<ScriptValue> V8PersistentCallbackFunction<V8AnyCallbackFunctionOptionalAnyArg>::Invoke(ScriptWrappable* callback_this_value, ScriptValue optionalAnyArg) { v8::Maybe<ScriptValue> V8PersistentCallbackFunction<V8AnyCallbackFunctionOptionalAnyArg>::Invoke(ScriptWrappable* callback_this_value, ScriptValue optionalAnyArg) {
return Proxy()->Invoke( return Proxy()->Invoke(
callback_this_value, optionalAnyArg); callback_this_value, optionalAnyArg);
......
...@@ -34,6 +34,10 @@ class CORE_EXPORT V8AnyCallbackFunctionOptionalAnyArg final : public CallbackFun ...@@ -34,6 +34,10 @@ class CORE_EXPORT V8AnyCallbackFunctionOptionalAnyArg final : public CallbackFun
// https://heycam.github.io/webidl/#es-invoking-callback-functions // https://heycam.github.io/webidl/#es-invoking-callback-functions
v8::Maybe<ScriptValue> Invoke(ScriptWrappable* callback_this_value, ScriptValue optionalAnyArg) WARN_UNUSED_RESULT; v8::Maybe<ScriptValue> Invoke(ScriptWrappable* callback_this_value, ScriptValue optionalAnyArg) WARN_UNUSED_RESULT;
// Performs "construct".
// https://heycam.github.io/webidl/#construct-a-callback-function
v8::Maybe<ScriptValue> Construct(ScriptValue optionalAnyArg) WARN_UNUSED_RESULT;
private: private:
explicit V8AnyCallbackFunctionOptionalAnyArg(v8::Local<v8::Function> callback_function) explicit V8AnyCallbackFunctionOptionalAnyArg(v8::Local<v8::Function> callback_function)
: CallbackFunctionBase(callback_function) {} : CallbackFunctionBase(callback_function) {}
......
...@@ -109,6 +109,86 @@ v8::Maybe<ScriptValue> V8AnyCallbackFunctionVariadicAnyArgs::Invoke(ScriptWrappa ...@@ -109,6 +109,86 @@ v8::Maybe<ScriptValue> V8AnyCallbackFunctionVariadicAnyArgs::Invoke(ScriptWrappa
} }
} }
v8::Maybe<ScriptValue> V8AnyCallbackFunctionVariadicAnyArgs::Construct(const Vector<ScriptValue>& arguments) {
// This function implements "construct" algorithm defined in
// "3.10. Invoking callback functions".
// https://heycam.github.io/webidl/#construct-a-callback-function
if (!IsCallbackFunctionRunnable(CallbackRelevantScriptState(),
IncumbentScriptState())) {
// Wrapper-tracing for the callback function makes the function object and
// its creation context alive. Thus it's safe to use the creation context
// of the callback function here.
v8::HandleScope handle_scope(GetIsolate());
CHECK(!CallbackFunction().IsEmpty());
v8::Context::Scope context_scope(CallbackFunction()->CreationContext());
V8ThrowException::ThrowError(
GetIsolate(),
ExceptionMessages::FailedToExecute(
"construct",
"AnyCallbackFunctionVariadicAnyArgs",
"The provided callback is no longer runnable."));
return v8::Nothing<ScriptValue>();
}
// step 7. Prepare to run script with relevant settings.
ScriptState::Scope callback_relevant_context_scope(
CallbackRelevantScriptState());
// step 8. Prepare to run a callback with stored settings.
v8::Context::BackupIncumbentScope backup_incumbent_scope(
IncumbentScriptState()->GetContext());
// step 3. If ! IsConstructor(F) is false, throw a TypeError exception.
//
// Note that step 7. and 8. are side effect free (except for a very rare
// exception due to no incumbent realm), so it's okay to put step 3. after
// step 7. and 8.
if (!CallbackFunction()->IsConstructor()) {
V8ThrowException::ThrowTypeError(
GetIsolate(),
ExceptionMessages::FailedToExecute(
"construct",
"AnyCallbackFunctionVariadicAnyArgs",
"The provided callback is not a constructor."));
return v8::Nothing<ScriptValue>();
}
// step 9. Let esArgs be the result of converting args to an ECMAScript
// arguments list. If this throws an exception, set completion to the
// completion value representing the thrown exception and jump to the step
// labeled return.
v8::Local<v8::Object> argument_creation_context =
CallbackRelevantScriptState()->GetContext()->Global();
ALLOW_UNUSED_LOCAL(argument_creation_context);
const int argc = 0 + arguments.size();
v8::Local<v8::Value> argv[argc];
for (wtf_size_t i = 0; i < arguments.size(); ++i) {
argv[0 + i] = ToV8(arguments[i], argument_creation_context, GetIsolate());
}
// step 10. Let callResult be Construct(F, esArgs).
v8::Local<v8::Value> call_result;
if (!V8ScriptRunner::CallAsConstructor(
GetIsolate(),
CallbackFunction(),
ExecutionContext::From(CallbackRelevantScriptState()),
argc,
argv).ToLocal(&call_result)) {
// step 11. If callResult is an abrupt completion, set completion to
// callResult and jump to the step labeled return.
return v8::Nothing<ScriptValue>();
}
// step 12. Set completion to the result of converting callResult.[[Value]] to
// an IDL value of the same type as the operation's return type.
ExceptionState exceptionState(GetIsolate(),
ExceptionState::kExecutionContext,
"AnyCallbackFunctionVariadicAnyArgs",
"construct");
ScriptValue native_result = ScriptValue(ScriptState::Current(GetIsolate()), call_result);
return v8::Just<ScriptValue>(native_result);
}
v8::Maybe<ScriptValue> V8PersistentCallbackFunction<V8AnyCallbackFunctionVariadicAnyArgs>::Invoke(ScriptWrappable* callback_this_value, const Vector<ScriptValue>& arguments) { v8::Maybe<ScriptValue> V8PersistentCallbackFunction<V8AnyCallbackFunctionVariadicAnyArgs>::Invoke(ScriptWrappable* callback_this_value, const Vector<ScriptValue>& arguments) {
return Proxy()->Invoke( return Proxy()->Invoke(
callback_this_value, arguments); callback_this_value, arguments);
......
...@@ -34,6 +34,10 @@ class CORE_EXPORT V8AnyCallbackFunctionVariadicAnyArgs final : public CallbackFu ...@@ -34,6 +34,10 @@ class CORE_EXPORT V8AnyCallbackFunctionVariadicAnyArgs final : public CallbackFu
// https://heycam.github.io/webidl/#es-invoking-callback-functions // https://heycam.github.io/webidl/#es-invoking-callback-functions
v8::Maybe<ScriptValue> Invoke(ScriptWrappable* callback_this_value, const Vector<ScriptValue>& arguments) WARN_UNUSED_RESULT; v8::Maybe<ScriptValue> Invoke(ScriptWrappable* callback_this_value, const Vector<ScriptValue>& arguments) WARN_UNUSED_RESULT;
// Performs "construct".
// https://heycam.github.io/webidl/#construct-a-callback-function
v8::Maybe<ScriptValue> Construct(const Vector<ScriptValue>& arguments) WARN_UNUSED_RESULT;
private: private:
explicit V8AnyCallbackFunctionVariadicAnyArgs(v8::Local<v8::Function> callback_function) explicit V8AnyCallbackFunctionVariadicAnyArgs(v8::Local<v8::Function> callback_function)
: CallbackFunctionBase(callback_function) {} : CallbackFunctionBase(callback_function) {}
......
...@@ -38,6 +38,9 @@ class PLATFORM_EXPORT CallbackFunctionBase ...@@ -38,6 +38,9 @@ class PLATFORM_EXPORT CallbackFunctionBase
return callback_relevant_script_state_; return callback_relevant_script_state_;
} }
// Returns true if the ES function has a [[Construct]] internal method.
bool IsConstructor() const { return CallbackFunction()->IsConstructor(); }
protected: protected:
explicit CallbackFunctionBase(v8::Local<v8::Function>); explicit CallbackFunctionBase(v8::Local<v8::Function>);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment