| {% 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 %} |