blob: d5de71303a4b256a01d9b71bf1885b17c9b1b9ea [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/script_custom_element_definition.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_custom_element_registry.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_element.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_error_handler.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_script_runner.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/events/error_event.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/html/custom/custom_element.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/bindings/v8_binding_macros.h"
#include "third_party/blink/renderer/platform/bindings/v8_per_context_data.h"
#include "third_party/blink/renderer/platform/bindings/v8_private_property.h"
#include "third_party/blink/renderer/platform/wtf/allocator.h"
#include "v8/include/v8.h"
namespace blink {
class CSSStyleSheet;
ScriptCustomElementDefinition* ScriptCustomElementDefinition::ForConstructor(
ScriptState* script_state,
CustomElementRegistry* registry,
const v8::Local<v8::Value>& constructor) {
V8PerContextData* per_context_data = script_state->PerContextData();
// TODO(yukishiino): Remove this check when crbug.com/583429 is fixed.
if (UNLIKELY(!per_context_data))
return nullptr;
auto private_id = per_context_data->GetPrivateCustomElementDefinitionId();
v8::Local<v8::Value> id_value;
if (!constructor.As<v8::Object>()
->GetPrivate(script_state->GetContext(), private_id)
.ToLocal(&id_value))
return nullptr;
if (!id_value->IsUint32())
return nullptr;
uint32_t id = id_value.As<v8::Uint32>()->Value();
// This downcast is safe because only ScriptCustomElementDefinitions
// have an ID associated with them. This relies on three things:
//
// 1. Only ScriptCustomElementDefinition::Create sets the private
// property on a constructor.
//
// 2. CustomElementRegistry adds ScriptCustomElementDefinitions
// assigned an ID to the list of definitions without fail.
//
// 3. The relationship between the CustomElementRegistry and its
// private property is never mixed up; this is guaranteed by the
// bindings system because the registry is associated with its
// context.
//
// At a meta-level, this downcast is safe because there is
// currently only one implementation of CustomElementDefinition in
// product code and that is ScriptCustomElementDefinition. But
// that may change in the future.
CustomElementDefinition* definition = registry->DefinitionForId(id);
CHECK(definition);
return static_cast<ScriptCustomElementDefinition*>(definition);
}
ScriptCustomElementDefinition* ScriptCustomElementDefinition::Create(
ScriptState* script_state,
CustomElementRegistry* registry,
const CustomElementDescriptor& descriptor,
CustomElementDefinition::Id id,
const v8::Local<v8::Object>& constructor,
const v8::Local<v8::Function>& connected_callback,
const v8::Local<v8::Function>& disconnected_callback,
const v8::Local<v8::Function>& adopted_callback,
const v8::Local<v8::Function>& attribute_changed_callback,
HashSet<AtomicString>&& observed_attributes,
CSSStyleSheet* default_style_sheet) {
ScriptCustomElementDefinition* definition = new ScriptCustomElementDefinition(
script_state, descriptor, constructor, connected_callback,
disconnected_callback, adopted_callback, attribute_changed_callback,
std::move(observed_attributes), default_style_sheet);
// Tag the JavaScript constructor object with its ID.
v8::Local<v8::Value> id_value =
v8::Integer::NewFromUnsigned(script_state->GetIsolate(), id);
auto private_id =
script_state->PerContextData()->GetPrivateCustomElementDefinitionId();
CHECK(
constructor->SetPrivate(script_state->GetContext(), private_id, id_value)
.ToChecked());
return definition;
}
ScriptCustomElementDefinition::ScriptCustomElementDefinition(
ScriptState* script_state,
const CustomElementDescriptor& descriptor,
const v8::Local<v8::Object>& constructor,
const v8::Local<v8::Function>& connected_callback,
const v8::Local<v8::Function>& disconnected_callback,
const v8::Local<v8::Function>& adopted_callback,
const v8::Local<v8::Function>& attribute_changed_callback,
HashSet<AtomicString>&& observed_attributes,
CSSStyleSheet* default_style_sheet)
: CustomElementDefinition(descriptor,
default_style_sheet,
std::move(observed_attributes)),
script_state_(script_state),
constructor_(script_state->GetIsolate(), constructor) {
v8::Isolate* isolate = script_state->GetIsolate();
if (!connected_callback.IsEmpty())
connected_callback_.Set(isolate, connected_callback);
if (!disconnected_callback.IsEmpty())
disconnected_callback_.Set(isolate, disconnected_callback);
if (!adopted_callback.IsEmpty())
adopted_callback_.Set(isolate, adopted_callback);
if (!attribute_changed_callback.IsEmpty())
attribute_changed_callback_.Set(isolate, attribute_changed_callback);
}
void ScriptCustomElementDefinition::Trace(Visitor* visitor) {
visitor->Trace(constructor_.Cast<v8::Value>());
visitor->Trace(connected_callback_.Cast<v8::Value>());
visitor->Trace(disconnected_callback_.Cast<v8::Value>());
visitor->Trace(adopted_callback_.Cast<v8::Value>());
visitor->Trace(attribute_changed_callback_.Cast<v8::Value>());
CustomElementDefinition::Trace(visitor);
}
HTMLElement* ScriptCustomElementDefinition::HandleCreateElementSyncException(
Document& document,
const QualifiedName& tag_name,
v8::Isolate* isolate,
ExceptionState& exception_state) {
DCHECK(exception_state.HadException());
// 6.1."If any of these subsubsteps threw an exception".1
// Report the exception.
V8ScriptRunner::ReportException(isolate, exception_state.GetException());
exception_state.ClearException();
// ... .2 Return HTMLUnknownElement.
return CustomElement::CreateFailedElement(document, tag_name);
}
HTMLElement* ScriptCustomElementDefinition::CreateAutonomousCustomElementSync(
Document& document,
const QualifiedName& tag_name) {
if (!script_state_->ContextIsValid())
return CustomElement::CreateFailedElement(document, tag_name);
ScriptState::Scope scope(script_state_.get());
v8::Isolate* isolate = script_state_->GetIsolate();
ExceptionState exception_state(isolate, ExceptionState::kConstructionContext,
"CustomElement");
// Create an element with the synchronous custom elements flag set.
// https://dom.spec.whatwg.org/#concept-create-element
// TODO(dominicc): Implement step 5 which constructs customized
// built-in elements.
Element* element = nullptr;
{
v8::TryCatch try_catch(script_state_->GetIsolate());
if (document.IsHTMLImport()) {
// V8HTMLElement::constructorCustom() can only refer to
// window.document() which is not the import document. Create
// elements in import documents ahead of time so they end up in
// the right document. This subtly violates recursive
// construction semantics, but only in import documents.
element = CreateElementForConstructor(document);
DCHECK(!try_catch.HasCaught());
ConstructionStackScope construction_stack_scope(this, element);
element = CallConstructor();
} else {
element = CallConstructor();
}
if (try_catch.HasCaught()) {
exception_state.RethrowV8Exception(try_catch.Exception());
return HandleCreateElementSyncException(document, tag_name, isolate,
exception_state);
}
}
// 6.1.3. through 6.1.9.
CheckConstructorResult(element, document, tag_name, exception_state);
if (exception_state.HadException()) {
return HandleCreateElementSyncException(document, tag_name, isolate,
exception_state);
}
// 6.1.10. Set result’s namespace prefix to prefix.
if (element->prefix() != tag_name.Prefix())
element->SetTagNameForCreateElementNS(tag_name);
DCHECK_EQ(element->GetCustomElementState(), CustomElementState::kCustom);
return ToHTMLElement(element);
}
// https://html.spec.whatwg.org/multipage/scripting.html#upgrades
bool ScriptCustomElementDefinition::RunConstructor(Element* element) {
if (!script_state_->ContextIsValid())
return false;
ScriptState::Scope scope(script_state_.get());
v8::Isolate* isolate = script_state_->GetIsolate();
// Step 5 says to rethrow the exception; but there is no one to
// catch it. The side effect is to report the error.
v8::TryCatch try_catch(isolate);
try_catch.SetVerbose(true);
Element* result = CallConstructor();
// To report exception thrown from callConstructor()
if (try_catch.HasCaught())
return false;
// To report InvalidStateError Exception, when the constructor returns some
// different object
if (result != element) {
const String& message =
"custom element constructors must call super() first and must "
"not return a different object";
v8::Local<v8::Value> exception = V8ThrowDOMException::CreateOrEmpty(
script_state_->GetIsolate(), DOMExceptionCode::kInvalidStateError,
message);
if (!exception.IsEmpty())
V8ScriptRunner::ReportException(isolate, exception);
return false;
}
return true;
}
Element* ScriptCustomElementDefinition::CallConstructor() {
v8::Isolate* isolate = script_state_->GetIsolate();
DCHECK(ScriptState::Current(isolate) == script_state_);
ExecutionContext* execution_context =
ExecutionContext::From(script_state_.get());
v8::Local<v8::Value> result;
if (!V8ScriptRunner::CallAsConstructor(isolate, Constructor(),
execution_context, 0, nullptr)
.ToLocal(&result)) {
return nullptr;
}
return V8Element::ToImplWithTypeCheck(isolate, result);
}
v8::Local<v8::Object> ScriptCustomElementDefinition::Constructor() const {
DCHECK(!constructor_.IsEmpty());
return constructor_.NewLocal(script_state_->GetIsolate());
}
// CustomElementDefinition
ScriptValue ScriptCustomElementDefinition::GetConstructorForScript() {
return ScriptValue(script_state_.get(), Constructor());
}
bool ScriptCustomElementDefinition::HasConnectedCallback() const {
return !connected_callback_.IsEmpty();
}
bool ScriptCustomElementDefinition::HasDisconnectedCallback() const {
return !disconnected_callback_.IsEmpty();
}
bool ScriptCustomElementDefinition::HasAdoptedCallback() const {
return !adopted_callback_.IsEmpty();
}
void ScriptCustomElementDefinition::RunCallback(
v8::Local<v8::Function> callback,
Element* element,
int argc,
v8::Local<v8::Value> argv[]) {
DCHECK(ScriptState::Current(script_state_->GetIsolate()) == script_state_);
v8::Isolate* isolate = script_state_->GetIsolate();
// Invoke custom element reactions
// https://html.spec.whatwg.org/multipage/scripting.html#invoke-custom-element-reactions
// If this throws any exception, then report the exception.
v8::TryCatch try_catch(isolate);
try_catch.SetVerbose(true);
ExecutionContext* execution_context =
ExecutionContext::From(script_state_.get());
v8::Local<v8::Value> element_handle =
ToV8(element, script_state_->GetContext()->Global(), isolate);
V8ScriptRunner::CallFunction(callback, execution_context, element_handle,
argc, argv, isolate);
}
void ScriptCustomElementDefinition::RunConnectedCallback(Element* element) {
if (!script_state_->ContextIsValid())
return;
ScriptState::Scope scope(script_state_.get());
v8::Isolate* isolate = script_state_->GetIsolate();
RunCallback(connected_callback_.NewLocal(isolate), element);
}
void ScriptCustomElementDefinition::RunDisconnectedCallback(Element* element) {
if (!script_state_->ContextIsValid())
return;
ScriptState::Scope scope(script_state_.get());
v8::Isolate* isolate = script_state_->GetIsolate();
RunCallback(disconnected_callback_.NewLocal(isolate), element);
}
void ScriptCustomElementDefinition::RunAdoptedCallback(Element* element,
Document* old_owner,
Document* new_owner) {
if (!script_state_->ContextIsValid())
return;
ScriptState::Scope scope(script_state_.get());
v8::Isolate* isolate = script_state_->GetIsolate();
v8::Local<v8::Value> argv[] = {
ToV8(old_owner, script_state_->GetContext()->Global(), isolate),
ToV8(new_owner, script_state_->GetContext()->Global(), isolate)};
RunCallback(adopted_callback_.NewLocal(isolate), element, base::size(argv),
argv);
}
void ScriptCustomElementDefinition::RunAttributeChangedCallback(
Element* element,
const QualifiedName& name,
const AtomicString& old_value,
const AtomicString& new_value) {
if (!script_state_->ContextIsValid())
return;
ScriptState::Scope scope(script_state_.get());
v8::Isolate* isolate = script_state_->GetIsolate();
v8::Local<v8::Value> argv[] = {
V8String(isolate, name.LocalName()), V8StringOrNull(isolate, old_value),
V8StringOrNull(isolate, new_value),
V8StringOrNull(isolate, name.NamespaceURI()),
};
RunCallback(attribute_changed_callback_.NewLocal(isolate), element,
base::size(argv), argv);
}
} // namespace blink