| // 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 |