1 | /* |
2 | * Copyright (C) 2004, 2005, 2006, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org> |
3 | * Copyright (C) 2004, 2005, 2006, 2007 Rob Buis <buis@kde.org> |
4 | * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. |
5 | * Copyright (C) 2011 Torch Mobile (Beijing) Co. Ltd. All rights reserved. |
6 | * Copyright (C) 2012 University of Szeged |
7 | * Copyright (C) 2012 Renata Hodovan <reni@webkit.org> |
8 | * Copyright (C) 2015-2019 Apple Inc. All rights reserved. |
9 | * |
10 | * This library is free software; you can redistribute it and/or |
11 | * modify it under the terms of the GNU Library General Public |
12 | * License as published by the Free Software Foundation; either |
13 | * version 2 of the License, or (at your option) any later version. |
14 | * |
15 | * This library is distributed in the hope that it will be useful, |
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
18 | * Library General Public License for more details. |
19 | * |
20 | * You should have received a copy of the GNU Library General Public License |
21 | * along with this library; see the file COPYING.LIB. If not, write to |
22 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
23 | * Boston, MA 02110-1301, USA. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "SVGUseElement.h" |
28 | |
29 | #include "CachedResourceLoader.h" |
30 | #include "CachedSVGDocument.h" |
31 | #include "ElementIterator.h" |
32 | #include "Event.h" |
33 | #include "EventNames.h" |
34 | #include "RenderSVGResource.h" |
35 | #include "RenderSVGTransformableContainer.h" |
36 | #include "SVGDocumentExtensions.h" |
37 | #include "SVGGElement.h" |
38 | #include "SVGSVGElement.h" |
39 | #include "SVGSymbolElement.h" |
40 | #include "ScriptDisallowedScope.h" |
41 | #include "ShadowRoot.h" |
42 | #include "XLinkNames.h" |
43 | #include <wtf/IsoMallocInlines.h> |
44 | |
45 | namespace WebCore { |
46 | |
47 | WTF_MAKE_ISO_ALLOCATED_IMPL(SVGUseElement); |
48 | |
49 | inline SVGUseElement::SVGUseElement(const QualifiedName& tagName, Document& document) |
50 | : SVGGraphicsElement(tagName, document) |
51 | , SVGExternalResourcesRequired(this) |
52 | , SVGURIReference(this) |
53 | , m_svgLoadEventTimer(*this, &SVGElement::svgLoadEventTimerFired) |
54 | { |
55 | ASSERT(hasCustomStyleResolveCallbacks()); |
56 | ASSERT(hasTagName(SVGNames::useTag)); |
57 | |
58 | static std::once_flag onceFlag; |
59 | std::call_once(onceFlag, [] { |
60 | PropertyRegistry::registerProperty<SVGNames::xAttr, &SVGUseElement::m_x>(); |
61 | PropertyRegistry::registerProperty<SVGNames::yAttr, &SVGUseElement::m_y>(); |
62 | PropertyRegistry::registerProperty<SVGNames::widthAttr, &SVGUseElement::m_width>(); |
63 | PropertyRegistry::registerProperty<SVGNames::heightAttr, &SVGUseElement::m_height>(); |
64 | }); |
65 | } |
66 | |
67 | Ref<SVGUseElement> SVGUseElement::create(const QualifiedName& tagName, Document& document) |
68 | { |
69 | return adoptRef(*new SVGUseElement(tagName, document)); |
70 | } |
71 | |
72 | SVGUseElement::~SVGUseElement() |
73 | { |
74 | if (m_externalDocument) |
75 | m_externalDocument->removeClient(*this); |
76 | } |
77 | |
78 | void SVGUseElement::parseAttribute(const QualifiedName& name, const AtomicString& value) |
79 | { |
80 | SVGParsingError parseError = NoError; |
81 | |
82 | if (name == SVGNames::xAttr) |
83 | m_x->setBaseValInternal(SVGLengthValue::construct(LengthModeWidth, value, parseError)); |
84 | else if (name == SVGNames::yAttr) |
85 | m_y->setBaseValInternal(SVGLengthValue::construct(LengthModeHeight, value, parseError)); |
86 | else if (name == SVGNames::widthAttr) |
87 | m_width->setBaseValInternal(SVGLengthValue::construct(LengthModeWidth, value, parseError, ForbidNegativeLengths)); |
88 | else if (name == SVGNames::heightAttr) |
89 | m_height->setBaseValInternal(SVGLengthValue::construct(LengthModeHeight, value, parseError, ForbidNegativeLengths)); |
90 | |
91 | reportAttributeParsingError(parseError, name, value); |
92 | |
93 | SVGExternalResourcesRequired::parseAttribute(name, value); |
94 | SVGGraphicsElement::parseAttribute(name, value); |
95 | SVGURIReference::parseAttribute(name, value); |
96 | } |
97 | |
98 | Node::InsertedIntoAncestorResult SVGUseElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree) |
99 | { |
100 | SVGGraphicsElement::insertedIntoAncestor(insertionType, parentOfInsertedTree); |
101 | if (insertionType.connectedToDocument) { |
102 | if (m_shadowTreeNeedsUpdate) |
103 | document().addSVGUseElement(*this); |
104 | SVGExternalResourcesRequired::insertedIntoDocument(); |
105 | invalidateShadowTree(); |
106 | // FIXME: Move back the call to updateExternalDocument() here once notifyFinished is made always async. |
107 | return InsertedIntoAncestorResult::NeedsPostInsertionCallback; |
108 | } |
109 | return InsertedIntoAncestorResult::Done; |
110 | } |
111 | |
112 | void SVGUseElement::didFinishInsertingNode() |
113 | { |
114 | updateExternalDocument(); |
115 | } |
116 | |
117 | void SVGUseElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree) |
118 | { |
119 | // Check m_shadowTreeNeedsUpdate before calling SVGElement::removedFromAncestor which calls SVGElement::invalidateInstances |
120 | // and SVGUseElement::updateExternalDocument which calls invalidateShadowTree(). |
121 | if (removalType.disconnectedFromDocument) { |
122 | if (m_shadowTreeNeedsUpdate) |
123 | document().removeSVGUseElement(*this); |
124 | } |
125 | SVGGraphicsElement::removedFromAncestor(removalType, oldParentOfRemovedTree); |
126 | if (removalType.disconnectedFromDocument) { |
127 | clearShadowTree(); |
128 | updateExternalDocument(); |
129 | } |
130 | } |
131 | |
132 | inline Document* SVGUseElement::externalDocument() const |
133 | { |
134 | return m_externalDocument ? m_externalDocument->document() : nullptr; |
135 | } |
136 | |
137 | void SVGUseElement::transferSizeAttributesToTargetClone(SVGElement& shadowElement) const |
138 | { |
139 | // FIXME: The check for valueInSpecifiedUnits being non-zero below is a workaround for the fact |
140 | // that we currently have no good way to tell whether a particular animatable attribute is a value |
141 | // indicating it was unspecified, or specified but could not be parsed. Would be nice to fix that some day. |
142 | if (is<SVGSymbolElement>(shadowElement)) { |
143 | // Spec (<use> on <symbol>): This generated 'svg' will always have explicit values for attributes width and height. |
144 | // If attributes width and/or height are provided on the 'use' element, then these attributes |
145 | // will be transferred to the generated 'svg'. If attributes width and/or height are not specified, |
146 | // the generated 'svg' element will use values of 100% for these attributes. |
147 | shadowElement.setAttribute(SVGNames::widthAttr, width().valueInSpecifiedUnits() ? AtomicString(width().valueAsString()) : "100%" ); |
148 | shadowElement.setAttribute(SVGNames::heightAttr, height().valueInSpecifiedUnits() ? AtomicString(height().valueAsString()) : "100%" ); |
149 | } else if (is<SVGSVGElement>(shadowElement)) { |
150 | // Spec (<use> on <svg>): If attributes width and/or height are provided on the 'use' element, then these |
151 | // values will override the corresponding attributes on the 'svg' in the generated tree. |
152 | auto correspondingElement = makeRefPtr(shadowElement.correspondingElement()); |
153 | shadowElement.setAttribute(SVGNames::widthAttr, width().valueInSpecifiedUnits() ? AtomicString(width().valueAsString()) : (correspondingElement ? correspondingElement->getAttribute(SVGNames::widthAttr) : nullAtom())); |
154 | shadowElement.setAttribute(SVGNames::heightAttr, height().valueInSpecifiedUnits() ? AtomicString(height().valueAsString()) : (correspondingElement ? correspondingElement->getAttribute(SVGNames::heightAttr) : nullAtom())); |
155 | } |
156 | } |
157 | |
158 | void SVGUseElement::svgAttributeChanged(const QualifiedName& attrName) |
159 | { |
160 | InstanceInvalidationGuard guard(*this); |
161 | |
162 | if (PropertyRegistry::isKnownAttribute(attrName)) { |
163 | updateRelativeLengthsInformation(); |
164 | if (attrName == SVGNames::widthAttr || attrName == SVGNames::heightAttr) { |
165 | // FIXME: It's unnecessarily inefficient to update both width and height each time either is changed. |
166 | if (auto targetClone = this->targetClone()) |
167 | transferSizeAttributesToTargetClone(*targetClone); |
168 | } |
169 | if (auto* renderer = this->renderer()) |
170 | RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer); |
171 | return; |
172 | } |
173 | |
174 | if (SVGURIReference::isKnownAttribute(attrName)) { |
175 | updateExternalDocument(); |
176 | invalidateShadowTree(); |
177 | return; |
178 | } |
179 | |
180 | if (SVGLangSpace::isKnownAttribute(attrName) || SVGExternalResourcesRequired::isKnownAttribute(attrName)) |
181 | invalidateShadowTree(); |
182 | |
183 | SVGGraphicsElement::svgAttributeChanged(attrName); |
184 | SVGExternalResourcesRequired::svgAttributeChanged(attrName); |
185 | } |
186 | |
187 | static HashSet<AtomicString> createAllowedElementSet() |
188 | { |
189 | // Spec: "Any 'svg', 'symbol', 'g', graphics element or other 'use' is potentially a template object that can be re-used |
190 | // (i.e., "instanced") in the SVG document via a 'use' element." |
191 | // "Graphics Element" is defined as 'circle', 'ellipse', 'image', 'line', 'path', 'polygon', 'polyline', 'rect', 'text' |
192 | // Excluded are anything that is used by reference or that only make sense to appear once in a document. |
193 | using namespace SVGNames; |
194 | HashSet<AtomicString> set; |
195 | for (auto& tag : { aTag.get(), circleTag.get(), descTag.get(), ellipseTag.get(), gTag.get(), imageTag.get(), lineTag.get(), metadataTag.get(), pathTag.get(), polygonTag.get(), polylineTag.get(), rectTag.get(), svgTag.get(), switchTag.get(), symbolTag.get(), textTag.get(), textPathTag.get(), titleTag.get(), trefTag.get(), tspanTag.get(), useTag.get() }) |
196 | set.add(tag.localName()); |
197 | return set; |
198 | } |
199 | |
200 | static inline bool isDisallowedElement(const SVGElement& element) |
201 | { |
202 | static NeverDestroyed<HashSet<AtomicString>> set = createAllowedElementSet(); |
203 | return !set.get().contains(element.localName()); |
204 | } |
205 | |
206 | static inline bool isDisallowedElement(const Element& element) |
207 | { |
208 | return !element.isSVGElement() || isDisallowedElement(downcast<SVGElement>(element)); |
209 | } |
210 | |
211 | void SVGUseElement::clearShadowTree() |
212 | { |
213 | if (auto root = userAgentShadowRoot()) { |
214 | // Safe because SVG use element's shadow tree is never used to fire synchronous events during layout or DOM mutations. |
215 | ScriptDisallowedScope::EventAllowedScope scope(*root); |
216 | root->removeChildren(); |
217 | } |
218 | } |
219 | |
220 | void SVGUseElement::buildPendingResource() |
221 | { |
222 | invalidateShadowTree(); |
223 | } |
224 | |
225 | void SVGUseElement::updateShadowTree() |
226 | { |
227 | m_shadowTreeNeedsUpdate = false; |
228 | |
229 | // FIXME: It's expensive to re-clone the entire tree every time. We should find a more efficient way to handle this. |
230 | clearShadowTree(); |
231 | |
232 | if (!isConnected()) |
233 | return; |
234 | document().removeSVGUseElement(*this); |
235 | |
236 | String targetID; |
237 | auto* target = findTarget(&targetID); |
238 | if (!target) { |
239 | document().accessSVGExtensions().addPendingResource(targetID, *this); |
240 | return; |
241 | } |
242 | |
243 | RELEASE_ASSERT(!isDescendantOf(target)); |
244 | { |
245 | auto& shadowRoot = ensureUserAgentShadowRoot(); |
246 | cloneTarget(shadowRoot, *target); |
247 | expandUseElementsInShadowTree(); |
248 | expandSymbolElementsInShadowTree(); |
249 | updateRelativeLengthsInformation(); |
250 | } |
251 | |
252 | transferEventListenersToShadowTree(); |
253 | |
254 | // When we invalidate the other shadow trees, it's important that we don't |
255 | // follow any cycles and invalidate ourselves. To avoid that, we temporarily |
256 | // set m_shadowTreeNeedsUpdate to true so invalidateShadowTree will |
257 | // quickly return and do nothing. |
258 | ASSERT(!m_shadowTreeNeedsUpdate); |
259 | m_shadowTreeNeedsUpdate = true; |
260 | invalidateDependentShadowTrees(); |
261 | m_shadowTreeNeedsUpdate = false; |
262 | } |
263 | |
264 | RefPtr<SVGElement> SVGUseElement::targetClone() const |
265 | { |
266 | auto root = userAgentShadowRoot(); |
267 | if (!root) |
268 | return nullptr; |
269 | return childrenOfType<SVGElement>(*root).first(); |
270 | } |
271 | |
272 | RenderPtr<RenderElement> SVGUseElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&) |
273 | { |
274 | return createRenderer<RenderSVGTransformableContainer>(*this, WTFMove(style)); |
275 | } |
276 | |
277 | static bool isDirectReference(const SVGElement& element) |
278 | { |
279 | using namespace SVGNames; |
280 | return element.hasTagName(circleTag) |
281 | || element.hasTagName(ellipseTag) |
282 | || element.hasTagName(pathTag) |
283 | || element.hasTagName(polygonTag) |
284 | || element.hasTagName(polylineTag) |
285 | || element.hasTagName(rectTag) |
286 | || element.hasTagName(textTag); |
287 | } |
288 | |
289 | Path SVGUseElement::toClipPath() |
290 | { |
291 | auto targetClone = this->targetClone(); |
292 | if (!is<SVGGraphicsElement>(targetClone)) |
293 | return { }; |
294 | |
295 | if (!isDirectReference(*targetClone)) { |
296 | // Spec: Indirect references are an error (14.3.5) |
297 | document().accessSVGExtensions().reportError("Not allowed to use indirect reference in <clip-path>"_s ); |
298 | return { }; |
299 | } |
300 | |
301 | Path path = downcast<SVGGraphicsElement>(*targetClone).toClipPath(); |
302 | SVGLengthContext lengthContext(this); |
303 | // FIXME: Find a way to do this without manual resolution of x/y here. It's potentially incorrect. |
304 | path.translate(FloatSize(x().value(lengthContext), y().value(lengthContext))); |
305 | path.transform(animatedLocalTransform()); |
306 | return path; |
307 | } |
308 | |
309 | RenderElement* SVGUseElement::rendererClipChild() const |
310 | { |
311 | auto targetClone = this->targetClone(); |
312 | if (!targetClone) |
313 | return nullptr; |
314 | if (!isDirectReference(*targetClone)) |
315 | return nullptr; |
316 | return targetClone->renderer(); |
317 | } |
318 | |
319 | static inline void disassociateAndRemoveClones(const Vector<Element*>& clones) |
320 | { |
321 | for (auto& clone : clones) { |
322 | for (auto& descendant : descendantsOfType<SVGElement>(*clone)) |
323 | descendant.setCorrespondingElement(nullptr); |
324 | if (is<SVGElement>(clone)) |
325 | downcast<SVGElement>(*clone).setCorrespondingElement(nullptr); |
326 | clone->parentNode()->removeChild(*clone); |
327 | } |
328 | } |
329 | |
330 | static void removeDisallowedElementsFromSubtree(SVGElement& subtree) |
331 | { |
332 | // Remove disallowed elements after the fact rather than not cloning them in the first place. |
333 | // This optimizes for the normal case where none of those elements are present. |
334 | |
335 | // This function is used only on elements in subtrees that are not yet in documents, so |
336 | // mutation events are not a factor; there are no event listeners to handle those events. |
337 | // Assert that it's not in a document to make sure callers are still using it this way. |
338 | ASSERT(!subtree.isConnected()); |
339 | |
340 | Vector<Element*> disallowedElements; |
341 | auto descendants = descendantsOfType<Element>(subtree); |
342 | for (auto it = descendants.begin(), end = descendants.end(); it != end; ) { |
343 | if (isDisallowedElement(*it)) { |
344 | disallowedElements.append(&*it); |
345 | it.traverseNextSkippingChildren(); |
346 | continue; |
347 | } |
348 | ++it; |
349 | } |
350 | |
351 | disassociateAndRemoveClones(disallowedElements); |
352 | } |
353 | |
354 | static void removeSymbolElementsFromSubtree(SVGElement& subtree) |
355 | { |
356 | // Symbol elements inside the subtree should not be cloned for two reasons: 1) They are invisible and |
357 | // don't need to be cloned to get correct rendering. 2) expandSymbolElementsInShadowTree will turn them |
358 | // into <svg> elements, which is correct for symbol elements directly referenced by use elements, |
359 | // but incorrect for ones that just happen to be in a subtree. |
360 | Vector<Element*> symbolElements; |
361 | for (auto& descendant : descendantsOfType<SVGSymbolElement>(subtree)) |
362 | symbolElements.append(&descendant); |
363 | disassociateAndRemoveClones(symbolElements); |
364 | } |
365 | |
366 | static void associateClonesWithOriginals(SVGElement& clone, SVGElement& original) |
367 | { |
368 | // This assertion checks that we don't call this with the arguments backwards. |
369 | // The clone is new and so it's not installed in a parent yet. |
370 | ASSERT(!clone.parentNode()); |
371 | |
372 | // The loop below works because we are associating these clones immediately, before |
373 | // doing transformations like removing disallowed elements or expanding elements. |
374 | clone.setCorrespondingElement(&original); |
375 | for (auto pair : descendantsOfType<SVGElement>(clone, original)) |
376 | pair.first.setCorrespondingElement(&pair.second); |
377 | } |
378 | |
379 | static void associateReplacementCloneWithOriginal(SVGElement& replacementClone, SVGElement& originalClone) |
380 | { |
381 | auto* correspondingElement = originalClone.correspondingElement(); |
382 | ASSERT(correspondingElement); |
383 | originalClone.setCorrespondingElement(nullptr); |
384 | replacementClone.setCorrespondingElement(correspondingElement); |
385 | } |
386 | |
387 | static void associateReplacementClonesWithOriginals(SVGElement& replacementClone, SVGElement& originalClone) |
388 | { |
389 | // This assertion checks that we don't call this with the arguments backwards. |
390 | // The replacement clone is new and so it's not installed in a parent yet. |
391 | ASSERT(!replacementClone.parentNode()); |
392 | |
393 | // The loop below works because we are associating these clones immediately, before |
394 | // doing transformations like removing disallowed elements or expanding elements. |
395 | associateReplacementCloneWithOriginal(replacementClone, originalClone); |
396 | for (auto pair : descendantsOfType<SVGElement>(replacementClone, originalClone)) |
397 | associateReplacementCloneWithOriginal(pair.first, pair.second); |
398 | } |
399 | |
400 | SVGElement* SVGUseElement::findTarget(String* targetID) const |
401 | { |
402 | auto* correspondingElement = this->correspondingElement(); |
403 | auto& original = correspondingElement ? downcast<SVGUseElement>(*correspondingElement) : *this; |
404 | |
405 | auto targetResult = targetElementFromIRIString(original.href(), original.treeScope(), original.externalDocument()); |
406 | if (targetID) { |
407 | *targetID = WTFMove(targetResult.identifier); |
408 | // If the reference is external, don't return the target ID to the caller. |
409 | // The caller would use the target ID to wait for a pending resource on the wrong document. |
410 | // If we ever want the change that and let the caller to wait on the external document, |
411 | // we should change this function so it returns the appropriate document to go with the ID. |
412 | if (!targetID->isNull() && isExternalURIReference(original.href(), original.document())) |
413 | *targetID = String { }; |
414 | } |
415 | if (!is<SVGElement>(targetResult.element)) |
416 | return nullptr; |
417 | auto& target = downcast<SVGElement>(*targetResult.element); |
418 | |
419 | if (!target.isConnected() || isDisallowedElement(target)) |
420 | return nullptr; |
421 | |
422 | if (correspondingElement) { |
423 | for (auto& ancestor : lineageOfType<SVGElement>(*this)) { |
424 | if (ancestor.correspondingElement() == &target) |
425 | return nullptr; |
426 | } |
427 | } else { |
428 | if (target.contains(this)) |
429 | return nullptr; |
430 | // Target should only refer to a node in the same tree or a node in another document. |
431 | ASSERT(!isDescendantOrShadowDescendantOf(&target)); |
432 | } |
433 | |
434 | return ⌖ |
435 | } |
436 | |
437 | void SVGUseElement::cloneTarget(ContainerNode& container, SVGElement& target) const |
438 | { |
439 | Ref<SVGElement> targetClone = static_cast<SVGElement&>(target.cloneElementWithChildren(document()).get()); |
440 | associateClonesWithOriginals(targetClone.get(), target); |
441 | removeDisallowedElementsFromSubtree(targetClone.get()); |
442 | removeSymbolElementsFromSubtree(targetClone.get()); |
443 | transferSizeAttributesToTargetClone(targetClone.get()); |
444 | container.appendChild(targetClone); |
445 | } |
446 | |
447 | static void cloneDataAndChildren(SVGElement& replacementClone, SVGElement& originalClone) |
448 | { |
449 | // This assertion checks that we don't call this with the arguments backwards. |
450 | // The replacement clone is new and so it's not installed in a parent yet. |
451 | ASSERT(!replacementClone.parentNode()); |
452 | |
453 | replacementClone.cloneDataFromElement(originalClone); |
454 | originalClone.cloneChildNodes(replacementClone); |
455 | associateReplacementClonesWithOriginals(replacementClone, originalClone); |
456 | removeDisallowedElementsFromSubtree(replacementClone); |
457 | } |
458 | |
459 | void SVGUseElement::expandUseElementsInShadowTree() const |
460 | { |
461 | auto descendants = descendantsOfType<SVGUseElement>(*userAgentShadowRoot()); |
462 | for (auto it = descendants.begin(), end = descendants.end(); it != end; ) { |
463 | SVGUseElement& originalClone = *it; |
464 | it = end; // Efficiently quiets assertions due to the outstanding iterator. |
465 | |
466 | auto* target = originalClone.findTarget(); |
467 | |
468 | // Spec: In the generated content, the 'use' will be replaced by 'g', where all attributes from the |
469 | // 'use' element except for x, y, width, height and xlink:href are transferred to the generated 'g' element. |
470 | |
471 | auto replacementClone = SVGGElement::create(document()); |
472 | |
473 | cloneDataAndChildren(replacementClone.get(), originalClone); |
474 | |
475 | replacementClone->removeAttribute(SVGNames::xAttr); |
476 | replacementClone->removeAttribute(SVGNames::yAttr); |
477 | replacementClone->removeAttribute(SVGNames::widthAttr); |
478 | replacementClone->removeAttribute(SVGNames::heightAttr); |
479 | replacementClone->removeAttribute(SVGNames::hrefAttr); |
480 | replacementClone->removeAttribute(XLinkNames::hrefAttr); |
481 | |
482 | if (target) |
483 | originalClone.cloneTarget(replacementClone.get(), *target); |
484 | |
485 | originalClone.parentNode()->replaceChild(replacementClone, originalClone); |
486 | |
487 | // Resume iterating, starting just inside the replacement clone. |
488 | it = descendants.from(replacementClone.get()); |
489 | } |
490 | } |
491 | |
492 | void SVGUseElement::expandSymbolElementsInShadowTree() const |
493 | { |
494 | auto descendants = descendantsOfType<SVGSymbolElement>(*userAgentShadowRoot()); |
495 | for (auto it = descendants.begin(), end = descendants.end(); it != end; ) { |
496 | SVGSymbolElement& originalClone = *it; |
497 | it = end; // Efficiently quiets assertions due to the outstanding iterator. |
498 | |
499 | // Spec: The referenced 'symbol' and its contents are deep-cloned into the generated tree, |
500 | // with the exception that the 'symbol' is replaced by an 'svg'. This generated 'svg' will |
501 | // always have explicit values for attributes width and height. If attributes width and/or |
502 | // height are provided on the 'use' element, then these attributes will be transferred to |
503 | // the generated 'svg'. If attributes width and/or height are not specified, the generated |
504 | // 'svg' element will use values of 100% for these attributes. |
505 | |
506 | auto replacementClone = SVGSVGElement::create(document()); |
507 | cloneDataAndChildren(replacementClone.get(), originalClone); |
508 | |
509 | originalClone.parentNode()->replaceChild(replacementClone, originalClone); |
510 | |
511 | // Resume iterating, starting just inside the replacement clone. |
512 | it = descendants.from(replacementClone.get()); |
513 | } |
514 | } |
515 | |
516 | void SVGUseElement::transferEventListenersToShadowTree() const |
517 | { |
518 | // FIXME: Don't directly add event listeners on each descendant. Copy event listeners on the use element instead. |
519 | for (auto& descendant : descendantsOfType<SVGElement>(*userAgentShadowRoot())) { |
520 | if (EventTargetData* data = descendant.correspondingElement()->eventTargetData()) |
521 | data->eventListenerMap.copyEventListenersNotCreatedFromMarkupToTarget(&descendant); |
522 | } |
523 | } |
524 | |
525 | void SVGUseElement::invalidateShadowTree() |
526 | { |
527 | if (m_shadowTreeNeedsUpdate) |
528 | return; |
529 | m_shadowTreeNeedsUpdate = true; |
530 | invalidateStyleAndRenderersForSubtree(); |
531 | invalidateDependentShadowTrees(); |
532 | if (isConnected()) |
533 | document().addSVGUseElement(*this); |
534 | } |
535 | |
536 | void SVGUseElement::invalidateDependentShadowTrees() |
537 | { |
538 | for (auto* instance : instances()) { |
539 | if (auto element = instance->correspondingUseElement()) |
540 | element->invalidateShadowTree(); |
541 | } |
542 | } |
543 | |
544 | bool SVGUseElement::selfHasRelativeLengths() const |
545 | { |
546 | if (x().isRelative() || y().isRelative() || width().isRelative() || height().isRelative()) |
547 | return true; |
548 | |
549 | auto targetClone = this->targetClone(); |
550 | return targetClone && targetClone->hasRelativeLengths(); |
551 | } |
552 | |
553 | void SVGUseElement::notifyFinished(CachedResource& resource) |
554 | { |
555 | ASSERT(ScriptDisallowedScope::InMainThread::isScriptAllowed()); |
556 | invalidateShadowTree(); |
557 | if (resource.errorOccurred()) |
558 | dispatchEvent(Event::create(eventNames().errorEvent, Event::CanBubble::No, Event::IsCancelable::No)); |
559 | else if (!resource.wasCanceled()) |
560 | SVGExternalResourcesRequired::dispatchLoadEvent(); |
561 | } |
562 | |
563 | void SVGUseElement::finishParsingChildren() |
564 | { |
565 | SVGGraphicsElement::finishParsingChildren(); |
566 | SVGExternalResourcesRequired::finishParsingChildren(); |
567 | } |
568 | |
569 | void SVGUseElement::updateExternalDocument() |
570 | { |
571 | URL externalDocumentURL; |
572 | if (isConnected() && isExternalURIReference(href(), document())) { |
573 | externalDocumentURL = document().completeURL(href()); |
574 | if (!externalDocumentURL.hasFragmentIdentifier()) |
575 | externalDocumentURL = URL(); |
576 | } |
577 | |
578 | if (externalDocumentURL == (m_externalDocument ? m_externalDocument->url() : URL())) |
579 | return; |
580 | |
581 | if (m_externalDocument) |
582 | m_externalDocument->removeClient(*this); |
583 | |
584 | if (externalDocumentURL.isNull()) |
585 | m_externalDocument = nullptr; |
586 | else { |
587 | ResourceLoaderOptions options = CachedResourceLoader::defaultCachedResourceOptions(); |
588 | options.contentSecurityPolicyImposition = isInUserAgentShadowTree() ? ContentSecurityPolicyImposition::SkipPolicyCheck : ContentSecurityPolicyImposition::DoPolicyCheck; |
589 | options.mode = FetchOptions::Mode::SameOrigin; |
590 | CachedResourceRequest request { ResourceRequest { externalDocumentURL }, options }; |
591 | request.setInitiator(*this); |
592 | m_externalDocument = document().cachedResourceLoader().requestSVGDocument(WTFMove(request)).value_or(nullptr); |
593 | if (m_externalDocument) |
594 | m_externalDocument->addClient(*this); |
595 | } |
596 | |
597 | invalidateShadowTree(); |
598 | } |
599 | |
600 | bool SVGUseElement::isValid() const |
601 | { |
602 | return SVGTests::isValid(); |
603 | } |
604 | |
605 | bool SVGUseElement::haveLoadedRequiredResources() |
606 | { |
607 | return SVGExternalResourcesRequired::haveLoadedRequiredResources(); |
608 | } |
609 | |
610 | void SVGUseElement::setHaveFiredLoadEvent(bool haveFiredLoadEvent) |
611 | { |
612 | m_haveFiredLoadEvent = haveFiredLoadEvent; |
613 | } |
614 | |
615 | bool SVGUseElement::haveFiredLoadEvent() const |
616 | { |
617 | return m_haveFiredLoadEvent; |
618 | } |
619 | |
620 | Timer* SVGUseElement::svgLoadEventTimer() |
621 | { |
622 | return &m_svgLoadEventTimer; |
623 | } |
624 | |
625 | } |
626 | |