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 |