blob: 1d5c406d2cb064e87f5433cf6a1ca91cf24b19d3 [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 "bindings/core/v8/ScriptCustomElementDefinitionBuilder.h"
#include "bindings/core/v8/DOMWrapperWorld.h"
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/ScriptCustomElementDefinition.h"
#include "bindings/core/v8/ScriptState.h"
#include "bindings/core/v8/ScriptValue.h"
#include "bindings/core/v8/V8Binding.h"
#include "bindings/core/v8/V8BindingMacros.h"
#include "core/dom/ExceptionCode.h"
namespace blink {
ScriptCustomElementDefinitionBuilder*
ScriptCustomElementDefinitionBuilder::s_stack = nullptr;
ScriptCustomElementDefinitionBuilder::ScriptCustomElementDefinitionBuilder(
ScriptState* scriptState,
CustomElementRegistry* registry,
const ScriptValue& constructor,
ExceptionState& exceptionState)
: m_prev(s_stack),
m_scriptState(scriptState),
m_registry(registry),
m_constructorValue(constructor.v8Value()),
m_exceptionState(exceptionState) {
s_stack = this;
}
ScriptCustomElementDefinitionBuilder::~ScriptCustomElementDefinitionBuilder() {
s_stack = m_prev;
}
bool ScriptCustomElementDefinitionBuilder::checkConstructorIntrinsics() {
DCHECK(m_scriptState->world().isMainWorld());
// The signature of CustomElementRegistry.define says this is a
// Function
// https://html.spec.whatwg.org/multipage/scripting.html#customelementsregistry
CHECK(m_constructorValue->IsFunction());
m_constructor = m_constructorValue.As<v8::Object>();
if (!m_constructor->IsConstructor()) {
m_exceptionState.throwTypeError(
"constructor argument is not a constructor");
return false;
}
return true;
}
bool ScriptCustomElementDefinitionBuilder::checkConstructorNotRegistered() {
if (ScriptCustomElementDefinition::forConstructor(
m_scriptState.get(), m_registry, m_constructor)) {
// Constructor is already registered.
m_exceptionState.throwDOMException(
NotSupportedError,
"this constructor has already been used with this registry");
return false;
}
for (auto builder = m_prev; builder; builder = builder->m_prev) {
CHECK(!builder->m_constructor.IsEmpty());
if (m_registry != builder->m_registry ||
m_constructor != builder->m_constructor) {
continue;
}
m_exceptionState.throwDOMException(
NotSupportedError,
"this constructor is already being defined in this registry");
return false;
}
return true;
}
bool ScriptCustomElementDefinitionBuilder::valueForName(
const v8::Local<v8::Object>& object,
const StringView& name,
v8::Local<v8::Value>& value) const {
v8::Isolate* isolate = m_scriptState->isolate();
v8::Local<v8::Context> context = m_scriptState->context();
v8::Local<v8::String> nameString = v8AtomicString(isolate, name);
v8::TryCatch tryCatch(isolate);
if (!v8Call(object->Get(context, nameString), value, tryCatch)) {
m_exceptionState.rethrowV8Exception(tryCatch.Exception());
return false;
}
return true;
}
bool ScriptCustomElementDefinitionBuilder::checkPrototype() {
v8::Local<v8::Value> prototypeValue;
if (!valueForName(m_constructor, "prototype", prototypeValue))
return false;
if (!prototypeValue->IsObject()) {
m_exceptionState.throwTypeError("constructor prototype is not an object");
return false;
}
m_prototype = prototypeValue.As<v8::Object>();
// If retrieving the prototype destroyed the context, indicate that
// defining the element should not proceed.
return true;
}
bool ScriptCustomElementDefinitionBuilder::callableForName(
const StringView& name,
v8::Local<v8::Function>& callback) const {
v8::Local<v8::Value> value;
if (!valueForName(m_prototype, name, value))
return false;
// "undefined" means "omitted", so return true.
if (value->IsUndefined())
return true;
if (!value->IsFunction()) {
m_exceptionState.throwTypeError(String::format(
"\"%s\" is not a callable object", name.toString().ascii().data()));
return false;
}
callback = value.As<v8::Function>();
return true;
}
bool ScriptCustomElementDefinitionBuilder::retrieveObservedAttributes() {
v8::Local<v8::Value> observedAttributesValue;
if (!valueForName(m_constructor, "observedAttributes",
observedAttributesValue))
return false;
if (observedAttributesValue->IsUndefined())
return true;
Vector<AtomicString> list = toImplSequence<Vector<AtomicString>>(
m_scriptState->isolate(), observedAttributesValue, m_exceptionState);
if (m_exceptionState.hadException())
return false;
if (list.isEmpty())
return true;
m_observedAttributes.reserveCapacityForSize(list.size());
for (const auto& attribute : list)
m_observedAttributes.add(attribute);
return true;
}
bool ScriptCustomElementDefinitionBuilder::rememberOriginalProperties() {
// Spec requires to use values of these properties at the point
// CustomElementDefinition is built, even if JS changes them afterwards.
return callableForName("connectedCallback", m_connectedCallback) &&
callableForName("disconnectedCallback", m_disconnectedCallback) &&
callableForName("adoptedCallback", m_adoptedCallback) &&
callableForName("attributeChangedCallback",
m_attributeChangedCallback) &&
(m_attributeChangedCallback.IsEmpty() || retrieveObservedAttributes());
}
CustomElementDefinition* ScriptCustomElementDefinitionBuilder::build(
const CustomElementDescriptor& descriptor) {
return ScriptCustomElementDefinition::create(
m_scriptState.get(), m_registry, descriptor, m_constructor, m_prototype,
m_connectedCallback, m_disconnectedCallback, m_adoptedCallback,
m_attributeChangedCallback, m_observedAttributes);
}
} // namespace blink