# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import web_idl
from . import name_style
from .blink_v8_bridge import blink_class_name
from .blink_v8_bridge import blink_type_info
from .blink_v8_bridge import make_blink_to_v8_value
from .blink_v8_bridge import native_value_tag
from .code_node import EmptyNode
from .code_node import FormatNode
from .code_node import ListNode
from .code_node import SequenceNode
from .code_node import SymbolNode
from .code_node import SymbolScopeNode
from .code_node import TextNode
from .code_node import Likeliness
from .code_node_cxx import CxxClassDefNode
from .code_node_cxx import CxxForLoopNode
from .code_node_cxx import CxxFuncDeclNode
from .code_node_cxx import CxxFuncDefNode
from .code_node_cxx import CxxIfElseNode
from .code_node_cxx import CxxLikelyIfNode
from .code_node_cxx import CxxNamespaceNode
from .code_node_cxx import CxxUnlikelyIfNode
from .codegen_accumulator import CodeGenAccumulator
from .codegen_context import CodeGenContext
from .codegen_utils import collect_forward_decls_and_include_headers
from .codegen_utils import component_export
from .codegen_utils import component_export_header
from .codegen_utils import enclose_with_header_guard
from .codegen_utils import make_copyright_header
from .codegen_utils import make_forward_declarations
from .codegen_utils import make_header_include_directives
from .codegen_utils import write_code_node_to_file
from .mako_renderer import MakoRenderer
from .package_initializer import package_initializer
from .path_manager import PathManager
from .task_queue import TaskQueue
def bind_local_vars(code_node, cg_context, is_construct_call=False):
assert isinstance(code_node, SymbolScopeNode)
assert isinstance(cg_context, CodeGenContext)
assert isinstance(is_construct_call, bool)
S = SymbolNode
local_vars = []
local_vars.extend([
S("exception_state", ("ExceptionState ${exception_state}("
"${isolate}, v8::ExceptionContext::kOperation,"
"${class_like_name}, ${property_name});")),
S("isolate", "v8::Isolate* ${isolate} = GetIsolate();"),
S("script_state",
"ScriptState* ${script_state} = CallbackRelevantScriptState();"),
])
if cg_context.callback_function:
local_vars.append(
S("class_like_name", ("const char* const ${class_like_name} = "
"\"${callback_function.identifier}\";")))
if is_construct_call:
local_vars.append(
S("property_name",
"const char* const ${property_name} = \"construct\";"))
else:
local_vars.append(
S("property_name",
"const char* const ${property_name} = \"invoke\";"))
elif cg_context.callback_interface:
local_vars.extend([
S("class_like_name", ("const char* const ${class_like_name} = "
"\"${callback_interface.identifier}\";")),
S("property_name", ("const char* const ${property_name} = "
"\"${property.identifier}\";")),
])
code_node.register_code_symbols(local_vars)
def _make_arg_type_and_names(func_like):
assert isinstance(func_like, web_idl.FunctionLike)
arg_type_and_names = []
for index, argument in enumerate(func_like.arguments):
type_info = blink_type_info(argument.idl_type)
if argument.idl_type.unwrap(variadic=False).is_interface:
arg_type = type_info.ref_t
else:
arg_type = type_info.const_ref_t
arg_name = name_style.arg_f("arg{}_{}", index + 1, argument.identifier)
arg_type_and_names.append((arg_type, arg_name))
return arg_type_and_names
def make_factory_methods(cg_context):
assert isinstance(cg_context, CodeGenContext)
func_def = CxxFuncDefNode(
name="Create",
arg_decls=["v8::Local<v8::Object> callback_object"],
return_type="${class_name}*",
static=True)
func_def.set_base_template_vars(cg_context.template_bindings())
func_def.body.append(
TextNode("return MakeGarbageCollected<${class_name}>("
"callback_object);"))
return func_def, None
def make_constructors(cg_context):
assert isinstance(cg_context, CodeGenContext)
decls = ListNode([
CxxFuncDefNode(name=cg_context.class_name,
arg_decls=["v8::Local<v8::Object> callback_object"],
return_type="",
explicit=True,
member_initializer_list=[
"${base_class_name}(callback_object)",
]),
CxxFuncDeclNode(name="~${class_name}",
arg_decls=[],
return_type="",
override=True,
default=True),
])
return decls, None
def make_nameclient_implementation(cg_context):
assert isinstance(cg_context, CodeGenContext)
func_decl = CxxFuncDeclNode(name="NameInHeapSnapshot",
arg_decls=[],
return_type="const char*",
const=True,
override=True)
func_def = CxxFuncDefNode(name="NameInHeapSnapshot",
arg_decls=[],
return_type="const char*",
class_name=cg_context.class_name,
const=True)
func_def.set_base_template_vars(cg_context.template_bindings())
func_def.body.append(TextNode("return \"${class_name}\";"))
return func_decl, func_def
def make_callback_invocation_function(cg_context,
function_name,
skip_runnability_check=False,
is_construct_call=False):
assert isinstance(cg_context, CodeGenContext)
assert isinstance(function_name, str)
assert isinstance(skip_runnability_check, bool)
assert isinstance(is_construct_call, bool)
T = TextNode
F = FormatNode
func_like = cg_context.function_like
return_type = ("void" if func_like.return_type.unwrap().is_undefined else
blink_type_info(func_like.return_type).value_t)
maybe_return_type = "v8::Maybe<{}>".format(return_type)
arg_type_and_names = _make_arg_type_and_names(func_like)
arg_decls = [
"{} {}".format(arg_type, arg_name)
for arg_type, arg_name in arg_type_and_names
]
if not is_construct_call:
arg_decls.insert(
0, "bindings::V8ValueOrScriptWrappableAdapter arg0_receiver")
decls = SequenceNode()
defs = SequenceNode()
func_decl = CxxFuncDeclNode(name=function_name,
arg_decls=arg_decls,
return_type=maybe_return_type,
nodiscard=True)
if cg_context.callback_function:
if is_construct_call:
comment = T("""\
// Performs "construct".
// https://webidl.spec.whatwg.org/#construct-a-callback-function\
""")
else:
comment = T("""\
// Performs "invoke".
// https://webidl.spec.whatwg.org/#invoke-a-callback-function\
""")
elif cg_context.callback_interface:
comment = T("""\
// Performs "call a user object's operation".
// https://webidl.spec.whatwg.org/#call-a-user-objects-operation\
""")
decls.extend([
comment,
func_decl,
])
func_def = CxxFuncDefNode(name=function_name,
arg_decls=arg_decls,
return_type=maybe_return_type,
class_name=cg_context.class_name)
func_def.set_base_template_vars(cg_context.template_bindings())
defs.append(func_def)
body = func_def.body
if is_construct_call:
body.add_template_var("arg0_receiver", "nullptr")
else:
body.add_template_var("arg0_receiver", "arg0_receiver")
for arg_type, arg_name in arg_type_and_names:
body.add_template_var(arg_name, arg_name)
bind_local_vars(body, cg_context, is_construct_call)
if func_like.return_type.unwrap(typedef=True).is_undefined:
text = "v8::JustVoid()"
else:
text = "helper.Result<{}, {}>()".format(
native_value_tag(func_like.return_type), return_type)
body.add_template_var("return_value_on_success", text)
text = "v8::Nothing<{}>()".format(return_type)
body.add_template_var("return_value_on_failure", text)
body.append(
T("""\
ScriptState* callback_relevant_script_state =
CallbackRelevantScriptStateOrThrowException(
${class_like_name}, ${property_name});
if (!callback_relevant_script_state) {
return ${return_value_on_failure};
}
"""))
if not skip_runnability_check:
body.extend([
CxxUnlikelyIfNode(
cond=("!IsCallbackFunctionRunnable("
"callback_relevant_script_state, "
"IncumbentScriptState())"),
attribute=None,
body=[
T("v8::HandleScope handle_scope(${isolate});"),
T("v8::Context::Scope context_scope("
"callback_relevant_script_state->GetContext());"),
T("${exception_state}.ThrowException("
"static_cast<ExceptionCode>(ESErrorType::kError), "
"\"The provided callback is no longer runnable.\");"),
T("return ${return_value_on_failure};"),
]),
EmptyNode(),
])
if cg_context.callback_function:
template_params = ["${base_class_name}"]
if is_construct_call:
template_params.append(
"bindings::CallbackInvokeHelperMode::kConstructorCall")
elif "LegacyTreatNonObjectAsNull" in func_like.extended_attributes:
template_params.append(
"bindings::"
"CallbackInvokeHelperMode::kLegacyTreatNonObjectAsNull")
else:
template_params.append(
"bindings::CallbackInvokeHelperMode::kDefault")
if func_like.return_type.unwrap(typedef=True).is_promise:
template_params.append(
"bindings::CallbackReturnTypeIsPromise::kYes")
else:
template_params.append(
"bindings::CallbackReturnTypeIsPromise::kNo")
elif cg_context.callback_interface:
template_params = ["CallbackInterfaceBase"]
body.extend([
F("""\
bindings::CallbackInvokeHelper<{template_params}> helper(
this, ${class_like_name}, ${property_name});\
""",
template_params=", ".join(template_params)),
CxxUnlikelyIfNode(cond="!helper.PrepareForCall(${arg0_receiver})",
attribute="[[unlikely]]",
body=[
CxxLikelyIfNode(
cond="helper.V8Result().IsEmpty()",
attribute=None,
body=[
T("return ${return_value_on_failure};"),
]),
T("return ${return_value_on_success};"),
]),
])
# The maximum number of arguments to a variadic function that we're willing
# to allocate on the stack. If the function takes more, we'll use the heap
# instead.
max_stack_array_length = 10
arguments = func_like.arguments
is_variadic = arguments and arguments[-1].is_variadic
if is_variadic:
_, variadic_arg_name = arg_type_and_names[-1]
# The next step in the C++ code is to define an array containing all
# the arguments, and its length argc. Depending on how many args we have,
# argv will either be a regular array or a LocalVector. To hide the
# difference, we unify both of them into a single span type
if not arguments:
body.append(T("base::span<v8::Local<v8::Value>> argv;"))
elif not is_variadic:
body.append(
T("v8::Local<v8::Value> argv_arr[{}];".format(len(arguments))))
body.append(T("base::span<v8::Local<v8::Value>> argv(argv_arr);"))
else:
# Forward declare both possible representations of argv so they're in
# scope for the rest of the function. We use a span to hide the
# difference from the type system.
body.append(
T("v8::Local<v8::Value> argv_arr[{}];".format(
max_stack_array_length))),
body.append(T("v8::LocalVector<v8::Value> argv_vec(GetIsolate());")),
body.append(T("base::span<v8::Local<v8::Value>> argv;")),
body.append(
T("const size_t argc = {} + {}.size();".format(
len(arguments) - 1, variadic_arg_name)))
body.append(
CxxIfElseNode(
cond=T("argc <= {}".format(max_stack_array_length)),
attribute=None,
# If argc is small, just use argv-arr
then=SymbolScopeNode(
code_nodes=[T("argv = base::make_span(argv_arr, argc);")]),
then_likeliness=Likeliness.LIKELY,
# If argc is large, create a vector instead
else_=SymbolScopeNode(code_nodes=[
T("argv_vec.resize(argc);"),
T("argv = argv_vec;"),
]),
else_likeliness=Likeliness.UNLIKELY))
for index, arg_type_and_name in enumerate(arg_type_and_names):
if arguments[index].is_variadic:
break
_, arg_name = arg_type_and_name
v8_arg_name = name_style.local_var_f("v8_arg{}_{}", index + 1,
arguments[index].identifier)
body.register_code_symbol(
make_blink_to_v8_value(v8_arg_name,
arg_name,
arguments[index].idl_type,
argument=arguments[index],
error_exit_return_statement=(
"return ${return_value_on_failure};")))
body.append(
F("argv[{index}] = ${{{v8_arg}}};",
index=index,
v8_arg=v8_arg_name))
if is_variadic:
v8_arg_name = name_style.local_var_f("v8_arg{}_{}", len(arguments),
arguments[-1].identifier)
body.register_code_symbol(
make_blink_to_v8_value(
v8_arg_name,
"{}[i]".format(variadic_arg_name),
arguments[-1].idl_type.unwrap(variadic=True),
argument=arguments[-1],
error_exit_return_statement=(
"return ${return_value_on_failure};")))
body.append(
CxxForLoopNode(
cond=F("wtf_size_t i = 0; i < {var_arg}.size(); ++i",
var_arg=variadic_arg_name),
body=[
F("argv[{non_var_arg_size} + i] = ${{{v8_arg}}};",
non_var_arg_size=len(arguments) - 1,
v8_arg=v8_arg_name),
],
weak_dep_syms=["isolate", "script_state"]))
body.extend([
CxxUnlikelyIfNode(
cond="!helper.Call(static_cast<int>(argv.size()), argv.data())",
attribute=None,
body=[
T("return ${return_value_on_failure};"),
]),
T("return ${return_value_on_success};"),
])
return decls, defs
def make_invoke_and_report_function(cg_context, function_name, api_func_name):
assert isinstance(cg_context, CodeGenContext)
assert isinstance(function_name, str)
assert isinstance(api_func_name, str)
T = TextNode
F = FormatNode
func_like = cg_context.function_like
if not (func_like.return_type.unwrap().is_undefined
or func_like.identifier == "Function"):
return None, None
arg_type_and_names = _make_arg_type_and_names(func_like)
arg_decls = ["bindings::V8ValueOrScriptWrappableAdapter arg0_receiver"] + [
"{} {}".format(arg_type, arg_name)
for arg_type, arg_name in arg_type_and_names
]
decls = SequenceNode()
defs = SequenceNode()
func_decl = CxxFuncDeclNode(name=function_name,
arg_decls=arg_decls,
return_type="void")
if cg_context.callback_function:
comment = T("""\
// Performs "invoke" and then reports an exception if any to the global
// error handler such as DevTools console.\
""")
elif cg_context.callback_interface:
comment = T("""\
// Performs "call a user object's operation" and then reports an exception
// if any to the global error handler such as DevTools console.\
""")
decls.extend([
comment,
func_decl,
])
func_def = CxxFuncDefNode(name=function_name,
arg_decls=arg_decls,
return_type="void",
class_name=cg_context.class_name)
func_def.set_base_template_vars(cg_context.template_bindings())
defs.append(func_def)
body = func_def.body
bind_local_vars(body, cg_context)
arg_names = ["arg0_receiver"
] + [arg_name for arg_type, arg_name in arg_type_and_names]
body.extend([
T("v8::TryCatch try_catch(${isolate});"),
T("try_catch.SetVerbose(true);"),
EmptyNode(),
F("std::ignore = {api_func_name}({arg_names});",
api_func_name=api_func_name,
arg_names=", ".join(arg_names)),
])
return decls, defs
def make_is_runnable_or_throw_exception(cg_context, function_name):
assert isinstance(cg_context, CodeGenContext)
assert isinstance(function_name, str)
T = TextNode
decls = SequenceNode()
defs = SequenceNode()
ignore_pause_def = ListNode([
T("enum class IgnorePause {"),
T(" kDontIgnore,"),
T(" kIgnore,"),
T("};"),
])
func_decl = CxxFuncDeclNode(name=function_name,
arg_decls=["IgnorePause ignore_pause"],
return_type="bool")
decls.extend([
T("""\
// Returns true if the callback is runnable, otherwise returns false and
// throws an exception.\
"""),
ignore_pause_def,
func_decl,
])
func_def = CxxFuncDefNode(name=function_name,
arg_decls=["IgnorePause ignore_pause"],
return_type="bool",
class_name=cg_context.class_name)
func_def.set_base_template_vars(cg_context.template_bindings())
defs.append(func_def)
body = func_def.body
body.add_template_var("ignore_pause", "ignore_pause")
bind_local_vars(body, cg_context)
body.extend([
T("""\
ScriptState* callback_relevant_script_state = CallbackRelevantScriptState();
const bool is_runnable =
ignore_pause == IgnorePause::kIgnore
? IsCallbackFunctionRunnableIgnoringPause(
callback_relevant_script_state, IncumbentScriptState())
: IsCallbackFunctionRunnable(
callback_relevant_script_state, IncumbentScriptState());
if (is_runnable)
return true;
"""),
T("ScriptState::Scope scope(callback_relevant_script_state);"),
T("""\
${exception_state}.ThrowException(
static_cast<ExceptionCode>(ESErrorType::kError),
"The provided callback is no longer runnable.");
return false;\
"""),
])
return decls, defs
def generate_callback_function(callback_function_identifier):
assert isinstance(callback_function_identifier, web_idl.Identifier)
web_idl_database = package_initializer().web_idl_database()
callback_function = web_idl_database.find(callback_function_identifier)
path_manager = PathManager(callback_function)
assert path_manager.api_component == path_manager.impl_component
api_component = path_manager.api_component
for_testing = callback_function.code_generator_info.for_testing
# Class names
class_name = blink_class_name(callback_function)
if "SupportsTaskAttribution" in callback_function.extended_attributes:
base_class_name = "CallbackFunctionWithTaskAttributionBase"
else:
base_class_name = "CallbackFunctionBase"
cg_context = CodeGenContext(callback_function=callback_function,
class_name=class_name,
base_class_name=base_class_name)
# Filepaths
header_path = path_manager.api_path(ext="h")
source_path = path_manager.api_path(ext="cc")
# Root nodes
header_node = ListNode(tail="\n")
header_node.set_accumulator(CodeGenAccumulator())
header_node.set_renderer(MakoRenderer())
source_node = ListNode(tail="\n")
source_node.set_accumulator(CodeGenAccumulator())
source_node.set_renderer(MakoRenderer())
# Namespaces
header_blink_ns = CxxNamespaceNode(name_style.namespace("blink"))
source_blink_ns = CxxNamespaceNode(name_style.namespace("blink"))
# Class definition
class_def = CxxClassDefNode(cg_context.class_name,
base_class_names=[base_class_name],
final=True,
export=component_export(
api_component, for_testing))
class_def.set_base_template_vars(cg_context.template_bindings())
# Implementation parts
factory_decls, factory_defs = make_factory_methods(cg_context)
ctor_decls, ctor_defs = make_constructors(cg_context)
nameclient_decls, nameclient_defs = make_nameclient_implementation(
cg_context)
cgc = cg_context.make_copy(callback_function=callback_function)
invoke_decls, invoke_defs = make_callback_invocation_function(
cgc, name_style.func("Invoke"), is_construct_call=False)
construct_decls, construct_defs = make_callback_invocation_function(
cgc, name_style.func("Construct"), is_construct_call=True)
(invoke_and_report_decls,
invoke_and_report_defs) = make_invoke_and_report_function(
cgc, name_style.func("InvokeAndReportException"),
name_style.func("Invoke"))
event_handler_decls, event_handler_defs = None, None
if callback_function.identifier == "EventHandlerNonNull":
event_handler_decls = SequenceNode()
event_handler_defs = SequenceNode()
(decls, defs) = make_is_runnable_or_throw_exception(
cgc, name_style.func("IsRunnableOrThrowException"))
event_handler_decls.append(decls)
event_handler_defs.append(defs)
event_handler_decls.append(EmptyNode())
event_handler_defs.append(EmptyNode())
(decls, defs) = make_callback_invocation_function(
cgc,
name_style.func("InvokeWithoutRunnabilityCheck"),
skip_runnability_check=True)
event_handler_decls.append(decls)
event_handler_defs.append(defs)
# Header part (copyright, include directives, and forward declarations)
header_node.extend([
make_copyright_header(),
EmptyNode(),
enclose_with_header_guard(
ListNode([
make_header_include_directives(header_node.accumulator),
EmptyNode(),
header_blink_ns,
]), name_style.header_guard(header_path)),
])
header_blink_ns.body.extend([
make_forward_declarations(header_node.accumulator),
EmptyNode(),
])
source_node.extend([
make_copyright_header(),
EmptyNode(),
TextNode("#include \"{}\"".format(header_path)),
EmptyNode(),
make_header_include_directives(source_node.accumulator),
EmptyNode(),
source_blink_ns,
])
source_blink_ns.body.extend([
make_forward_declarations(source_node.accumulator),
EmptyNode(),
])
# Assemble the parts.
header_node.accumulator.add_include_headers([
component_export_header(api_component, for_testing),
"third_party/blink/renderer/platform/bindings/callback_function_base.h",
"third_party/blink/renderer/platform/bindings/v8_value_or_script_wrappable_adapter.h",
])
source_node.accumulator.add_stdcpp_include_headers([
"tuple",
])
source_node.accumulator.add_include_headers([
"third_party/blink/renderer/bindings/core/v8/callback_invoke_helper.h",
"third_party/blink/renderer/bindings/core/v8/generated_code_helper.h",
"third_party/blink/renderer/bindings/core/v8/to_v8_traits.h",
"base/containers/span.h",
])
(
header_forward_decls,
header_include_headers,
header_stdcpp_include_headers,
source_forward_decls,
source_include_headers,
) = collect_forward_decls_and_include_headers(
[callback_function.return_type] + list(
map(lambda argument: argument.idl_type,
callback_function.arguments)))
header_node.accumulator.add_class_decls(header_forward_decls)
header_node.accumulator.add_include_headers(header_include_headers)
header_node.accumulator.add_stdcpp_include_headers(
header_stdcpp_include_headers)
source_node.accumulator.add_class_decls(source_forward_decls)
source_node.accumulator.add_include_headers(source_include_headers)
header_blink_ns.body.append(class_def)
header_blink_ns.body.append(EmptyNode())
class_def.public_section.append(factory_decls)
class_def.public_section.append(EmptyNode())
source_blink_ns.body.append(factory_defs)
source_blink_ns.body.append(EmptyNode())
class_def.public_section.append(ctor_decls)
class_def.public_section.append(EmptyNode())
source_blink_ns.body.append(ctor_defs)
source_blink_ns.body.append(EmptyNode())
class_def.public_section.append(TextNode("// NameClient overrides:"))
class_def.public_section.append(nameclient_decls)
class_def.public_section.append(EmptyNode())
source_blink_ns.body.append(nameclient_defs)
source_blink_ns.body.append(EmptyNode())
class_def.public_section.append(invoke_decls)
class_def.public_section.append(EmptyNode())
source_blink_ns.body.append(invoke_defs)
source_blink_ns.body.append(EmptyNode())
class_def.public_section.append(construct_decls)
class_def.public_section.append(EmptyNode())
source_blink_ns.body.append(construct_defs)
source_blink_ns.body.append(EmptyNode())
class_def.public_section.append(invoke_and_report_decls)
class_def.public_section.append(EmptyNode())
source_blink_ns.body.append(invoke_and_report_defs)
source_blink_ns.body.append(EmptyNode())
class_def.public_section.append(event_handler_decls)
class_def.public_section.append(EmptyNode())
source_blink_ns.body.append(event_handler_defs)
source_blink_ns.body.append(EmptyNode())
# Write down to the files.
write_code_node_to_file(header_node, path_manager.gen_path_to(header_path))
write_code_node_to_file(source_node, path_manager.gen_path_to(source_path))
def generate_callback_functions(task_queue):
assert isinstance(task_queue, TaskQueue)
web_idl_database = package_initializer().web_idl_database()
for callback_function in web_idl_database.callback_functions:
if callback_function.identifier in (
"OnErrorEventHandlerNonNull",
"OnBeforeUnloadEventHandlerNonNull"):
# OnErrorEventHandlerNonNull and OnBeforeUnloadEventHandlerNonNull
# are unified into EventHandlerNonNull, and they won't be used.
continue
task_queue.post_task(generate_callback_function,
callback_function.identifier)