blob: 45ee4d8143ce0cd90f1ba7c9d537a4072fb16bbb [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/bindings/core/v8/v8_v0_custom_element_lifecycle_callbacks.h"
#include <memory>
#include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_element.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/platform/bindings/dom_data_store.h"
#include "third_party/blink/renderer/platform/bindings/v0_custom_element_binding.h"
#include "third_party/blink/renderer/platform/bindings/v8_per_context_data.h"
#include "third_party/blink/renderer/platform/bindings/v8_private_property.h"
namespace blink {
#define CALLBACK_LIST(V) \
V(created, CreatedCallback) \
V(attached, AttachedCallback) \
V(detached, DetachedCallback) \
V(attribute_changed, AttributeChangedCallback)
V8V0CustomElementLifecycleCallbacks*
V8V0CustomElementLifecycleCallbacks::Create(
ScriptState* script_state,
v8::Local<v8::Object> prototype,
v8::MaybeLocal<v8::Function> created,
v8::MaybeLocal<v8::Function> attached,
v8::MaybeLocal<v8::Function> detached,
v8::MaybeLocal<v8::Function> attribute_changed) {
v8::Isolate* isolate = script_state->GetIsolate();
// A given object can only be used as a Custom Element prototype
// once; see customElementIsInterfacePrototypeObject
#define SET_PRIVATE_PROPERTY(Maybe, Name) \
V8PrivateProperty::Symbol symbol##Name = \
V8PrivateProperty::GetCustomElementLifecycle##Name(isolate); \
DCHECK(!symbol##Name.HasValue(prototype)); \
{ \
v8::Local<v8::Function> function; \
if (Maybe.ToLocal(&function)) \
symbol##Name.Set(prototype, function); \
}
CALLBACK_LIST(SET_PRIVATE_PROPERTY)
#undef SET_PRIVATE_PROPERTY
return new V8V0CustomElementLifecycleCallbacks(
script_state, prototype, created, attached, detached, attribute_changed);
}
static V0CustomElementLifecycleCallbacks::CallbackType FlagSet(
v8::MaybeLocal<v8::Function> attached,
v8::MaybeLocal<v8::Function> detached,
v8::MaybeLocal<v8::Function> attribute_changed) {
// V8 Custom Elements always run created to swizzle prototypes.
int flags = V0CustomElementLifecycleCallbacks::kCreatedCallback;
if (!attached.IsEmpty())
flags |= V0CustomElementLifecycleCallbacks::kAttachedCallback;
if (!detached.IsEmpty())
flags |= V0CustomElementLifecycleCallbacks::kDetachedCallback;
if (!attribute_changed.IsEmpty())
flags |= V0CustomElementLifecycleCallbacks::kAttributeChangedCallback;
return V0CustomElementLifecycleCallbacks::CallbackType(flags);
}
V8V0CustomElementLifecycleCallbacks::V8V0CustomElementLifecycleCallbacks(
ScriptState* script_state,
v8::Local<v8::Object> prototype,
v8::MaybeLocal<v8::Function> created,
v8::MaybeLocal<v8::Function> attached,
v8::MaybeLocal<v8::Function> detached,
v8::MaybeLocal<v8::Function> attribute_changed)
: V0CustomElementLifecycleCallbacks(
FlagSet(attached, detached, attribute_changed)),
script_state_(script_state),
prototype_(script_state->GetIsolate(), prototype),
created_(script_state->GetIsolate(), created),
attached_(script_state->GetIsolate(), attached),
detached_(script_state->GetIsolate(), detached),
attribute_changed_(script_state->GetIsolate(), attribute_changed) {
prototype_.SetPhantom();
#define MAKE_WEAK(Var, Ignored) \
if (!Var##_.IsEmpty()) \
Var##_.SetPhantom();
CALLBACK_LIST(MAKE_WEAK)
#undef MAKE_WEAK
}
V8PerContextData* V8V0CustomElementLifecycleCallbacks::CreationContextData() {
if (!script_state_->ContextIsValid())
return nullptr;
v8::Local<v8::Context> context = script_state_->GetContext();
if (context.IsEmpty())
return nullptr;
return V8PerContextData::From(context);
}
V8V0CustomElementLifecycleCallbacks::~V8V0CustomElementLifecycleCallbacks() =
default;
bool V8V0CustomElementLifecycleCallbacks::SetBinding(
std::unique_ptr<V0CustomElementBinding> binding) {
V8PerContextData* per_context_data = CreationContextData();
if (!per_context_data)
return false;
// The context is responsible for keeping the prototype
// alive. This in turn keeps callbacks alive through hidden
// references; see CALLBACK_LIST(SET_HIDDEN_VALUE).
per_context_data->AddCustomElementBinding(std::move(binding));
return true;
}
void V8V0CustomElementLifecycleCallbacks::Created(Element* element) {
// FIXME: callbacks while paused should be queued up for execution to
// continue then be delivered in order rather than delivered immediately.
// Bug 329665 tracks similar behavior for other synchronous events.
if (!script_state_->ContextIsValid())
return;
element->SetV0CustomElementState(Element::kV0Upgraded);
ScriptState::Scope scope(script_state_.get());
v8::Isolate* isolate = script_state_->GetIsolate();
v8::Local<v8::Context> context = script_state_->GetContext();
v8::Local<v8::Value> receiver_value =
ToV8(element, context->Global(), isolate);
if (receiver_value.IsEmpty())
return;
v8::Local<v8::Object> receiver = receiver_value.As<v8::Object>();
// Swizzle the prototype of the wrapper.
v8::Local<v8::Object> prototype = prototype_.NewLocal(isolate);
if (prototype.IsEmpty())
return;
if (!V8CallBoolean(receiver->SetPrototype(context, prototype)))
return;
v8::Local<v8::Function> callback = created_.NewLocal(isolate);
if (callback.IsEmpty())
return;
v8::TryCatch exception_catcher(isolate);
exception_catcher.SetVerbose(true);
V8ScriptRunner::CallFunction(callback,
ExecutionContext::From(script_state_.get()),
receiver, 0, nullptr, isolate);
}
void V8V0CustomElementLifecycleCallbacks::Attached(Element* element) {
Call(attached_, element);
}
void V8V0CustomElementLifecycleCallbacks::Detached(Element* element) {
Call(detached_, element);
}
void V8V0CustomElementLifecycleCallbacks::AttributeChanged(
Element* element,
const AtomicString& name,
const AtomicString& old_value,
const AtomicString& new_value) {
// FIXME: callbacks while paused should be queued up for execution to
// continue then be delivered in order rather than delivered immediately.
// Bug 329665 tracks similar behavior for other synchronous events.
if (!script_state_->ContextIsValid())
return;
ScriptState::Scope scope(script_state_.get());
v8::Isolate* isolate = script_state_->GetIsolate();
v8::Local<v8::Context> context = script_state_->GetContext();
v8::Local<v8::Value> receiver = ToV8(element, context->Global(), isolate);
if (receiver.IsEmpty())
return;
v8::Local<v8::Function> callback = attribute_changed_.NewLocal(isolate);
if (callback.IsEmpty())
return;
v8::Local<v8::Value> argv[] = {
V8String(isolate, name),
old_value.IsNull() ? v8::Local<v8::Value>(v8::Null(isolate))
: v8::Local<v8::Value>(V8String(isolate, old_value)),
new_value.IsNull() ? v8::Local<v8::Value>(v8::Null(isolate))
: v8::Local<v8::Value>(V8String(isolate, new_value))};
v8::TryCatch exception_catcher(isolate);
exception_catcher.SetVerbose(true);
V8ScriptRunner::CallFunction(callback,
ExecutionContext::From(script_state_.get()),
receiver, base::size(argv), argv, isolate);
}
void V8V0CustomElementLifecycleCallbacks::Call(
const ScopedPersistent<v8::Function>& weak_callback,
Element* element) {
// FIXME: callbacks while paused should be queued up for execution to
// continue then be delivered in order rather than delivered immediately.
// Bug 329665 tracks similar behavior for other synchronous events.
if (!script_state_->ContextIsValid())
return;
ScriptState::Scope scope(script_state_.get());
v8::Isolate* isolate = script_state_->GetIsolate();
v8::Local<v8::Context> context = script_state_->GetContext();
v8::Local<v8::Function> callback = weak_callback.NewLocal(isolate);
if (callback.IsEmpty())
return;
v8::Local<v8::Value> receiver = ToV8(element, context->Global(), isolate);
if (receiver.IsEmpty())
return;
v8::TryCatch exception_catcher(isolate);
exception_catcher.SetVerbose(true);
V8ScriptRunner::CallFunction(callback,
ExecutionContext::From(script_state_.get()),
receiver, 0, nullptr, isolate);
}
void V8V0CustomElementLifecycleCallbacks::Trace(blink::Visitor* visitor) {
V0CustomElementLifecycleCallbacks::Trace(visitor);
}
} // namespace blink