1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/SVGUseElement.h"
9 #include "mozilla/ArrayUtils.h"
10 #include "mozilla/ErrorResult.h"
11 #include "mozilla/ScopeExit.h"
12 #include "mozilla/StaticPrefs_svg.h"
13 #include "mozilla/SVGObserverUtils.h"
14 #include "mozilla/SVGUseFrame.h"
15 #include "mozilla/URLExtraData.h"
16 #include "mozilla/dom/Document.h"
17 #include "mozilla/dom/ReferrerInfo.h"
18 #include "mozilla/dom/ShadowIncludingTreeIterator.h"
19 #include "mozilla/dom/SVGGraphicsElement.h"
20 #include "mozilla/dom/SVGLengthBinding.h"
21 #include "mozilla/dom/SVGSVGElement.h"
22 #include "mozilla/dom/SVGSwitchElement.h"
23 #include "mozilla/dom/SVGSymbolElement.h"
24 #include "mozilla/dom/SVGUseElementBinding.h"
25 #include "nsGkAtoms.h"
26 #include "nsContentUtils.h"
27 #include "nsIReferrerInfo.h"
29 #include "SVGGeometryProperty.h"
31 NS_IMPL_NS_NEW_SVG_ELEMENT(Use
)
33 namespace mozilla::dom
{
35 JSObject
* SVGUseElement::WrapNode(JSContext
* aCx
,
36 JS::Handle
<JSObject
*> aGivenProto
) {
37 return SVGUseElement_Binding::Wrap(aCx
, this, aGivenProto
);
40 ////////////////////////////////////////////////////////////////////////
43 SVGElement::LengthInfo
SVGUseElement::sLengthInfo
[4] = {
44 {nsGkAtoms::x
, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER
,
46 {nsGkAtoms::y
, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER
,
48 {nsGkAtoms::width
, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER
,
50 {nsGkAtoms::height
, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER
,
54 SVGElement::StringInfo
SVGUseElement::sStringInfo
[2] = {
55 {nsGkAtoms::href
, kNameSpaceID_None
, true},
56 {nsGkAtoms::href
, kNameSpaceID_XLink
, true}};
58 //----------------------------------------------------------------------
59 // nsISupports methods
61 NS_IMPL_CYCLE_COLLECTION_CLASS(SVGUseElement
)
63 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SVGUseElement
,
65 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginal
)
67 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
68 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SVGUseElement
,
70 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginal
)
71 tmp
->mReferencedElementTracker
.Traverse(&cb
);
72 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
74 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(SVGUseElement
, SVGUseElementBase
,
77 //----------------------------------------------------------------------
80 SVGUseElement::SVGUseElement(
81 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
)
82 : SVGUseElementBase(std::move(aNodeInfo
)), mReferencedElementTracker(this) {
83 SetEnabledCallbacks(kCharacterDataChanged
| kAttributeChanged
|
84 kContentAppended
| kContentInserted
|
85 kContentWillBeRemoved
| kNodeWillBeDestroyed
);
88 SVGUseElement::~SVGUseElement() {
90 MOZ_DIAGNOSTIC_ASSERT(!OwnerDoc()->SVGUseElementNeedsShadowTreeUpdate(*this),
91 "Dying without unbinding?");
94 namespace SVGT
= SVGGeometryProperty::Tags
;
96 //----------------------------------------------------------------------
99 void SVGUseElement::ProcessAttributeChange(int32_t aNamespaceID
,
100 nsAtom
* aAttribute
) {
101 if (OwnerDoc()->CloningForSVGUse()) {
104 if (aNamespaceID
== kNameSpaceID_None
) {
105 if (aAttribute
== nsGkAtoms::width
|| aAttribute
== nsGkAtoms::height
) {
106 const bool hadValidDimensions
= HasValidDimensions();
107 const bool isUsed
= OurWidthAndHeightAreUsed();
109 SyncWidthOrHeight(aAttribute
);
112 if (auto* frame
= GetFrame()) {
113 frame
->DimensionAttributeChanged(hadValidDimensions
, isUsed
);
118 if ((aNamespaceID
== kNameSpaceID_XLink
||
119 aNamespaceID
== kNameSpaceID_None
) &&
120 aAttribute
== nsGkAtoms::href
) {
121 // We're changing our nature, clear out the clone information.
122 if (auto* frame
= GetFrame()) {
123 frame
->HrefChanged();
130 void SVGUseElement::DidAnimateAttribute(int32_t aNameSpaceID
,
131 nsAtom
* aAttribute
) {
132 ProcessAttributeChange(aNameSpaceID
, aAttribute
);
135 void SVGUseElement::AfterSetAttr(int32_t aNamespaceID
, nsAtom
* aAttribute
,
136 const nsAttrValue
* aValue
,
137 const nsAttrValue
* aOldValue
,
138 nsIPrincipal
* aSubjectPrincipal
,
140 ProcessAttributeChange(aNamespaceID
, aAttribute
);
141 return SVGUseElementBase::AfterSetAttr(aNamespaceID
, aAttribute
, aValue
,
142 aOldValue
, aSubjectPrincipal
, aNotify
);
145 nsresult
SVGUseElement::Clone(dom::NodeInfo
* aNodeInfo
,
146 nsINode
** aResult
) const {
149 new (aNodeInfo
->NodeInfoManager()) SVGUseElement(do_AddRef(aNodeInfo
));
151 nsCOMPtr
<nsINode
> kungFuDeathGrip(it
);
152 nsresult rv1
= it
->Init();
153 nsresult rv2
= const_cast<SVGUseElement
*>(this)->CopyInnerTo(it
);
155 if (aNodeInfo
->GetDocument()->CloningForSVGUse()) {
156 // SVGUseElement specific portion - record who we cloned from
157 it
->mOriginal
= const_cast<SVGUseElement
*>(this);
160 if (NS_SUCCEEDED(rv1
) && NS_SUCCEEDED(rv2
)) {
161 kungFuDeathGrip
.swap(*aResult
);
164 return NS_FAILED(rv1
) ? rv1
: rv2
;
167 nsresult
SVGUseElement::BindToTree(BindContext
& aContext
, nsINode
& aParent
) {
168 nsresult rv
= SVGUseElementBase::BindToTree(aContext
, aParent
);
169 NS_ENSURE_SUCCESS(rv
, rv
);
175 void SVGUseElement::UnbindFromTree(UnbindContext
& aContext
) {
176 SVGUseElementBase::UnbindFromTree(aContext
);
177 OwnerDoc()->UnscheduleSVGUseElementShadowTreeUpdate(*this);
180 already_AddRefed
<DOMSVGAnimatedString
> SVGUseElement::Href() {
181 return mStringAttributes
[HREF
].IsExplicitlySet()
182 ? mStringAttributes
[HREF
].ToDOMAnimatedString(this)
183 : mStringAttributes
[XLINK_HREF
].ToDOMAnimatedString(this);
186 //----------------------------------------------------------------------
188 already_AddRefed
<DOMSVGAnimatedLength
> SVGUseElement::X() {
189 return mLengthAttributes
[ATTR_X
].ToDOMAnimatedLength(this);
192 already_AddRefed
<DOMSVGAnimatedLength
> SVGUseElement::Y() {
193 return mLengthAttributes
[ATTR_Y
].ToDOMAnimatedLength(this);
196 already_AddRefed
<DOMSVGAnimatedLength
> SVGUseElement::Width() {
197 return mLengthAttributes
[ATTR_WIDTH
].ToDOMAnimatedLength(this);
200 already_AddRefed
<DOMSVGAnimatedLength
> SVGUseElement::Height() {
201 return mLengthAttributes
[ATTR_HEIGHT
].ToDOMAnimatedLength(this);
204 //----------------------------------------------------------------------
205 // nsIMutationObserver methods
207 void SVGUseElement::CharacterDataChanged(nsIContent
* aContent
,
208 const CharacterDataChangeInfo
&) {
209 if (nsContentUtils::IsInSameAnonymousTree(mReferencedElementTracker
.get(),
215 void SVGUseElement::AttributeChanged(Element
* aElement
, int32_t aNamespaceID
,
216 nsAtom
* aAttribute
, int32_t aModType
,
217 const nsAttrValue
* aOldValue
) {
218 if (nsContentUtils::IsInSameAnonymousTree(mReferencedElementTracker
.get(),
224 void SVGUseElement::ContentAppended(nsIContent
* aFirstNewContent
) {
225 // FIXME(emilio, bug 1442336): Why does this check the parent but
226 // ContentInserted the child?
227 if (nsContentUtils::IsInSameAnonymousTree(mReferencedElementTracker
.get(),
228 aFirstNewContent
->GetParent())) {
233 void SVGUseElement::ContentInserted(nsIContent
* aChild
) {
234 // FIXME(emilio, bug 1442336): Why does this check the child but
235 // ContentAppended the parent?
236 if (nsContentUtils::IsInSameAnonymousTree(mReferencedElementTracker
.get(),
242 void SVGUseElement::ContentWillBeRemoved(nsIContent
* aChild
) {
243 if (nsContentUtils::IsInSameAnonymousTree(mReferencedElementTracker
.get(),
249 void SVGUseElement::NodeWillBeDestroyed(nsINode
* aNode
) {
250 nsCOMPtr
<nsIMutationObserver
> kungFuDeathGrip(this);
254 // Returns whether this node could ever be displayed.
255 static bool NodeCouldBeRendered(const nsINode
& aNode
) {
256 if (const auto* symbol
= SVGSymbolElement::FromNode(aNode
)) {
257 return symbol
->CouldBeRendered();
259 if (auto* svgSwitch
=
260 SVGSwitchElement::FromNodeOrNull(aNode
.GetParentNode())) {
261 if (&aNode
!= svgSwitch
->GetActiveChild()) {
264 } else if (const auto* svgGraphics
= SVGGraphicsElement::FromNode(aNode
)) {
265 if (!svgGraphics
->PassesConditionalProcessingTests()) {
272 // <svg:use> can be used (no pun intended) to trivially cause an explosion of
273 // clones that could potentially DoS the browser. We have a configurable limit
275 static bool IsTooMuchRecursion(uint32_t aCount
) {
276 switch (StaticPrefs::svg_use_element_recursive_clone_limit_enabled()) {
282 if (!XRE_IsParentProcess()) {
287 return aCount
>= StaticPrefs::svg_use_element_recursive_clone_limit();
290 // Circular loop detection, plus detection of whether this shadow tree is
292 auto SVGUseElement::ScanAncestors(const Element
& aTarget
) const -> ScanResult
{
294 return ScanAncestorsInternal(aTarget
, count
);
297 auto SVGUseElement::ScanAncestorsInternal(
298 const Element
& aTarget
, uint32_t& aCount
) const -> ScanResult
{
299 if (&aTarget
== this) {
300 return ScanResult::CyclicReference
;
303 if (IsTooMuchRecursion(++aCount
)) {
304 return ScanResult::TooDeep
;
306 auto result
= mOriginal
->ScanAncestorsInternal(aTarget
, aCount
);
308 case ScanResult::TooDeep
:
309 case ScanResult::CyclicReference
:
312 case ScanResult::Invisible
:
317 auto result
= ScanResult::Ok
;
318 for (nsINode
* parent
= GetParentOrShadowHostNode(); parent
;
319 parent
= parent
->GetParentOrShadowHostNode()) {
320 if (parent
== &aTarget
) {
321 return ScanResult::CyclicReference
;
323 if (auto* use
= SVGUseElement::FromNode(*parent
)) {
324 if (IsTooMuchRecursion(++aCount
)) {
325 return ScanResult::TooDeep
;
327 if (mOriginal
&& use
->mOriginal
== mOriginal
) {
328 return ScanResult::CyclicReference
;
331 // Do we have other similar cases we can optimize out easily?
332 if (!NodeCouldBeRendered(*parent
)) {
333 // NOTE(emilio): We can't just return here. If we're cyclic, we need to
335 result
= ScanResult::Invisible
;
341 //----------------------------------------------------------------------
343 static bool IsForbiddenUseNode(const nsINode
& aNode
) {
344 if (!aNode
.IsElement()) {
347 const auto* svg
= SVGElement::FromNode(aNode
);
348 return !svg
|| !svg
->IsSVGGraphicsElement();
351 static void CollectForbiddenNodes(Element
& aRoot
,
352 nsTArray
<RefPtr
<nsINode
>>& aNodes
) {
353 auto iter
= dom::ShadowIncludingTreeIterator(aRoot
);
355 nsINode
* node
= *iter
;
356 if (IsForbiddenUseNode(*node
)) {
357 aNodes
.AppendElement(node
);
365 // SVG1 restricted <use> trees to SVGGraphicsElements.
366 // https://www.w3.org/TR/SVG11/struct.html#UseElement:
368 // Any ‘svg’, ‘symbol’, ‘g’, graphics element or other ‘use’ is potentially a
369 // template object that can be re-used (i.e., "instanced") in the SVG
370 // document via a ‘use’ element. The ‘use’ element references another element
371 // and indicates that the graphical contents of that element is
372 // included/drawn at that given point in the document.
374 // SVG2 doesn't have that same restriction.
375 // https://www.w3.org/TR/SVG2/struct.html#UseShadowTree:
377 // Previous versions of SVG restricted the contents of the shadow tree to SVG
378 // graphics elements. This specification allows any valid SVG document
379 // subtree to be cloned. Cloning non-graphical content, however, will not
380 // usually have any visible effect.
382 // But it's pretty ambiguous as to what the behavior should be for some
383 // elements, because <script> is inert, but <iframe> is not, see:
384 // https://github.com/w3c/svgwg/issues/876
386 // So, fairly confusing, all-in-all.
387 static void RemoveForbiddenNodes(Element
& aRoot
, bool aIsCrossDocument
) {
388 switch (StaticPrefs::svg_use_element_graphics_element_restrictions()) {
392 if (!aIsCrossDocument
) {
400 AutoTArray
<RefPtr
<nsINode
>, 10> unsafeNodes
;
401 CollectForbiddenNodes(aRoot
, unsafeNodes
);
402 for (auto& unsafeNode
: unsafeNodes
) {
403 unsafeNode
->Remove();
407 void SVGUseElement::UpdateShadowTree() {
408 MOZ_ASSERT(IsInComposedDoc());
410 if (mReferencedElementTracker
.get()) {
411 mReferencedElementTracker
.get()->RemoveMutationObserver(this);
416 RefPtr
<ShadowRoot
> shadow
= GetShadowRoot();
418 shadow
= AttachShadowWithoutNameChecks(ShadowRootMode::Closed
);
422 auto* targetElement
=
423 SVGGraphicsElement::FromNodeOrNull(mReferencedElementTracker
.get());
424 RefPtr
<Element
> newElement
;
426 auto UpdateShadowTree
= mozilla::MakeScopeExit([&]() {
427 if (nsIContent
* firstChild
= shadow
->GetFirstChild()) {
428 MOZ_ASSERT(!firstChild
->GetNextSibling());
429 shadow
->RemoveChildNode(firstChild
, /* aNotify = */ true);
433 shadow
->AppendChildTo(newElement
, /* aNotify = */ true, IgnoreErrors());
437 // make sure target is valid type for <use>
438 if (!targetElement
) {
442 if (ScanAncestors(*targetElement
) != ScanResult::Ok
) {
446 nsCOMPtr
<nsIURI
> baseURI
= targetElement
->GetBaseURI();
452 const bool isCrossDocument
= targetElement
->OwnerDoc() != OwnerDoc();
454 nsNodeInfoManager
* nodeInfoManager
=
455 isCrossDocument
? OwnerDoc()->NodeInfoManager() : nullptr;
457 nsCOMPtr
<nsINode
> newNode
=
458 targetElement
->Clone(true, nodeInfoManager
, IgnoreErrors());
463 MOZ_ASSERT(newNode
->IsElement());
464 newElement
= newNode
.forget().downcast
<Element
>();
465 RemoveForbiddenNodes(*newElement
, isCrossDocument
);
468 if (newElement
->IsAnyOfSVGElements(nsGkAtoms::svg
, nsGkAtoms::symbol
)) {
469 auto* newSVGElement
= static_cast<SVGElement
*>(newElement
.get());
470 if (mLengthAttributes
[ATTR_WIDTH
].IsExplicitlySet())
471 newSVGElement
->SetLength(nsGkAtoms::width
, mLengthAttributes
[ATTR_WIDTH
]);
472 if (mLengthAttributes
[ATTR_HEIGHT
].IsExplicitlySet())
473 newSVGElement
->SetLength(nsGkAtoms::height
,
474 mLengthAttributes
[ATTR_HEIGHT
]);
477 // Bug 1415044 the specs do not say which referrer information we should use.
478 // This may change if there's any spec comes out.
479 auto referrerInfo
= MakeRefPtr
<ReferrerInfo
>(*this);
480 mContentURLData
= new URLExtraData(baseURI
.forget(), referrerInfo
.forget(),
481 do_AddRef(NodePrincipal()));
483 targetElement
->AddMutationObserver(this);
486 nsIURI
* SVGUseElement::GetSourceDocURI() {
487 nsIContent
* targetElement
= mReferencedElementTracker
.get();
488 if (!targetElement
) {
492 return targetElement
->OwnerDoc()->GetDocumentURI();
495 const Encoding
* SVGUseElement::GetSourceDocCharacterSet() {
496 nsIContent
* targetElement
= mReferencedElementTracker
.get();
497 if (!targetElement
) {
501 return targetElement
->OwnerDoc()->GetDocumentCharacterSet();
504 static nsINode
* GetClonedChild(const SVGUseElement
& aUseElement
) {
505 const ShadowRoot
* shadow
= aUseElement
.GetShadowRoot();
506 return shadow
? shadow
->GetFirstChild() : nullptr;
509 bool SVGUseElement::OurWidthAndHeightAreUsed() const {
510 nsINode
* clonedChild
= GetClonedChild(*this);
511 return clonedChild
&&
512 clonedChild
->IsAnyOfSVGElements(nsGkAtoms::svg
, nsGkAtoms::symbol
);
515 //----------------------------------------------------------------------
516 // implementation helpers
518 void SVGUseElement::SyncWidthOrHeight(nsAtom
* aName
) {
519 NS_ASSERTION(aName
== nsGkAtoms::width
|| aName
== nsGkAtoms::height
,
520 "The clue is in the function name");
521 NS_ASSERTION(OurWidthAndHeightAreUsed(), "Don't call this");
523 if (!OurWidthAndHeightAreUsed()) {
527 auto* target
= SVGElement::FromNode(GetClonedChild(*this));
529 sLengthInfo
[ATTR_WIDTH
].mName
== aName
? ATTR_WIDTH
: ATTR_HEIGHT
;
531 if (mLengthAttributes
[index
].IsExplicitlySet()) {
532 target
->SetLength(aName
, mLengthAttributes
[index
]);
535 if (target
->IsSVGElement(nsGkAtoms::svg
)) {
536 // Our width/height attribute is now no longer explicitly set, so we
537 // need to revert the clone's width/height to the width/height of the
538 // content that's being cloned.
542 // Our width/height attribute is now no longer explicitly set, so we
543 // need to set the value to 100%
544 SVGAnimatedLength length
;
545 length
.Init(SVGContentUtils::XY
, 0xff, 100,
546 SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE
);
547 target
->SetLength(aName
, length
);
550 void SVGUseElement::LookupHref() {
552 if (mStringAttributes
[HREF
].IsExplicitlySet()) {
553 mStringAttributes
[HREF
].GetAnimValue(href
, this);
555 mStringAttributes
[XLINK_HREF
].GetAnimValue(href
, this);
558 if (href
.IsEmpty()) {
562 Element
* treeToWatch
= mOriginal
? mOriginal
.get() : this;
563 if (nsContentUtils::IsLocalRefURL(href
)) {
564 mReferencedElementTracker
.ResetToLocalFragmentID(*treeToWatch
, href
);
568 nsCOMPtr
<nsIURI
> baseURI
= treeToWatch
->GetBaseURI();
569 nsCOMPtr
<nsIURI
> targetURI
;
570 nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI
), href
,
571 GetComposedDoc(), baseURI
);
576 // Don't allow <use href="data:...">. Using "#ref" inside a data: document is
578 if (targetURI
->SchemeIs("data") &&
579 !StaticPrefs::svg_use_element_data_url_href_allowed()) {
583 nsIReferrerInfo
* referrer
=
584 OwnerDoc()->ReferrerInfoForInternalCSSAndSVGResources();
585 mReferencedElementTracker
.ResetToURIWithFragmentID(treeToWatch
, targetURI
,
589 void SVGUseElement::TriggerReclone() {
590 if (Document
* doc
= GetComposedDoc()) {
591 doc
->ScheduleSVGUseElementShadowTreeUpdate(*this);
595 void SVGUseElement::UnlinkSource() {
596 if (mReferencedElementTracker
.get()) {
597 mReferencedElementTracker
.get()->RemoveMutationObserver(this);
599 mReferencedElementTracker
.Unlink();
602 //----------------------------------------------------------------------
603 // SVGElement methods
606 gfxMatrix
SVGUseElement::ChildToUserSpaceTransform() const {
608 if (!SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
>(this, &x
, &y
)) {
609 const_cast<SVGUseElement
*>(this)->GetAnimatedLengthValues(&x
, &y
, nullptr);
611 return gfxMatrix::Translation(x
, y
);
615 bool SVGUseElement::HasValidDimensions() const {
616 if (!OurWidthAndHeightAreUsed()) {
620 return (!mLengthAttributes
[ATTR_WIDTH
].IsExplicitlySet() ||
621 mLengthAttributes
[ATTR_WIDTH
].GetAnimValInSpecifiedUnits() > 0) &&
622 (!mLengthAttributes
[ATTR_HEIGHT
].IsExplicitlySet() ||
623 mLengthAttributes
[ATTR_HEIGHT
].GetAnimValInSpecifiedUnits() > 0);
626 SVGElement::LengthAttributesInfo
SVGUseElement::GetLengthInfo() {
627 return LengthAttributesInfo(mLengthAttributes
, sLengthInfo
,
628 std::size(sLengthInfo
));
631 SVGElement::StringAttributesInfo
SVGUseElement::GetStringInfo() {
632 return StringAttributesInfo(mStringAttributes
, sStringInfo
,
633 std::size(sStringInfo
));
636 SVGUseFrame
* SVGUseElement::GetFrame() const {
637 nsIFrame
* frame
= GetPrimaryFrame();
638 // We might be a plain SVGContainerFrame if we didn't pass the conditional
639 // processing checks.
640 if (!frame
|| !frame
->IsSVGUseFrame()) {
641 MOZ_ASSERT_IF(frame
, frame
->Type() == LayoutFrameType::None
);
644 return static_cast<SVGUseFrame
*>(frame
);
647 //----------------------------------------------------------------------
648 // nsIContent methods
651 SVGUseElement::IsAttributeMapped(const nsAtom
* name
) const {
652 return name
== nsGkAtoms::x
|| name
== nsGkAtoms::y
||
653 SVGUseElementBase::IsAttributeMapped(name
);
656 nsCSSPropertyID
SVGUseElement::GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum
) {
659 return eCSSProperty_x
;
661 return eCSSProperty_y
;
663 // Currently we don't map width or height to style
664 return eCSSProperty_UNKNOWN
;
668 } // namespace mozilla::dom