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