blob: 231d6870a59604a1cc56ca082dd6ea4f14737545 [file] [log] [blame]
{% from 'utilities.cc.tmpl' import declare_enum_validation_variable %}
{# Implements callback interface\'s "call a user object's operation", or
callback function\'s "invoke" and/or "construct".
https://heycam.github.io/webidl/#call-a-user-objects-operation
https://heycam.github.io/webidl/#es-invoking-callback-functions
Args:
interface_or_function = 'callback interface' or 'callback function'
invoke_or_construct = 'invoke', 'construct', or None
return_cpp_type = Blink (C++) return type
return_native_value_traits_tag = tag of NativeValueTraits for return type
arguments = dict of arguments\' info
is_treat_non_object_as_null = True if [TreatNonObjectAsNull]
bypass_runnability_check = Skip IsCallbackFunctionRunnable check if True
interface_name = interface name used for exception
operation_name = interface name used for exception and property lookup
#}
{% macro callback_invoke(
interface_or_function, invoke_or_construct,
return_cpp_type, return_native_value_traits_tag, arguments,
is_treat_non_object_as_null, bypass_runnability_check,
interface_name, operation_name) %}
{% if not bypass_runnability_check %}
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());
v8::Local<v8::Object> callback_object = CallbackObject();
CHECK(!callback_object.IsEmpty());
v8::Context::Scope context_scope(callback_object->CreationContext());
V8ThrowException::ThrowError(
GetIsolate(),
ExceptionMessages::FailedToExecute(
"{{operation_name}}",
"{{interface_name}}",
"The provided callback is no longer runnable."));
return v8::Nothing<{{return_cpp_type}}>();
}
{% endif %}
// step: Prepare to run script with relevant settings.
ScriptState::Scope callback_relevant_context_scope(
CallbackRelevantScriptState());
// step: Prepare to run a callback with stored settings.
v8::Context::BackupIncumbentScope backup_incumbent_scope(
IncumbentScriptState()->GetContext());
{% if invoke_or_construct == 'construct' %}
// 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 (!IsConstructor()) {
V8ThrowException::ThrowTypeError(
GetIsolate(),
ExceptionMessages::FailedToExecute(
"{{operation_name}}",
"{{interface_name}}",
"The provided callback is not a constructor."));
return v8::Nothing<{{return_cpp_type}}>();
}
{% endif %}
v8::Local<v8::Function> function;
{# Fill |function|. #}
{% if interface_or_function == 'callback function' %}
// callback function's invoke:
// step 4. If ! IsCallable(F) is false:
{% if is_treat_non_object_as_null %}
if (!CallbackObject()->IsFunction()) {
// Handle the special case of [TreatNonObjectAsNull].
//
{% if return_cpp_type == 'void' %}
// step 4.1. If the callback function's return type is void, return.
return v8::JustVoid();
{% else %}
// step 4.2. Return the result of converting undefined to the callback
// function's return type.
ExceptionState exception_state(GetIsolate(),
ExceptionState::kExecutionContext,
"{{interface_name}}",
"{{operation_name}}");
auto native_result =
NativeValueTraits<{{return_native_value_traits_tag}}>::NativeValue(
GetIsolate(), v8::Undefined(GetIsolate()), exception_state);
if (exception_state.HadException())
return v8::Nothing<{{return_cpp_type}}>();
else
return v8::Just<{{return_cpp_type}}>(native_result);
{% endif %}{# if return_cpp_type == 'void' #}
}
{% else %}
//
// No [TreatNonObjectAsNull] presents. Must be always callable.
DCHECK(CallbackObject()->IsFunction());
{% endif %}
function = CallbackFunction();
{% else %}
if (IsCallbackObjectCallable()) {
// step 9.1. If value's interface is a single operation callback interface
// and !IsCallable(O) is true, then set X to O.
function = CallbackObject().As<v8::Function>();
} else {
// step 9.2.1. Let getResult be Get(O, opName).
// step 9.2.2. If getResult is an abrupt completion, set completion to
// getResult and jump to the step labeled return.
v8::Local<v8::Value> value;
if (!CallbackObject()->Get(CallbackRelevantScriptState()->GetContext(),
V8String(GetIsolate(), "{{operation_name}}"))
.ToLocal(&value)) {
return v8::Nothing<{{return_cpp_type}}>();
}
// step 10. If !IsCallable(X) is false, then set completion to a new
// Completion{[[Type]]: throw, [[Value]]: a newly created TypeError
// object, [[Target]]: empty}, and jump to the step labeled return.
if (!value->IsFunction()) {
V8ThrowException::ThrowTypeError(
GetIsolate(),
ExceptionMessages::FailedToExecute(
"{{operation_name}}",
"{{interface_name}}",
"The provided callback is not callable."));
return v8::Nothing<{{return_cpp_type}}>();
}
function = value.As<v8::Function>();
}
{% endif %}
{% if invoke_or_construct != 'construct' %}
v8::Local<v8::Value> this_arg;
{% endif %}
{# Fill |this_arg|. #}
{% if invoke_or_construct == 'invoke' %}
this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
{% elif interface_or_function == 'callback interface' %}
if (!IsCallbackObjectCallable()) {
// step 11. If value's interface is not a single operation callback
// interface, or if !IsCallable(O) is false, set thisArg to O (overriding
// the provided value).
this_arg = CallbackObject();
} else if (!callback_this_value) {
// step 2. If thisArg was not given, let thisArg be undefined.
this_arg = v8::Undefined(GetIsolate());
} else {
this_arg = ToV8(callback_this_value, CallbackRelevantScriptState());
}
{% endif %}
{% for argument in arguments if argument.enum_values %}
// Enum values provided by Blink must be valid, otherwise typo.
#if DCHECK_IS_ON()
{
{% set valid_enum_variables = 'valid_' + argument.name + '_values' %}
{{declare_enum_validation_variable(argument.enum_values, valid_enum_variables) | trim | indent(4)}}
ExceptionState exception_state(GetIsolate(),
ExceptionState::kExecutionContext,
"{{interface_name}}",
"{{operation_name}}");
if (!IsValidEnum({{argument.name}}, {{valid_enum_variables}}, base::size({{valid_enum_variables}}), "{{argument.enum_type}}", exception_state)) { //
NOTREACHED();
return v8::Nothing<{{return_cpp_type}}>();
}
}
#endif
{% endfor %}
// step: 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 %}
v8::Local<v8::Value> call_result;
{# Fill |call_result|. #}
{% if invoke_or_construct == 'construct' %}
if (!V8ScriptRunner::CallAsConstructor(
GetIsolate(),
function,
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}}>();
}
{% else %}
// step: Let callResult be Call(X, thisArg, esArgs).
if (!V8ScriptRunner::CallFunction(
function,
ExecutionContext::From(CallbackRelevantScriptState()),
this_arg,
argc,
argv,
GetIsolate()).ToLocal(&call_result)) {
// step: If callResult is an abrupt completion, set completion to callResult
// and jump to the step labeled return.
return v8::Nothing<{{return_cpp_type}}>();
}
{% endif %}
// step: Set completion to the result of converting callResult.[[Value]] to
// an IDL value of the same type as the operation's return type.
{% if return_cpp_type == 'void' %}
return v8::JustVoid();
{% else %}
{
ExceptionState exception_state(GetIsolate(),
ExceptionState::kExecutionContext,
"{{interface_name}}",
"{{operation_name}}");
auto native_result =
NativeValueTraits<{{return_native_value_traits_tag}}>::NativeValue(
GetIsolate(), call_result, exception_state);
if (exception_state.HadException())
return v8::Nothing<{{return_cpp_type}}>();
else
return v8::Just<{{return_cpp_type}}>(native_result);
}
{% endif %}
{% endmacro %}