/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/SVGMPathElement.h" #include "nsDebug.h" #include "mozilla/ArrayUtils.h" #include "mozilla/SVGObserverUtils.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/SVGAnimateMotionElement.h" #include "mozilla/dom/SVGGeometryElement.h" #include "nsContentUtils.h" #include "nsIReferrerInfo.h" #include "mozilla/dom/SVGMPathElementBinding.h" #include "nsIURI.h" NS_IMPL_NS_NEW_SVG_ELEMENT(MPath) namespace mozilla::dom { JSObject* SVGMPathElement::WrapNode(JSContext* aCx, JS::Handle aGivenProto) { return SVGMPathElement_Binding::Wrap(aCx, this, aGivenProto); } SVGElement::StringInfo SVGMPathElement::sStringInfo[2] = { {nsGkAtoms::href, kNameSpaceID_None, false}, {nsGkAtoms::href, kNameSpaceID_XLink, false}}; // Cycle collection magic -- based on SVGUseElement NS_IMPL_CYCLE_COLLECTION_CLASS(SVGMPathElement) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SVGMPathElement, SVGMPathElementBase) tmp->UnlinkHrefTarget(false); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SVGMPathElement, SVGMPathElementBase) tmp->mPathTracker.Traverse(&cb); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END //---------------------------------------------------------------------- // nsISupports methods NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(SVGMPathElement, SVGMPathElementBase, nsIMutationObserver) // Constructor SVGMPathElement::SVGMPathElement( already_AddRefed&& aNodeInfo) : SVGMPathElementBase(std::move(aNodeInfo)), mPathTracker(this) {} SVGMPathElement::~SVGMPathElement() { UnlinkHrefTarget(false); } //---------------------------------------------------------------------- // nsINode methods NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGMPathElement) already_AddRefed SVGMPathElement::Href() { return mStringAttributes[HREF].IsExplicitlySet() ? mStringAttributes[HREF].ToDOMAnimatedString(this) : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); } //---------------------------------------------------------------------- // nsIContent methods nsresult SVGMPathElement::BindToTree(BindContext& aContext, nsINode& aParent) { MOZ_ASSERT(!mPathTracker.get(), "Shouldn't have href-target yet (or it should've been cleared)"); nsresult rv = SVGMPathElementBase::BindToTree(aContext, aParent); NS_ENSURE_SUCCESS(rv, rv); if (IsInComposedDoc()) { const nsAttrValue* hrefAttrValue = HasAttr(kNameSpaceID_None, nsGkAtoms::href) ? mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_None) : mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink); if (hrefAttrValue) { UpdateHrefTarget(nsIContent::FromNode(aParent), hrefAttrValue->GetStringValue()); } } return NS_OK; } void SVGMPathElement::UnbindFromTree(bool aNullParent) { UnlinkHrefTarget(true); SVGMPathElementBase::UnbindFromTree(aNullParent); } void SVGMPathElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, const nsAttrValue* aValue, const nsAttrValue* aOldValue, nsIPrincipal* aMaybeScriptedPrincipal, bool aNotify) { if (aName == nsGkAtoms::href) { if (aValue) { if ((aNamespaceID == kNameSpaceID_XLink || aNamespaceID == kNameSpaceID_None) && IsInComposedDoc()) { // Note: If we fail the IsInComposedDoc call, it's ok -- we'll update // the target on next BindToTree call. // Note: "href" takes priority over xlink:href. So if "xlink:href" is // being set here, we only let that update our target if "href" is // *unset*. if (aNamespaceID != kNameSpaceID_XLink || !mStringAttributes[HREF].IsExplicitlySet()) { UpdateHrefTarget(GetParent(), aValue->GetStringValue()); } } } else { // href attr being removed. if (aNamespaceID == kNameSpaceID_None) { UnlinkHrefTarget(true); // After unsetting href, we may still have xlink:href, so we should // try to add it back. const nsAttrValue* xlinkHref = mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink); if (xlinkHref) { UpdateHrefTarget(GetParent(), xlinkHref->GetStringValue()); } } else if (aNamespaceID == kNameSpaceID_XLink && !HasAttr(nsGkAtoms::href)) { UnlinkHrefTarget(true); } // else: we unset some random-namespace href attribute, or unset // xlink:href but still have href attribute, so keep the target linking // to href. } } return SVGMPathElementBase::AfterSetAttr( aNamespaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify); } //---------------------------------------------------------------------- // SVGElement methods SVGElement::StringAttributesInfo SVGMPathElement::GetStringInfo() { return StringAttributesInfo(mStringAttributes, sStringInfo, ArrayLength(sStringInfo)); } //---------------------------------------------------------------------- // nsIMutationObserver methods void SVGMPathElement::AttributeChanged(Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType, const nsAttrValue* aOldValue) { if (aNameSpaceID == kNameSpaceID_None) { if (aAttribute == nsGkAtoms::d) { NotifyParentOfMpathChange(GetParent()); } } } //---------------------------------------------------------------------- // Public helper methods SVGGeometryElement* SVGMPathElement::GetReferencedPath() { if (!HasAttr(kNameSpaceID_XLink, nsGkAtoms::href) && !HasAttr(kNameSpaceID_None, nsGkAtoms::href)) { MOZ_ASSERT(!mPathTracker.get(), "We shouldn't have a href target " "if we don't have an xlink:href or href attribute"); return nullptr; } return SVGGeometryElement::FromNodeOrNull(mPathTracker.get()); } //---------------------------------------------------------------------- // Protected helper methods void SVGMPathElement::UpdateHrefTarget(nsIContent* aParent, const nsAString& aHrefStr) { nsCOMPtr baseURI = GetBaseURI(); if (nsContentUtils::IsLocalRefURL(aHrefStr)) { baseURI = SVGObserverUtils::GetBaseURLForLocalRef(this, baseURI); } nsCOMPtr targetURI; nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), aHrefStr, OwnerDoc(), baseURI); // Stop observing old target (if any) if (mPathTracker.get()) { mPathTracker.get()->RemoveMutationObserver(this); } if (aParent) { // Pass in |aParent| instead of |this| -- first argument is only used // for a call to GetComposedDoc(), and |this| might not have a current // document yet (if our caller is BindToTree). nsCOMPtr referrerInfo = OwnerDoc()->ReferrerInfoForInternalCSSAndSVGResources(); mPathTracker.ResetToURIFragmentID(aParent, targetURI, referrerInfo); } else { // if we don't have a parent, then there's no animateMotion element // depending on our target, so there's no point tracking it right now. mPathTracker.Unlink(); } // Start observing new target (if any) if (mPathTracker.get()) { mPathTracker.get()->AddMutationObserver(this); } NotifyParentOfMpathChange(aParent); } void SVGMPathElement::UnlinkHrefTarget(bool aNotifyParent) { // Stop observing old target (if any) if (mPathTracker.get()) { mPathTracker.get()->RemoveMutationObserver(this); } mPathTracker.Unlink(); if (aNotifyParent) { NotifyParentOfMpathChange(GetParent()); } } void SVGMPathElement::NotifyParentOfMpathChange(nsIContent* aParent) { if (auto* animateMotionParent = SVGAnimateMotionElement::FromNodeOrNull(aParent)) { animateMotionParent->MpathChanged(); AnimationNeedsResample(); } } } // namespace mozilla::dom