blob: 5ea6550e116f86d444bcfc7bf26c96a32ea9e59a [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/bindings/core/v8/generated_code_helper.h"
#include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_css_style_declaration.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_element.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_set_return_value_for_core.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/range.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/html/custom/ce_reactions_scope.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/xml/dom_parser.h"
#include "third_party/blink/renderer/platform/bindings/v8_binding.h"
#include "third_party/blink/renderer/platform/bindings/v8_per_context_data.h"
namespace blink {
void V8ConstructorAttributeGetter(
v8::Local<v8::Name> property_name,
const v8::PropertyCallbackInfo<v8::Value>& info,
const WrapperTypeInfo* wrapper_type_info) {
RUNTIME_CALL_TIMER_SCOPE_DISABLED_BY_DEFAULT(
info.GetIsolate(), "Blink_V8ConstructorAttributeGetter");
V8PerContextData* per_context_data =
V8PerContextData::From(info.Holder()->CreationContext());
if (!per_context_data)
return;
V8SetReturnValue(info,
per_context_data->ConstructorForType(wrapper_type_info));
}
namespace {
enum class IgnorePause { kDontIgnore, kIgnore };
// 'beforeunload' event listeners are runnable even when execution contexts are
// paused. Use |RespectPause::kPrioritizeOverPause| in such a case.
bool IsCallbackFunctionRunnableInternal(
const ScriptState* callback_relevant_script_state,
const ScriptState* incumbent_script_state,
IgnorePause ignore_pause) {
if (!callback_relevant_script_state->ContextIsValid())
return false;
const ExecutionContext* relevant_execution_context =
ExecutionContext::From(callback_relevant_script_state);
if (!relevant_execution_context ||
relevant_execution_context->IsContextDestroyed()) {
return false;
}
if (relevant_execution_context->IsContextPaused()) {
if (ignore_pause == IgnorePause::kDontIgnore)
return false;
}
// TODO(yukishiino): Callback function type value must make the incumbent
// environment alive, i.e. the reference to v8::Context must be strong.
v8::HandleScope handle_scope(incumbent_script_state->GetIsolate());
v8::Local<v8::Context> incumbent_context =
incumbent_script_state->GetContext();
ExecutionContext* incumbent_execution_context =
incumbent_context.IsEmpty() ? nullptr
: ToExecutionContext(incumbent_context);
// The incumbent realm schedules the currently-running callback although it
// may not correspond to the currently-running function object. So we check
// the incumbent context which originally schedules the currently-running
// callback to see whether the script setting is disabled before invoking
// the callback.
// TODO(crbug.com/608641): move IsMainWorld check into
// ExecutionContext::CanExecuteScripts()
if (!incumbent_execution_context ||
incumbent_execution_context->IsContextDestroyed()) {
return false;
}
if (incumbent_execution_context->IsContextPaused()) {
if (ignore_pause == IgnorePause::kDontIgnore)
return false;
}
return !incumbent_script_state->World().IsMainWorld() ||
incumbent_execution_context->CanExecuteScripts(kAboutToExecuteScript);
}
} // namespace
bool IsCallbackFunctionRunnable(
const ScriptState* callback_relevant_script_state,
const ScriptState* incumbent_script_state) {
return IsCallbackFunctionRunnableInternal(callback_relevant_script_state,
incumbent_script_state,
IgnorePause::kDontIgnore);
}
bool IsCallbackFunctionRunnableIgnoringPause(
const ScriptState* callback_relevant_script_state,
const ScriptState* incumbent_script_state) {
return IsCallbackFunctionRunnableInternal(callback_relevant_script_state,
incumbent_script_state,
IgnorePause::kIgnore);
}
void V8SetReflectedBooleanAttribute(
const v8::FunctionCallbackInfo<v8::Value>& info,
const char* interface_name,
const char* idl_attribute_name,
const QualifiedName& content_attr) {
v8::Isolate* isolate = info.GetIsolate();
Element* impl = V8Element::ToImpl(info.Holder());
ExceptionState exception_state(isolate, ExceptionState::kSetterContext,
interface_name, idl_attribute_name);
CEReactionsScope ce_reactions_scope;
// Prepare the value to be set.
bool cpp_value = NativeValueTraits<IDLBoolean>::NativeValue(isolate, info[0],
exception_state);
if (exception_state.HadException())
return;
impl->SetBooleanAttribute(content_attr, cpp_value);
}
void V8SetReflectedDOMStringAttribute(
const v8::FunctionCallbackInfo<v8::Value>& info,
const QualifiedName& content_attr) {
Element* impl = V8Element::ToImpl(info.Holder());
CEReactionsScope ce_reactions_scope;
// Prepare the value to be set.
V8StringResource<> cpp_value{info[0]};
if (!cpp_value.Prepare())
return;
impl->setAttribute(content_attr, cpp_value);
}
void V8SetReflectedNullableDOMStringAttribute(
const v8::FunctionCallbackInfo<v8::Value>& info,
const QualifiedName& content_attr) {
Element* impl = V8Element::ToImpl(info.Holder());
CEReactionsScope ce_reactions_scope;
// Prepare the value to be set.
V8StringResource<kTreatNullAndUndefinedAsNullString> cpp_value{info[0]};
if (!cpp_value.Prepare())
return;
impl->setAttribute(content_attr, cpp_value);
}
namespace bindings {
void SetupIDLInterfaceTemplate(
v8::Isolate* isolate,
const WrapperTypeInfo* wrapper_type_info,
v8::Local<v8::ObjectTemplate> instance_template,
v8::Local<v8::ObjectTemplate> prototype_template,
v8::Local<v8::FunctionTemplate> interface_template,
v8::Local<v8::FunctionTemplate> parent_interface_template) {
v8::Local<v8::String> class_string =
V8AtomicString(isolate, wrapper_type_info->interface_name);
if (!parent_interface_template.IsEmpty())
interface_template->Inherit(parent_interface_template);
interface_template->ReadOnlyPrototype();
interface_template->SetClassName(class_string);
prototype_template->Set(
v8::Symbol::GetToStringTag(isolate), class_string,
static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontEnum));
instance_template->SetInternalFieldCount(kV8DefaultWrapperInternalFieldCount);
}
void SetupIDLNamespaceTemplate(
v8::Isolate* isolate,
const WrapperTypeInfo* wrapper_type_info,
v8::Local<v8::ObjectTemplate> interface_template) {
v8::Local<v8::String> class_string =
V8AtomicString(isolate, wrapper_type_info->interface_name);
interface_template->Set(
v8::Symbol::GetToStringTag(isolate), class_string,
static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontEnum));
}
void SetupIDLCallbackInterfaceTemplate(
v8::Isolate* isolate,
const WrapperTypeInfo* wrapper_type_info,
v8::Local<v8::FunctionTemplate> interface_template) {
interface_template->RemovePrototype();
interface_template->SetClassName(
V8AtomicString(isolate, wrapper_type_info->interface_name));
}
base::Optional<size_t> FindIndexInEnumStringTable(
v8::Isolate* isolate,
v8::Local<v8::Value> value,
base::span<const char* const> enum_value_table,
const char* enum_type_name,
ExceptionState& exception_state) {
const String& str_value = NativeValueTraits<IDLStringV2>::NativeValue(
isolate, value, exception_state);
if (exception_state.HadException())
return base::nullopt;
base::Optional<size_t> index =
FindIndexInEnumStringTable(str_value, enum_value_table);
if (!index.has_value()) {
exception_state.ThrowTypeError("The provided value '" + str_value +
"' is not a valid enum value of type " +
enum_type_name + ".");
}
return index;
}
base::Optional<size_t> FindIndexInEnumStringTable(
const String& str_value,
base::span<const char* const> enum_value_table) {
for (size_t i = 0; i < enum_value_table.size(); ++i) {
if (Equal(str_value.Impl(), enum_value_table[i]))
return i;
}
return base::nullopt;
}
void ReportInvalidEnumSetToAttribute(v8::Isolate* isolate,
const String& value,
const String& enum_type_name,
ExceptionState& exception_state) {
ScriptState* script_state = ScriptState::From(isolate->GetCurrentContext());
ExecutionContext* execution_context = ExecutionContext::From(script_state);
exception_state.ThrowTypeError("The provided value '" + value +
"' is not a valid enum value of type " +
enum_type_name + ".");
String message = exception_state.Message();
exception_state.ClearException();
execution_context->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kJavaScript,
mojom::blink::ConsoleMessageLevel::kWarning, message,
SourceLocation::Capture(execution_context)));
}
bool IsEsIterableObject(v8::Isolate* isolate,
v8::Local<v8::Value> value,
ExceptionState& exception_state) {
// https://heycam.github.io/webidl/#es-overloads
// step 9. Otherwise: if Type(V) is Object and ...
if (!value->IsObject())
return false;
// step 9.1. Let method be ? GetMethod(V, @@iterator).
// https://tc39.es/ecma262/#sec-getmethod
v8::TryCatch try_catch(isolate);
v8::Local<v8::Value> iterator_key = v8::Symbol::GetIterator(isolate);
v8::Local<v8::Value> iterator_value;
if (!value.As<v8::Object>()
->Get(isolate->GetCurrentContext(), iterator_key)
.ToLocal(&iterator_value)) {
exception_state.RethrowV8Exception(try_catch.Exception());
return false;
}
if (iterator_value->IsNullOrUndefined())
return false;
if (!iterator_value->IsFunction()) {
exception_state.ThrowTypeError("@@iterator must be a function");
return false;
}
return true;
}
Document* ToDocumentFromExecutionContext(ExecutionContext* execution_context) {
return DynamicTo<LocalDOMWindow>(execution_context)->document();
}
ExecutionContext* ExecutionContextFromV8Wrappable(const Range* range) {
return range->startContainer()->GetExecutionContext();
}
ExecutionContext* ExecutionContextFromV8Wrappable(const DOMParser* parser) {
return parser->GetWindow();
}
v8::MaybeLocal<v8::Value> CreateNamedConstructorFunction(
ScriptState* script_state,
v8::FunctionCallback callback,
const char* func_name,
int func_length,
const WrapperTypeInfo* wrapper_type_info) {
v8::Isolate* isolate = script_state->GetIsolate();
const DOMWrapperWorld& world = script_state->World();
V8PerIsolateData* per_isolate_data = V8PerIsolateData::From(isolate);
const void* callback_key = reinterpret_cast<const void*>(callback);
if (!script_state->ContextIsValid()) {
return v8::Undefined(isolate);
}
v8::Local<v8::FunctionTemplate> function_template =
per_isolate_data->FindV8Template(world, callback_key)
.As<v8::FunctionTemplate>();
if (function_template.IsEmpty()) {
function_template = v8::FunctionTemplate::New(
isolate, callback, v8::Local<v8::Value>(), v8::Local<v8::Signature>(),
func_length, v8::ConstructorBehavior::kAllow,
v8::SideEffectType::kHasSideEffect);
v8::Local<v8::FunctionTemplate> interface_template =
wrapper_type_info->GetV8ClassTemplate(isolate, world)
.As<v8::FunctionTemplate>();
function_template->Inherit(interface_template);
function_template->SetClassName(V8AtomicString(isolate, func_name));
function_template->InstanceTemplate()->SetInternalFieldCount(
kV8DefaultWrapperInternalFieldCount);
per_isolate_data->AddV8Template(world, callback_key, function_template);
}
v8::Local<v8::Context> context = script_state->GetContext();
V8PerContextData* per_context_data = V8PerContextData::From(context);
v8::Local<v8::Function> function;
if (!function_template->GetFunction(context).ToLocal(&function)) {
return v8::MaybeLocal<v8::Value>();
}
v8::Local<v8::Object> prototype_object =
per_context_data->PrototypeForType(wrapper_type_info);
bool did_define;
if (!function
->DefineOwnProperty(
context, V8AtomicString(isolate, "prototype"), prototype_object,
static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontEnum |
v8::DontDelete))
.To(&did_define)) {
return v8::MaybeLocal<v8::Value>();
}
CHECK(did_define);
return function;
}
void InstallUnscopablePropertyNames(
v8::Isolate* isolate,
v8::Local<v8::Context> context,
v8::Local<v8::Object> prototype_object,
base::span<const char* const> property_name_table) {
// 3.6.3. Interface prototype object
// https://heycam.github.io/webidl/#interface-prototype-object
// step 8. If interface has any member declared with the [Unscopable]
// extended attribute, then:
// step 8.1. Let unscopableObject be the result of performing
// ! ObjectCreate(null).
v8::Local<v8::Object> unscopable_object =
v8::Object::New(isolate, v8::Null(isolate), nullptr, nullptr, 0);
for (const char* const property_name : property_name_table) {
// step 8.2.2. Perform ! CreateDataProperty(unscopableObject, id, true).
unscopable_object
->CreateDataProperty(context, V8AtomicString(isolate, property_name),
v8::True(isolate))
.ToChecked();
}
// step 8.3. Let desc be the PropertyDescriptor{[[Value]]: unscopableObject,
// [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true}.
// step 8.4. Perform ! DefinePropertyOrThrow(interfaceProtoObj,
// @@unscopables, desc).
prototype_object
->DefineOwnProperty(
context, v8::Symbol::GetUnscopables(isolate), unscopable_object,
static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontEnum))
.ToChecked();
}
v8::Local<v8::Array> EnumerateIndexedProperties(v8::Isolate* isolate,
uint32_t length) {
Vector<v8::Local<v8::Value>> elements;
elements.ReserveCapacity(length);
for (uint32_t i = 0; i < length; ++i)
elements.UncheckedAppend(v8::Integer::New(isolate, i));
return v8::Array::New(isolate, elements.data(), elements.size());
}
#if defined(USE_BLINK_V8_BINDING_NEW_IDL_INTERFACE)
void InstallCSSPropertyAttributes(
v8::Isolate* isolate,
const DOMWrapperWorld& world,
v8::Local<v8::Template> instance_template,
v8::Local<v8::Template> prototype_template,
v8::Local<v8::Template> interface_template,
v8::Local<v8::Signature> signature,
base::span<const char* const> css_property_names) {
const String kGetPrefix = "get ";
const String kSetPrefix = "set ";
for (const char* const property_name : css_property_names) {
v8::Local<v8::Value> v8_property_name = v8::External::New(
isolate,
const_cast<void*>(reinterpret_cast<const void*>(property_name)));
v8::Local<v8::FunctionTemplate> get_func = v8::FunctionTemplate::New(
isolate, CSSPropertyAttributeGet, v8_property_name, signature, 0,
v8::ConstructorBehavior::kThrow, v8::SideEffectType::kHasNoSideEffect);
v8::Local<v8::FunctionTemplate> set_func = v8::FunctionTemplate::New(
isolate, CSSPropertyAttributeSet, v8_property_name, signature, 1,
v8::ConstructorBehavior::kThrow, v8::SideEffectType::kHasSideEffect);
get_func->SetAcceptAnyReceiver(false);
set_func->SetAcceptAnyReceiver(false);
get_func->SetClassName(
V8AtomicString(isolate, String(kGetPrefix + property_name)));
set_func->SetClassName(
V8AtomicString(isolate, String(kSetPrefix + property_name)));
prototype_template->SetAccessorProperty(
V8AtomicString(isolate, property_name), get_func, set_func);
}
}
void CSSPropertyAttributeGet(const v8::FunctionCallbackInfo<v8::Value>& info) {
CSSStyleDeclaration* blink_receiver =
V8CSSStyleDeclaration::ToWrappableUnsafe(info.This());
const char* property_name =
reinterpret_cast<const char*>(info.Data().As<v8::External>()->Value());
// TODO(andruud): AnonymousNamedGetter is not the best function. Change the
// function to a more appropriate one.
auto&& return_value = blink_receiver->AnonymousNamedGetter(property_name);
bindings::V8SetReturnValue(info, return_value, info.GetIsolate(),
bindings::V8ReturnValue::kNonNullable);
}
void CSSPropertyAttributeSet(const v8::FunctionCallbackInfo<v8::Value>& info) {
v8::Isolate* isolate = info.GetIsolate();
const char* const class_like_name = "CSSStyleDeclaration";
const char* property_name =
reinterpret_cast<const char*>(info.Data().As<v8::External>()->Value());
ExceptionState exception_state(isolate, ExceptionState::kSetterContext,
class_like_name, property_name);
// [CEReactions]
CEReactionsScope ce_reactions_scope;
v8::Local<v8::Object> v8_receiver = info.This();
CSSStyleDeclaration* blink_receiver =
V8CSSStyleDeclaration::ToWrappableUnsafe(v8_receiver);
v8::Local<v8::Value> v8_property_value = info[0];
auto&& arg1_value =
NativeValueTraits<IDLStringTreatNullAsEmptyStringV2>::NativeValue(
isolate, v8_property_value, exception_state);
if (exception_state.HadException()) {
return;
}
v8::Local<v8::Context> receiver_context = v8_receiver->CreationContext();
ScriptState* receiver_script_state = ScriptState::From(receiver_context);
// TODO(andruud): AnonymousNamedSetter is not the best function. Change the
// function to a more appropriate one. It's better to pass |exception_state|
// as the implementation of AnonymousNamedSetter needs it.
blink_receiver->AnonymousNamedSetter(receiver_script_state, property_name,
arg1_value);
}
template <typename IDLType,
typename ArgType,
void (Element::*MemFunc)(const QualifiedName&, ArgType)>
void PerformAttributeSetCEReactionsReflect(
const v8::FunctionCallbackInfo<v8::Value>& info,
const QualifiedName& content_attribute,
const char* interface_name,
const char* attribute_name) {
v8::Isolate* isolate = info.GetIsolate();
ExceptionState exception_state(isolate, ExceptionState::kSetterContext,
interface_name, attribute_name);
if (UNLIKELY(info.Length() < 1)) {
exception_state.ThrowTypeError(
ExceptionMessages::NotEnoughArguments(1, info.Length()));
return;
}
CEReactionsScope ce_reactions_scope;
Element* blink_receiver = V8Element::ToWrappableUnsafe(info.This());
auto&& arg_value = NativeValueTraits<IDLType>::NativeValue(isolate, info[0],
exception_state);
if (exception_state.HadException())
return;
(blink_receiver->*MemFunc)(content_attribute, arg_value);
}
void PerformAttributeSetCEReactionsReflectTypeBoolean(
const v8::FunctionCallbackInfo<v8::Value>& info,
const QualifiedName& content_attribute,
const char* interface_name,
const char* attribute_name) {
PerformAttributeSetCEReactionsReflect<IDLBoolean, bool,
&Element::SetBooleanAttribute>(
info, content_attribute, interface_name, attribute_name);
}
void PerformAttributeSetCEReactionsReflectTypeString(
const v8::FunctionCallbackInfo<v8::Value>& info,
const QualifiedName& content_attribute,
const char* interface_name,
const char* attribute_name) {
PerformAttributeSetCEReactionsReflect<IDLStringV2, const AtomicString&,
&Element::setAttribute>(
info, content_attribute, interface_name, attribute_name);
}
void PerformAttributeSetCEReactionsReflectTypeStringLegacyNullToEmptyString(
const v8::FunctionCallbackInfo<v8::Value>& info,
const QualifiedName& content_attribute,
const char* interface_name,
const char* attribute_name) {
PerformAttributeSetCEReactionsReflect<IDLStringTreatNullAsEmptyStringV2,
const AtomicString&,
&Element::setAttribute>(
info, content_attribute, interface_name, attribute_name);
}
void PerformAttributeSetCEReactionsReflectTypeStringOrNull(
const v8::FunctionCallbackInfo<v8::Value>& info,
const QualifiedName& content_attribute,
const char* interface_name,
const char* attribute_name) {
PerformAttributeSetCEReactionsReflect<
IDLNullable<IDLStringV2>, const AtomicString&, &Element::setAttribute>(
info, content_attribute, interface_name, attribute_name);
}
#endif // USE_BLINK_V8_BINDING_NEW_IDL_INTERFACE
} // namespace bindings
} // namespace blink