| OLD | NEW |
| (Empty) |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "core/dom/custom/CustomElementsRegistry.h" | |
| 6 | |
| 7 #include "bindings/core/v8/ExceptionState.h" | |
| 8 #include "bindings/core/v8/ScriptCustomElementDefinitionBuilder.h" | |
| 9 #include "bindings/core/v8/ScriptPromise.h" | |
| 10 #include "bindings/core/v8/ScriptPromiseResolver.h" | |
| 11 #include "core/dom/Document.h" | |
| 12 #include "core/dom/Element.h" | |
| 13 #include "core/dom/ElementRegistrationOptions.h" | |
| 14 #include "core/dom/ExceptionCode.h" | |
| 15 #include "core/dom/custom/CEReactionsScope.h" | |
| 16 #include "core/dom/custom/CustomElement.h" | |
| 17 #include "core/dom/custom/CustomElementDefinition.h" | |
| 18 #include "core/dom/custom/CustomElementDefinitionBuilder.h" | |
| 19 #include "core/dom/custom/CustomElementDescriptor.h" | |
| 20 #include "core/dom/custom/CustomElementUpgradeReaction.h" | |
| 21 #include "core/dom/custom/CustomElementUpgradeSorter.h" | |
| 22 #include "core/dom/custom/V0CustomElementRegistrationContext.h" | |
| 23 #include "core/frame/LocalDOMWindow.h" | |
| 24 #include "wtf/Allocator.h" | |
| 25 | |
| 26 namespace blink { | |
| 27 | |
| 28 // Returns true if |name| is invalid. | |
| 29 static bool throwIfInvalidName( | |
| 30 const AtomicString& name, | |
| 31 ExceptionState& exceptionState) | |
| 32 { | |
| 33 if (CustomElement::isValidName(name)) | |
| 34 return false; | |
| 35 exceptionState.throwDOMException( | |
| 36 SyntaxError, | |
| 37 "\"" + name + "\" is not a valid custom element name"); | |
| 38 return true; | |
| 39 } | |
| 40 | |
| 41 | |
| 42 class CustomElementsRegistry::NameIsBeingDefined final { | |
| 43 STACK_ALLOCATED(); | |
| 44 DISALLOW_IMPLICIT_CONSTRUCTORS(NameIsBeingDefined); | |
| 45 public: | |
| 46 NameIsBeingDefined( | |
| 47 CustomElementsRegistry* registry, | |
| 48 const AtomicString& name) | |
| 49 : m_registry(registry) | |
| 50 , m_name(name) | |
| 51 { | |
| 52 DCHECK(!m_registry->m_namesBeingDefined.contains(name)); | |
| 53 m_registry->m_namesBeingDefined.add(name); | |
| 54 } | |
| 55 | |
| 56 ~NameIsBeingDefined() | |
| 57 { | |
| 58 m_registry->m_namesBeingDefined.remove(m_name); | |
| 59 } | |
| 60 | |
| 61 private: | |
| 62 Member<CustomElementsRegistry> m_registry; | |
| 63 const AtomicString& m_name; | |
| 64 }; | |
| 65 | |
| 66 CustomElementsRegistry* CustomElementsRegistry::create( | |
| 67 const LocalDOMWindow* owner) | |
| 68 { | |
| 69 CustomElementsRegistry* registry = new CustomElementsRegistry(owner); | |
| 70 Document* document = owner->document(); | |
| 71 if (V0CustomElementRegistrationContext* v0 = | |
| 72 document ? document->registrationContext() : nullptr) | |
| 73 registry->entangle(v0); | |
| 74 return registry; | |
| 75 } | |
| 76 | |
| 77 CustomElementsRegistry::CustomElementsRegistry(const LocalDOMWindow* owner) | |
| 78 : m_owner(owner) | |
| 79 , m_v0 (new V0RegistrySet()) | |
| 80 , m_upgradeCandidates(new UpgradeCandidateMap()) | |
| 81 { | |
| 82 } | |
| 83 | |
| 84 DEFINE_TRACE(CustomElementsRegistry) | |
| 85 { | |
| 86 visitor->trace(m_definitions); | |
| 87 visitor->trace(m_owner); | |
| 88 visitor->trace(m_v0); | |
| 89 visitor->trace(m_upgradeCandidates); | |
| 90 visitor->trace(m_whenDefinedPromiseMap); | |
| 91 } | |
| 92 | |
| 93 void CustomElementsRegistry::define( | |
| 94 ScriptState* scriptState, | |
| 95 const AtomicString& name, | |
| 96 const ScriptValue& constructor, | |
| 97 const ElementRegistrationOptions& options, | |
| 98 ExceptionState& exceptionState) | |
| 99 { | |
| 100 ScriptCustomElementDefinitionBuilder builder( | |
| 101 scriptState, | |
| 102 this, | |
| 103 constructor, | |
| 104 exceptionState); | |
| 105 define(name, builder, options, exceptionState); | |
| 106 } | |
| 107 | |
| 108 // http://w3c.github.io/webcomponents/spec/custom/#dfn-element-definition | |
| 109 void CustomElementsRegistry::define( | |
| 110 const AtomicString& name, | |
| 111 CustomElementDefinitionBuilder& builder, | |
| 112 const ElementRegistrationOptions& options, | |
| 113 ExceptionState& exceptionState) | |
| 114 { | |
| 115 if (!builder.checkConstructorIntrinsics()) | |
| 116 return; | |
| 117 | |
| 118 if (throwIfInvalidName(name, exceptionState)) | |
| 119 return; | |
| 120 | |
| 121 if (m_namesBeingDefined.contains(name)) { | |
| 122 exceptionState.throwDOMException( | |
| 123 NotSupportedError, | |
| 124 "this name is already being defined in this registry"); | |
| 125 return; | |
| 126 } | |
| 127 NameIsBeingDefined defining(this, name); | |
| 128 | |
| 129 if (nameIsDefined(name) || v0NameIsDefined(name)) { | |
| 130 exceptionState.throwDOMException( | |
| 131 NotSupportedError, | |
| 132 "this name has already been used with this registry"); | |
| 133 return; | |
| 134 } | |
| 135 | |
| 136 if (!builder.checkConstructorNotRegistered()) | |
| 137 return; | |
| 138 | |
| 139 // TODO(dominicc): Implement steps: | |
| 140 // 5: localName | |
| 141 // 6-7: extends processing | |
| 142 | |
| 143 // 8-9: observed attributes caching is done below, together with callbacks. | |
| 144 // TODO(kojii): https://github.com/whatwg/html/issues/1373 for the ordering. | |
| 145 // When it's resolved, revisit if this code needs changes. | |
| 146 | |
| 147 // TODO(dominicc): Add a test where the prototype getter destroys | |
| 148 // the context. | |
| 149 | |
| 150 if (!builder.checkPrototype()) | |
| 151 return; | |
| 152 | |
| 153 // 8-9: observed attributes caching | |
| 154 // 12-13: connected callback | |
| 155 // 14-15: disconnected callback | |
| 156 // 16-17: attribute changed callback | |
| 157 | |
| 158 if (!builder.rememberOriginalProperties()) | |
| 159 return; | |
| 160 | |
| 161 // TODO(dominicc): Add a test where retrieving the prototype | |
| 162 // recursively calls define with the same name. | |
| 163 | |
| 164 CustomElementDescriptor descriptor(name, name); | |
| 165 CustomElementDefinition* definition = builder.build(descriptor); | |
| 166 CHECK(!exceptionState.hadException()); | |
| 167 CHECK(definition->descriptor() == descriptor); | |
| 168 DefinitionMap::AddResult result = | |
| 169 m_definitions.add(descriptor.name(), definition); | |
| 170 CHECK(result.isNewEntry); | |
| 171 | |
| 172 HeapVector<Member<Element>> candidates; | |
| 173 collectCandidates(descriptor, &candidates); | |
| 174 for (Element* candidate : candidates) | |
| 175 definition->enqueueUpgradeReaction(candidate); | |
| 176 | |
| 177 // 19: when-defined promise processing | |
| 178 const auto& entry = m_whenDefinedPromiseMap.find(name); | |
| 179 if (entry == m_whenDefinedPromiseMap.end()) | |
| 180 return; | |
| 181 entry->value->resolve(); | |
| 182 m_whenDefinedPromiseMap.remove(entry); | |
| 183 } | |
| 184 | |
| 185 // https://html.spec.whatwg.org/multipage/scripting.html#dom-customelementsregis
try-get | |
| 186 ScriptValue CustomElementsRegistry::get(const AtomicString& name) | |
| 187 { | |
| 188 CustomElementDefinition* definition = definitionForName(name); | |
| 189 if (!definition) { | |
| 190 // Binding layer converts |ScriptValue()| to script specific value, | |
| 191 // e.g. |undefined| for v8. | |
| 192 return ScriptValue(); | |
| 193 } | |
| 194 return definition->getConstructorForScript(); | |
| 195 } | |
| 196 | |
| 197 CustomElementDefinition* CustomElementsRegistry::definitionFor(const CustomEleme
ntDescriptor& desc) const | |
| 198 { | |
| 199 CustomElementDefinition* definition = definitionForName(desc.name()); | |
| 200 if (!definition) | |
| 201 return nullptr; | |
| 202 // The definition for a customized built-in element, such as | |
| 203 // <button is="my-button"> should not be provided for an | |
| 204 // autonomous element, such as <my-button>, even though the | |
| 205 // name "my-button" matches. | |
| 206 return definition->descriptor() == desc ? definition : nullptr; | |
| 207 } | |
| 208 | |
| 209 bool CustomElementsRegistry::nameIsDefined(const AtomicString& name) const | |
| 210 { | |
| 211 return m_definitions.contains(name); | |
| 212 } | |
| 213 | |
| 214 void CustomElementsRegistry::entangle(V0CustomElementRegistrationContext* v0) | |
| 215 { | |
| 216 m_v0->add(v0); | |
| 217 v0->setV1(this); | |
| 218 } | |
| 219 | |
| 220 bool CustomElementsRegistry::v0NameIsDefined(const AtomicString& name) | |
| 221 { | |
| 222 for (const auto& v0 : *m_v0) { | |
| 223 if (v0->nameIsDefined(name)) | |
| 224 return true; | |
| 225 } | |
| 226 return false; | |
| 227 } | |
| 228 | |
| 229 CustomElementDefinition* CustomElementsRegistry::definitionForName( | |
| 230 const AtomicString& name) const | |
| 231 { | |
| 232 return m_definitions.get(name); | |
| 233 } | |
| 234 | |
| 235 void CustomElementsRegistry::addCandidate(Element* candidate) | |
| 236 { | |
| 237 const AtomicString& name = candidate->localName(); | |
| 238 if (nameIsDefined(name) || v0NameIsDefined(name)) | |
| 239 return; | |
| 240 UpgradeCandidateMap::iterator it = m_upgradeCandidates->find(name); | |
| 241 UpgradeCandidateSet* set; | |
| 242 if (it != m_upgradeCandidates->end()) { | |
| 243 set = it->value; | |
| 244 } else { | |
| 245 set = m_upgradeCandidates->add(name, new UpgradeCandidateSet()) | |
| 246 .storedValue | |
| 247 ->value; | |
| 248 } | |
| 249 set->add(candidate); | |
| 250 } | |
| 251 | |
| 252 // https://html.spec.whatwg.org/multipage/scripting.html#dom-customelementsregis
try-whendefined | |
| 253 ScriptPromise CustomElementsRegistry::whenDefined( | |
| 254 ScriptState* scriptState, | |
| 255 const AtomicString& name, | |
| 256 ExceptionState& exceptionState) | |
| 257 { | |
| 258 if (throwIfInvalidName(name, exceptionState)) | |
| 259 return ScriptPromise(); | |
| 260 CustomElementDefinition* definition = definitionForName(name); | |
| 261 if (definition) | |
| 262 return ScriptPromise::castUndefined(scriptState); | |
| 263 ScriptPromiseResolver* resolver = m_whenDefinedPromiseMap.get(name); | |
| 264 if (resolver) | |
| 265 return resolver->promise(); | |
| 266 ScriptPromiseResolver* newResolver = | |
| 267 ScriptPromiseResolver::create(scriptState); | |
| 268 m_whenDefinedPromiseMap.add(name, newResolver); | |
| 269 return newResolver->promise(); | |
| 270 } | |
| 271 | |
| 272 void CustomElementsRegistry::collectCandidates( | |
| 273 const CustomElementDescriptor& desc, | |
| 274 HeapVector<Member<Element>>* elements) | |
| 275 { | |
| 276 UpgradeCandidateMap::iterator it = m_upgradeCandidates->find(desc.name()); | |
| 277 if (it == m_upgradeCandidates->end()) | |
| 278 return; | |
| 279 CustomElementUpgradeSorter sorter; | |
| 280 for (Element* element : *it.get()->value) { | |
| 281 if (!element || !desc.matches(*element)) | |
| 282 continue; | |
| 283 sorter.add(element); | |
| 284 } | |
| 285 | |
| 286 m_upgradeCandidates->remove(it); | |
| 287 | |
| 288 Document* document = m_owner->document(); | |
| 289 if (!document) | |
| 290 return; | |
| 291 | |
| 292 sorter.sorted(elements, document); | |
| 293 } | |
| 294 | |
| 295 } // namespace blink | |
| OLD | NEW |