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
45namespace WebCore {
46
47WTF_MAKE_ISO_ALLOCATED_IMPL(SVGUseElement);
48
49inline 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
67Ref<SVGUseElement> SVGUseElement::create(const QualifiedName& tagName, Document& document)
68{
69 return adoptRef(*new SVGUseElement(tagName, document));
70}
71
72SVGUseElement::~SVGUseElement()
73{
74 if (m_externalDocument)
75 m_externalDocument->removeClient(*this);
76}
77
78void 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
98Node::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
112void SVGUseElement::didFinishInsertingNode()
113{
114 updateExternalDocument();
115}
116
117void 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
132inline Document* SVGUseElement::externalDocument() const
133{
134 return m_externalDocument ? m_externalDocument->document() : nullptr;
135}
136
137void 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
158void 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
187static 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
200static inline bool isDisallowedElement(const SVGElement& element)
201{
202 static NeverDestroyed<HashSet<AtomicString>> set = createAllowedElementSet();
203 return !set.get().contains(element.localName());
204}
205
206static inline bool isDisallowedElement(const Element& element)
207{
208 return !element.isSVGElement() || isDisallowedElement(downcast<SVGElement>(element));
209}
210
211void 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
220void SVGUseElement::buildPendingResource()
221{
222 invalidateShadowTree();
223}
224
225void 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
264RefPtr<SVGElement> SVGUseElement::targetClone() const
265{
266 auto root = userAgentShadowRoot();
267 if (!root)
268 return nullptr;
269 return childrenOfType<SVGElement>(*root).first();
270}
271
272RenderPtr<RenderElement> SVGUseElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
273{
274 return createRenderer<RenderSVGTransformableContainer>(*this, WTFMove(style));
275}
276
277static 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
289Path 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
309RenderElement* 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
319static 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
330static 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
354static 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
366static 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
379static 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
387static 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
400SVGElement* 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 &target;
435}
436
437void 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
447static 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
459void 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
492void 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
516void 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
525void 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
536void SVGUseElement::invalidateDependentShadowTrees()
537{
538 for (auto* instance : instances()) {
539 if (auto element = instance->correspondingUseElement())
540 element->invalidateShadowTree();
541 }
542}
543
544bool 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
553void 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
563void SVGUseElement::finishParsingChildren()
564{
565 SVGGraphicsElement::finishParsingChildren();
566 SVGExternalResourcesRequired::finishParsingChildren();
567}
568
569void 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
600bool SVGUseElement::isValid() const
601{
602 return SVGTests::isValid();
603}
604
605bool SVGUseElement::haveLoadedRequiredResources()
606{
607 return SVGExternalResourcesRequired::haveLoadedRequiredResources();
608}
609
610void SVGUseElement::setHaveFiredLoadEvent(bool haveFiredLoadEvent)
611{
612 m_haveFiredLoadEvent = haveFiredLoadEvent;
613}
614
615bool SVGUseElement::haveFiredLoadEvent() const
616{
617 return m_haveFiredLoadEvent;
618}
619
620Timer* SVGUseElement::svgLoadEventTimer()
621{
622 return &m_svgLoadEventTimer;
623}
624
625}
626