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/CustomElementConstructorBuilder.h"
     33 
     34 #include "HTMLNames.h"
     35 #include "SVGNames.h"
     36 #include "V8Document.h"
     37 #include "V8HTMLElementWrapperFactory.h"
     38 #include "V8SVGElementWrapperFactory.h"
     39 #include "bindings/v8/CustomElementBinding.h"
     40 #include "bindings/v8/DOMWrapperWorld.h"
     41 #include "bindings/v8/Dictionary.h"
     42 #include "bindings/v8/ExceptionState.h"
     43 #include "bindings/v8/UnsafePersistent.h"
     44 #include "bindings/v8/V8Binding.h"
     45 #include "bindings/v8/V8HiddenPropertyName.h"
     46 #include "bindings/v8/V8PerContextData.h"
     47 #include "core/dom/CustomElementCallbackDispatcher.h"
     48 #include "core/dom/CustomElementDefinition.h"
     49 #include "core/dom/CustomElementDescriptor.h"
     50 #include "core/dom/Document.h"
     51 #include "wtf/Assertions.h"
     52 
     53 namespace WebCore {
     54 
     55 static void constructCustomElement(const v8::FunctionCallbackInfo<v8::Value>&);
     56 
     57 CustomElementConstructorBuilder::CustomElementConstructorBuilder(ScriptState* state, const Dictionary* options)
     58     : m_context(state->context())
     59     , m_options(options)
     60     , m_wrapperType(0)
     61 {
     62     ASSERT(m_context == v8::Isolate::GetCurrent()->GetCurrentContext());
     63 }
     64 
     65 bool CustomElementConstructorBuilder::isFeatureAllowed() const
     66 {
     67     // Check that we are in the main world
     68     return !DOMWrapperWorld::isolatedWorld(m_context);
     69 }
     70 
     71 bool CustomElementConstructorBuilder::validateOptions()
     72 {
     73     ASSERT(m_prototype.IsEmpty());
     74 
     75     ScriptValue prototypeScriptValue;
     76     if (!m_options->get("prototype", prototypeScriptValue)) {
     77         // FIXME: Implement the default value handling.
     78         // Currently default value of the "prototype" parameter, which
     79         // is HTMLSpanElement.prototype, has an ambiguity about its
     80         // behavior. The spec should be fixed before WebKit implements
     81         // it. https://www.w3.org/Bugs/Public/show_bug.cgi?id=20801
     82         return false;
     83     }
     84 
     85     v8::Handle<v8::Value> prototypeValue = prototypeScriptValue.v8Value();
     86     if (prototypeValue.IsEmpty() || !prototypeValue->IsObject())
     87         return false;
     88     m_prototype = prototypeValue.As<v8::Object>();
     89 
     90     V8PerContextData* perContextData;
     91     if (!(perContextData = V8PerContextData::from(m_context))) {
     92         // FIXME: This should generate an InvalidContext exception at a later point.
     93         return false;
     94     }
     95 
     96     if (hasValidPrototypeChainFor(perContextData, &V8HTMLElement::info)) {
     97         m_namespaceURI = HTMLNames::xhtmlNamespaceURI;
     98         return true;
     99     }
    100 
    101     if (hasValidPrototypeChainFor(perContextData, &V8SVGElement::info)) {
    102         m_namespaceURI = SVGNames::svgNamespaceURI;
    103         return true;
    104     }
    105 
    106     if (hasValidPrototypeChainFor(perContextData, &V8Element::info)) {
    107         m_namespaceURI = nullAtom;
    108         // This generates a different DOM exception, so we feign success for now.
    109         return true;
    110     }
    111 
    112     return false;
    113 }
    114 
    115 bool CustomElementConstructorBuilder::findTagName(const AtomicString& customElementType, QualifiedName& tagName)
    116 {
    117     ASSERT(!m_prototype.IsEmpty());
    118 
    119     m_wrapperType = findWrapperType(m_prototype);
    120     if (!m_wrapperType) {
    121         // Invalid prototype.
    122         return false;
    123     }
    124 
    125     if (const QualifiedName* htmlName = findHTMLTagNameOfV8Type(m_wrapperType)) {
    126         ASSERT(htmlName->namespaceURI() == m_namespaceURI);
    127         tagName = *htmlName;
    128         return true;
    129     }
    130 
    131     if (const QualifiedName* svgName = findSVGTagNameOfV8Type(m_wrapperType)) {
    132         ASSERT(svgName->namespaceURI() == m_namespaceURI);
    133         tagName = *svgName;
    134         return true;
    135     }
    136 
    137     if (m_namespaceURI != nullAtom) {
    138         // Use the custom element type as the tag's local name.
    139         tagName = QualifiedName(nullAtom, customElementType, m_namespaceURI);
    140         return true;
    141     }
    142 
    143     return false;
    144 }
    145 
    146 PassRefPtr<CustomElementLifecycleCallbacks> CustomElementConstructorBuilder::createCallbacks()
    147 {
    148     ASSERT(!m_prototype.IsEmpty());
    149 
    150     RefPtr<ScriptExecutionContext> scriptExecutionContext(toScriptExecutionContext(m_context));
    151 
    152     v8::TryCatch exceptionCatcher;
    153     exceptionCatcher.SetVerbose(true);
    154 
    155     v8::Isolate* isolate = v8::Isolate::GetCurrent();
    156     v8::Handle<v8::Function> created = retrieveCallback(isolate, "createdCallback");
    157     v8::Handle<v8::Function> enteredDocument = retrieveCallback(isolate, "enteredDocumentCallback");
    158     v8::Handle<v8::Function> leftDocument = retrieveCallback(isolate, "leftDocumentCallback");
    159     v8::Handle<v8::Function> attributeChanged = retrieveCallback(isolate, "attributeChangedCallback");
    160 
    161     m_callbacks = V8CustomElementLifecycleCallbacks::create(scriptExecutionContext.get(), m_prototype, created, enteredDocument, leftDocument, attributeChanged);
    162     return m_callbacks.get();
    163 }
    164 
    165 v8::Handle<v8::Function> CustomElementConstructorBuilder::retrieveCallback(v8::Isolate* isolate, const char* name)
    166 {
    167     v8::Handle<v8::Value> value = m_prototype->Get(v8String(name, isolate));
    168     if (value.IsEmpty() || !value->IsFunction())
    169         return v8::Handle<v8::Function>();
    170     return value.As<v8::Function>();
    171 }
    172 
    173 bool CustomElementConstructorBuilder::createConstructor(Document* document, CustomElementDefinition* definition)
    174 {
    175     ASSERT(!m_prototype.IsEmpty());
    176     ASSERT(m_constructor.IsEmpty());
    177     ASSERT(document);
    178 
    179     v8::Isolate* isolate = m_context->GetIsolate();
    180 
    181     if (!prototypeIsValid())
    182         return false;
    183 
    184     v8::Local<v8::FunctionTemplate> constructorTemplate = v8::FunctionTemplate::New();
    185     constructorTemplate->SetCallHandler(constructCustomElement);
    186     m_constructor = constructorTemplate->GetFunction();
    187     if (m_constructor.IsEmpty())
    188         return false;
    189 
    190     const CustomElementDescriptor& descriptor = definition->descriptor();
    191 
    192     v8::Handle<v8::String> v8TagName = v8String(descriptor.localName(), isolate);
    193     v8::Handle<v8::Value> v8Type;
    194     if (descriptor.isTypeExtension())
    195         v8Type = v8String(descriptor.type(), isolate);
    196     else
    197         v8Type = v8::Null(isolate);
    198 
    199     m_constructor->SetName(v8Type->IsNull() ? v8TagName : v8Type.As<v8::String>());
    200 
    201     V8HiddenPropertyName::setNamedHiddenReference(m_constructor, "customElementDocument", toV8(document, m_context->Global(), isolate));
    202     V8HiddenPropertyName::setNamedHiddenReference(m_constructor, "customElementNamespaceURI", v8String(descriptor.namespaceURI(), isolate));
    203     V8HiddenPropertyName::setNamedHiddenReference(m_constructor, "customElementTagName", v8TagName);
    204     V8HiddenPropertyName::setNamedHiddenReference(m_constructor, "customElementType", v8Type);
    205 
    206     v8::Handle<v8::String> prototypeKey = v8String("prototype", isolate);
    207     ASSERT(m_constructor->HasOwnProperty(prototypeKey));
    208     // This sets the property *value*; calling Set is safe because
    209     // "prototype" is a non-configurable data property so there can be
    210     // no side effects.
    211     m_constructor->Set(prototypeKey, m_prototype);
    212     // This *configures* the property. ForceSet of a function's
    213     // "prototype" does not affect the value, but can reconfigure the
    214     // property.
    215     m_constructor->ForceSet(prototypeKey, m_prototype, v8::PropertyAttribute(v8::ReadOnly | v8::DontEnum | v8::DontDelete));
    216 
    217     V8HiddenPropertyName::setNamedHiddenReference(m_prototype, "customElementIsInterfacePrototypeObject", v8::True());
    218     m_prototype->ForceSet(v8String("constructor", isolate), m_constructor, v8::DontEnum);
    219 
    220     return true;
    221 }
    222 
    223 bool CustomElementConstructorBuilder::prototypeIsValid() const
    224 {
    225     if (m_prototype->InternalFieldCount() || !m_prototype->GetHiddenValue(V8HiddenPropertyName::customElementIsInterfacePrototypeObject()).IsEmpty()) {
    226         // Alcreated an interface prototype object.
    227         return false;
    228     }
    229 
    230     if (m_prototype->GetPropertyAttributes(v8String("constructor", m_context->GetIsolate())) & v8::DontDelete) {
    231         // "constructor" is not configurable.
    232         return false;
    233     }
    234 
    235     return true;
    236 }
    237 
    238 bool CustomElementConstructorBuilder::didRegisterDefinition(CustomElementDefinition* definition) const
    239 {
    240     ASSERT(!m_constructor.IsEmpty());
    241 
    242     return m_callbacks->setBinding(definition, CustomElementBinding::create(m_context->GetIsolate(), m_prototype, m_wrapperType));
    243 }
    244 
    245 ScriptValue CustomElementConstructorBuilder::bindingsReturnValue() const
    246 {
    247     return ScriptValue(m_constructor);
    248 }
    249 
    250 WrapperTypeInfo* CustomElementConstructorBuilder::findWrapperType(v8::Handle<v8::Value> chain)
    251 {
    252     while (!chain.IsEmpty() && chain->IsObject()) {
    253         v8::Handle<v8::Object> chainObject = chain.As<v8::Object>();
    254         // Only prototype objects of native-backed types have the extra internal field storing WrapperTypeInfo.
    255         if (v8PrototypeInternalFieldcount == chainObject->InternalFieldCount()) {
    256             WrapperTypeInfo* wrapperType = reinterpret_cast<WrapperTypeInfo*>(chainObject->GetAlignedPointerFromInternalField(v8PrototypeTypeIndex));
    257             ASSERT(wrapperType);
    258             return wrapperType;
    259         }
    260         chain = chainObject->GetPrototype();
    261     }
    262 
    263     return 0;
    264 }
    265 
    266 bool CustomElementConstructorBuilder::hasValidPrototypeChainFor(V8PerContextData* perContextData, WrapperTypeInfo* typeInfo) const
    267 {
    268     v8::Handle<v8::Object> elementConstructor = perContextData->constructorForType(typeInfo);
    269     v8::Handle<v8::Object> elementPrototype = elementConstructor->Get(v8String("prototype", m_context->GetIsolate())).As<v8::Object>();
    270     if (elementPrototype.IsEmpty())
    271         return false;
    272 
    273     v8::Handle<v8::Value> chain = m_prototype;
    274     while (!chain.IsEmpty() && chain->IsObject()) {
    275         if (chain == elementPrototype)
    276             return true;
    277         chain = chain.As<v8::Object>()->GetPrototype();
    278     }
    279 
    280     return false;
    281 }
    282 
    283 static void constructCustomElement(const v8::FunctionCallbackInfo<v8::Value>& args)
    284 {
    285     v8::Isolate* isolate = args.GetIsolate();
    286 
    287     if (!args.IsConstructCall()) {
    288         throwTypeError("DOM object constructor cannot be called as a function.", isolate);
    289         return;
    290     }
    291 
    292     if (args.Length() > 0) {
    293         throwTypeError(isolate);
    294         return;
    295     }
    296 
    297     Document* document = V8Document::toNative(args.Callee()->GetHiddenValue(V8HiddenPropertyName::customElementDocument()).As<v8::Object>());
    298     V8TRYCATCH_FOR_V8STRINGRESOURCE_VOID(V8StringResource<>, namespaceURI, args.Callee()->GetHiddenValue(V8HiddenPropertyName::customElementNamespaceURI()));
    299     V8TRYCATCH_FOR_V8STRINGRESOURCE_VOID(V8StringResource<>, tagName, args.Callee()->GetHiddenValue(V8HiddenPropertyName::customElementTagName()));
    300     v8::Handle<v8::Value> maybeType = args.Callee()->GetHiddenValue(V8HiddenPropertyName::customElementType());
    301     V8TRYCATCH_FOR_V8STRINGRESOURCE_VOID(V8StringResource<>, type, maybeType);
    302 
    303     ExceptionState es(args.GetIsolate());
    304     CustomElementCallbackDispatcher::CallbackDeliveryScope deliveryScope;
    305     RefPtr<Element> element = document->createElementNS(namespaceURI, tagName, maybeType->IsNull() ? nullAtom : type, es);
    306     if (es.throwIfNeeded())
    307         return;
    308     v8SetReturnValue(args, toV8Fast(element.release(), args, document));
    309 }
    310 
    311 } // namespace WebCore
    312