Home | History | Annotate | Download | only in v8
      1 /*
      2  * Copyright (C) 2013 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 #include "config.h"
     32 #include "bindings/v8/V8CustomElementLifecycleCallbacks.h"
     33 
     34 #include "bindings/core/v8/V8Element.h"
     35 #include "bindings/v8/CustomElementBinding.h"
     36 #include "bindings/v8/DOMDataStore.h"
     37 #include "bindings/v8/ScriptController.h"
     38 #include "bindings/v8/V8Binding.h"
     39 #include "bindings/v8/V8HiddenValue.h"
     40 #include "bindings/v8/V8PerContextData.h"
     41 #include "core/dom/ExecutionContext.h"
     42 #include "core/inspector/InspectorInstrumentation.h"
     43 #include "wtf/PassOwnPtr.h"
     44 
     45 namespace WebCore {
     46 
     47 #define CALLBACK_LIST(V)                  \
     48     V(created, Created)                   \
     49     V(attached, Attached)           \
     50     V(detached, Detached)                 \
     51     V(attributeChanged, AttributeChanged)
     52 
     53 PassRefPtr<V8CustomElementLifecycleCallbacks> V8CustomElementLifecycleCallbacks::create(ScriptState* scriptState, v8::Handle<v8::Object> prototype, v8::Handle<v8::Function> created, v8::Handle<v8::Function> attached, v8::Handle<v8::Function> detached, v8::Handle<v8::Function> attributeChanged)
     54 {
     55     v8::Isolate* isolate = scriptState->isolate();
     56     // A given object can only be used as a Custom Element prototype
     57     // once; see customElementIsInterfacePrototypeObject
     58 #define SET_HIDDEN_VALUE(Value, Name) \
     59     ASSERT(V8HiddenValue::getHiddenValue(isolate, prototype, V8HiddenValue::customElement##Name(isolate)).IsEmpty()); \
     60     if (!Value.IsEmpty()) \
     61         V8HiddenValue::setHiddenValue(isolate, prototype, V8HiddenValue::customElement##Name(isolate), Value);
     62 
     63     CALLBACK_LIST(SET_HIDDEN_VALUE)
     64 #undef SET_HIDDEN_VALUE
     65 
     66     return adoptRef(new V8CustomElementLifecycleCallbacks(scriptState, prototype, created, attached, detached, attributeChanged));
     67 }
     68 
     69 static CustomElementLifecycleCallbacks::CallbackType flagSet(v8::Handle<v8::Function> attached, v8::Handle<v8::Function> detached, v8::Handle<v8::Function> attributeChanged)
     70 {
     71     // V8 Custom Elements always run created to swizzle prototypes.
     72     int flags = CustomElementLifecycleCallbacks::Created;
     73 
     74     if (!attached.IsEmpty())
     75         flags |= CustomElementLifecycleCallbacks::Attached;
     76 
     77     if (!detached.IsEmpty())
     78         flags |= CustomElementLifecycleCallbacks::Detached;
     79 
     80     if (!attributeChanged.IsEmpty())
     81         flags |= CustomElementLifecycleCallbacks::AttributeChanged;
     82 
     83     return CustomElementLifecycleCallbacks::CallbackType(flags);
     84 }
     85 
     86 template <typename T>
     87 static void weakCallback(const v8::WeakCallbackData<T, ScopedPersistent<T> >& data)
     88 {
     89     data.GetParameter()->clear();
     90 }
     91 
     92 V8CustomElementLifecycleCallbacks::V8CustomElementLifecycleCallbacks(ScriptState* scriptState, v8::Handle<v8::Object> prototype, v8::Handle<v8::Function> created, v8::Handle<v8::Function> attached, v8::Handle<v8::Function> detached, v8::Handle<v8::Function> attributeChanged)
     93     : CustomElementLifecycleCallbacks(flagSet(attached, detached, attributeChanged))
     94     , ContextLifecycleObserver(scriptState->executionContext())
     95     , m_owner(0)
     96     , m_scriptState(scriptState)
     97     , m_prototype(scriptState->isolate(), prototype)
     98     , m_created(scriptState->isolate(), created)
     99     , m_attached(scriptState->isolate(), attached)
    100     , m_detached(scriptState->isolate(), detached)
    101     , m_attributeChanged(scriptState->isolate(), attributeChanged)
    102 {
    103     m_prototype.setWeak(&m_prototype, weakCallback<v8::Object>);
    104 
    105 #define MAKE_WEAK(Var, _) \
    106     if (!m_##Var.isEmpty()) \
    107         m_##Var.setWeak(&m_##Var, weakCallback<v8::Function>);
    108 
    109     CALLBACK_LIST(MAKE_WEAK)
    110 #undef MAKE_WEAK
    111 }
    112 
    113 V8PerContextData* V8CustomElementLifecycleCallbacks::creationContextData()
    114 {
    115     if (!executionContext())
    116         return 0;
    117 
    118     v8::Handle<v8::Context> context = m_scriptState->context();
    119     if (context.IsEmpty())
    120         return 0;
    121 
    122     return V8PerContextData::from(context);
    123 }
    124 
    125 V8CustomElementLifecycleCallbacks::~V8CustomElementLifecycleCallbacks()
    126 {
    127     if (!m_owner)
    128         return;
    129 
    130     v8::HandleScope handleScope(m_scriptState->isolate());
    131     if (V8PerContextData* perContextData = creationContextData())
    132         perContextData->clearCustomElementBinding(m_owner);
    133 }
    134 
    135 bool V8CustomElementLifecycleCallbacks::setBinding(CustomElementDefinition* owner, PassOwnPtr<CustomElementBinding> binding)
    136 {
    137     ASSERT(!m_owner);
    138 
    139     V8PerContextData* perContextData = creationContextData();
    140     if (!perContextData)
    141         return false;
    142 
    143     m_owner = owner;
    144 
    145     // Bindings retrieve the prototype when needed from per-context data.
    146     perContextData->addCustomElementBinding(owner, binding);
    147 
    148     return true;
    149 }
    150 
    151 void V8CustomElementLifecycleCallbacks::created(Element* element)
    152 {
    153     // FIXME: callbacks while paused should be queued up for execution to
    154     // continue then be delivered in order rather than delivered immediately.
    155     // Bug 329665 tracks similar behavior for other synchronous events.
    156     if (!executionContext() || executionContext()->activeDOMObjectsAreStopped())
    157         return;
    158 
    159     element->setCustomElementState(Element::Upgraded);
    160 
    161     if (m_scriptState->contextIsEmpty())
    162         return;
    163     ScriptState::Scope scope(m_scriptState.get());
    164     v8::Isolate* isolate = m_scriptState->isolate();
    165     v8::Handle<v8::Context> context = m_scriptState->context();
    166     v8::Handle<v8::Object> receiver = m_scriptState->world().domDataStore().get<V8Element>(element, isolate);
    167     if (!receiver.IsEmpty()) {
    168         // Swizzle the prototype of the existing wrapper. We don't need to
    169         // worry about non-existent wrappers; they will get the right
    170         // prototype when wrapped.
    171         v8::Handle<v8::Object> prototype = m_prototype.newLocal(isolate);
    172         if (prototype.IsEmpty())
    173             return;
    174         receiver->SetPrototype(prototype);
    175     }
    176 
    177     v8::Handle<v8::Function> callback = m_created.newLocal(isolate);
    178     if (callback.IsEmpty())
    179         return;
    180 
    181     if (receiver.IsEmpty())
    182         receiver = toV8(element, context->Global(), isolate).As<v8::Object>();
    183 
    184     ASSERT(!receiver.IsEmpty());
    185 
    186     InspectorInstrumentation::willExecuteCustomElementCallback(element);
    187 
    188     v8::TryCatch exceptionCatcher;
    189     exceptionCatcher.SetVerbose(true);
    190     ScriptController::callFunction(executionContext(), callback, receiver, 0, 0, isolate);
    191 }
    192 
    193 void V8CustomElementLifecycleCallbacks::attached(Element* element)
    194 {
    195     call(m_attached, element);
    196 }
    197 
    198 void V8CustomElementLifecycleCallbacks::detached(Element* element)
    199 {
    200     call(m_detached, element);
    201 }
    202 
    203 void V8CustomElementLifecycleCallbacks::attributeChanged(Element* element, const AtomicString& name, const AtomicString& oldValue, const AtomicString& newValue)
    204 {
    205     // FIXME: callbacks while paused should be queued up for execution to
    206     // continue then be delivered in order rather than delivered immediately.
    207     // Bug 329665 tracks similar behavior for other synchronous events.
    208     if (!executionContext() || executionContext()->activeDOMObjectsAreStopped())
    209         return;
    210 
    211     if (m_scriptState->contextIsEmpty())
    212         return;
    213     ScriptState::Scope scope(m_scriptState.get());
    214     v8::Isolate* isolate = m_scriptState->isolate();
    215     v8::Handle<v8::Context> context = m_scriptState->context();
    216     v8::Handle<v8::Object> receiver = toV8(element, context->Global(), isolate).As<v8::Object>();
    217     ASSERT(!receiver.IsEmpty());
    218 
    219     v8::Handle<v8::Function> callback = m_attributeChanged.newLocal(isolate);
    220     if (callback.IsEmpty())
    221         return;
    222 
    223     v8::Handle<v8::Value> argv[] = {
    224         v8String(isolate, name),
    225         oldValue.isNull() ? v8::Handle<v8::Value>(v8::Null(isolate)) : v8::Handle<v8::Value>(v8String(isolate, oldValue)),
    226         newValue.isNull() ? v8::Handle<v8::Value>(v8::Null(isolate)) : v8::Handle<v8::Value>(v8String(isolate, newValue))
    227     };
    228 
    229     InspectorInstrumentation::willExecuteCustomElementCallback(element);
    230 
    231     v8::TryCatch exceptionCatcher;
    232     exceptionCatcher.SetVerbose(true);
    233     ScriptController::callFunction(executionContext(), callback, receiver, WTF_ARRAY_LENGTH(argv), argv, isolate);
    234 }
    235 
    236 void V8CustomElementLifecycleCallbacks::call(const ScopedPersistent<v8::Function>& weakCallback, Element* element)
    237 {
    238     // FIXME: callbacks while paused should be queued up for execution to
    239     // continue then be delivered in order rather than delivered immediately.
    240     // Bug 329665 tracks similar behavior for other synchronous events.
    241     if (!executionContext() || executionContext()->activeDOMObjectsAreStopped())
    242         return;
    243 
    244     if (m_scriptState->contextIsEmpty())
    245         return;
    246     ScriptState::Scope scope(m_scriptState.get());
    247     v8::Isolate* isolate = m_scriptState->isolate();
    248     v8::Handle<v8::Context> context = m_scriptState->context();
    249     v8::Handle<v8::Function> callback = weakCallback.newLocal(isolate);
    250     if (callback.IsEmpty())
    251         return;
    252 
    253     v8::Handle<v8::Object> receiver = toV8(element, context->Global(), isolate).As<v8::Object>();
    254     ASSERT(!receiver.IsEmpty());
    255 
    256     InspectorInstrumentation::willExecuteCustomElementCallback(element);
    257 
    258     v8::TryCatch exceptionCatcher;
    259     exceptionCatcher.SetVerbose(true);
    260     ScriptController::callFunction(executionContext(), callback, receiver, 0, 0, isolate);
    261 }
    262 
    263 } // namespace WebCore
    264