/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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/ArrayUtils.h" #include "mozilla/dom/SVGUseElement.h" #include "mozilla/dom/SVGUseElementBinding.h" #include "nsGkAtoms.h" #include "mozilla/dom/SVGSVGElement.h" #include "nsIDocument.h" #include "nsIPresShell.h" #include "mozilla/dom/Element.h" #include "nsContentUtils.h" #include "nsIURI.h" #include "nsSVGEffects.h" NS_IMPL_NS_NEW_NAMESPACED_SVG_ELEMENT(Use) namespace mozilla { namespace dom { JSObject* SVGUseElement::WrapNode(JSContext *aCx, JS::Handle aGivenProto) { return SVGUseElementBinding::Wrap(aCx, this, aGivenProto); } //////////////////////////////////////////////////////////////////////// // implementation nsSVGElement::LengthInfo SVGUseElement::sLengthInfo[4] = { { &nsGkAtoms::x, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::X }, { &nsGkAtoms::y, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::Y }, { &nsGkAtoms::width, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::X }, { &nsGkAtoms::height, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::Y }, }; nsSVGElement::StringInfo SVGUseElement::sStringInfo[2] = { { &nsGkAtoms::href, kNameSpaceID_None, true }, { &nsGkAtoms::href, kNameSpaceID_XLink, true } }; //---------------------------------------------------------------------- // nsISupports methods NS_IMPL_CYCLE_COLLECTION_CLASS(SVGUseElement) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SVGUseElement, SVGUseElementBase) nsAutoScriptBlocker scriptBlocker; NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginal) tmp->DestroyAnonymousContent(); tmp->UnlinkSource(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SVGUseElement, SVGUseElementBase) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClone) tmp->mSource.Traverse(&cb); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_ADDREF_INHERITED(SVGUseElement,SVGUseElementBase) NS_IMPL_RELEASE_INHERITED(SVGUseElement,SVGUseElementBase) NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(SVGUseElement) NS_INTERFACE_TABLE_INHERITED(SVGUseElement, nsIMutationObserver) NS_INTERFACE_TABLE_TAIL_INHERITING(SVGUseElementBase) //---------------------------------------------------------------------- // Implementation SVGUseElement::SVGUseElement(already_AddRefed& aNodeInfo) : SVGUseElementBase(aNodeInfo), mSource(this) { } SVGUseElement::~SVGUseElement() { UnlinkSource(); } //---------------------------------------------------------------------- // nsIDOMNode methods nsresult SVGUseElement::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const { *aResult = nullptr; already_AddRefed ni = RefPtr(aNodeInfo).forget(); SVGUseElement *it = new SVGUseElement(ni); nsCOMPtr kungFuDeathGrip(it); nsresult rv1 = it->Init(); nsresult rv2 = const_cast(this)->CopyInnerTo(it); // SVGUseElement specific portion - record who we cloned from it->mOriginal = const_cast(this); if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2)) { kungFuDeathGrip.swap(*aResult); } return NS_FAILED(rv1) ? rv1 : rv2; } already_AddRefed SVGUseElement::Href() { return mStringAttributes[HREF].IsExplicitlySet() ? mStringAttributes[HREF].ToDOMAnimatedString(this) : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); } //---------------------------------------------------------------------- already_AddRefed SVGUseElement::X() { return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); } already_AddRefed SVGUseElement::Y() { return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); } already_AddRefed SVGUseElement::Width() { return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); } already_AddRefed SVGUseElement::Height() { return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this); } //---------------------------------------------------------------------- // nsIMutationObserver methods void SVGUseElement::CharacterDataChanged(nsIDocument *aDocument, nsIContent *aContent, CharacterDataChangeInfo* aInfo) { if (nsContentUtils::IsInSameAnonymousTree(this, aContent)) { TriggerReclone(); } } void SVGUseElement::AttributeChanged(nsIDocument* aDocument, Element* aElement, int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType, const nsAttrValue* aOldValue) { if (nsContentUtils::IsInSameAnonymousTree(this, aElement)) { TriggerReclone(); } } void SVGUseElement::ContentAppended(nsIDocument *aDocument, nsIContent *aContainer, nsIContent *aFirstNewContent, int32_t aNewIndexInContainer) { if (nsContentUtils::IsInSameAnonymousTree(this, aContainer)) { TriggerReclone(); } } void SVGUseElement::ContentInserted(nsIDocument *aDocument, nsIContent *aContainer, nsIContent *aChild, int32_t aIndexInContainer) { if (nsContentUtils::IsInSameAnonymousTree(this, aChild)) { TriggerReclone(); } } void SVGUseElement::ContentRemoved(nsIDocument *aDocument, nsIContent *aContainer, nsIContent *aChild, int32_t aIndexInContainer, nsIContent *aPreviousSibling) { if (nsContentUtils::IsInSameAnonymousTree(this, aChild)) { TriggerReclone(); } } void SVGUseElement::NodeWillBeDestroyed(const nsINode *aNode) { nsCOMPtr kungFuDeathGrip(this); UnlinkSource(); } //---------------------------------------------------------------------- nsIContent* SVGUseElement::CreateAnonymousContent() { mClone = nullptr; if (mSource.get()) { mSource.get()->RemoveMutationObserver(this); } LookupHref(); nsIContent* targetContent = mSource.get(); if (!targetContent) return nullptr; // make sure target is valid type for // QIable nsSVGGraphicsElement would eliminate enumerating all elements if (!targetContent->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol, nsGkAtoms::g, nsGkAtoms::path, nsGkAtoms::text, nsGkAtoms::rect, nsGkAtoms::circle, nsGkAtoms::ellipse, nsGkAtoms::line, nsGkAtoms::polyline, nsGkAtoms::polygon, nsGkAtoms::image, nsGkAtoms::use)) return nullptr; // circular loop detection // check 1 - check if we're a document descendent of the target if (nsContentUtils::ContentIsDescendantOf(this, targetContent)) return nullptr; // check 2 - check if we're a clone, and if we already exist in the hierarchy if (GetParent() && mOriginal) { for (nsCOMPtr content = GetParent(); content; content = content->GetParent()) { if (content->IsSVGElement(nsGkAtoms::use) && static_cast(content.get())->mOriginal == mOriginal) { return nullptr; } } } nsCOMArray unused; nsNodeInfoManager* nodeInfoManager = targetContent->OwnerDoc() == OwnerDoc() ? nullptr : OwnerDoc()->NodeInfoManager(); IgnoredErrorResult rv; nsCOMPtr newnode = nsNodeUtils::Clone(targetContent, true, nodeInfoManager, &unused, rv); nsCOMPtr newcontent = do_QueryInterface(newnode); if (!newcontent) return nullptr; if (newcontent->IsSVGElement(nsGkAtoms::symbol)) { nsIDocument *document = GetComposedDoc(); if (!document) return nullptr; nsNodeInfoManager *nodeInfoManager = document->NodeInfoManager(); if (!nodeInfoManager) return nullptr; RefPtr nodeInfo; nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::svg, nullptr, kNameSpaceID_SVG, nsIDOMNode::ELEMENT_NODE); nsCOMPtr svgNode; NS_NewSVGSVGElement(getter_AddRefs(svgNode), nodeInfo.forget(), NOT_FROM_PARSER); if (!svgNode) return nullptr; // copy attributes BorrowedAttrInfo info; uint32_t i; for (i = 0; (info = newcontent->GetAttrInfoAt(i)); i++) { nsAutoString value; int32_t nsID = info.mName->NamespaceID(); nsIAtom* lname = info.mName->LocalName(); info.mValue->ToString(value); svgNode->SetAttr(nsID, lname, info.mName->GetPrefix(), value, false); } // move the children over uint32_t num = newcontent->GetChildCount(); for (i = 0; i < num; i++) { nsCOMPtr child = newcontent->GetFirstChild(); newcontent->RemoveChildAt(0, false); svgNode->InsertChildAt(child, i, true); } newcontent = svgNode; } if (newcontent->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol)) { nsSVGElement *newElement = static_cast(newcontent.get()); if (mLengthAttributes[ATTR_WIDTH].IsExplicitlySet()) newElement->SetLength(nsGkAtoms::width, mLengthAttributes[ATTR_WIDTH]); if (mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet()) newElement->SetLength(nsGkAtoms::height, mLengthAttributes[ATTR_HEIGHT]); } // Set up its base URI correctly nsCOMPtr baseURI = targetContent->GetBaseURI(); if (!baseURI) return nullptr; newcontent->SetExplicitBaseURI(baseURI); targetContent->AddMutationObserver(this); mClone = newcontent; #ifdef DEBUG // Our anonymous clone can get restyled by various things // (e.g. SMIL). Reconstructing its frame is OK, though, because // it's going to be our _only_ child in the frame tree, so can't get // mis-ordered with anything. mClone->SetProperty(nsGkAtoms::restylableAnonymousNode, reinterpret_cast(true)); #endif // DEBUG return mClone; } nsIURI* SVGUseElement::GetSourceDocURI() { nsIContent* targetContent = mSource.get(); if (!targetContent) return nullptr; return targetContent->OwnerDoc()->GetDocumentURI(); } void SVGUseElement::DestroyAnonymousContent() { nsContentUtils::DestroyAnonymousContent(&mClone); } bool SVGUseElement::OurWidthAndHeightAreUsed() const { return mClone && mClone->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol); } //---------------------------------------------------------------------- // implementation helpers void SVGUseElement::SyncWidthOrHeight(nsIAtom* aName) { NS_ASSERTION(aName == nsGkAtoms::width || aName == nsGkAtoms::height, "The clue is in the function name"); NS_ASSERTION(OurWidthAndHeightAreUsed(), "Don't call this"); if (OurWidthAndHeightAreUsed()) { nsSVGElement *target = static_cast(mClone.get()); uint32_t index = *sLengthInfo[ATTR_WIDTH].mName == aName ? ATTR_WIDTH : ATTR_HEIGHT; if (mLengthAttributes[index].IsExplicitlySet()) { target->SetLength(aName, mLengthAttributes[index]); return; } if (mClone->IsSVGElement(nsGkAtoms::svg)) { // Our width/height attribute is now no longer explicitly set, so we // need to revert the clone's width/height to the width/height of the // content that's being cloned. TriggerReclone(); return; } // Our width/height attribute is now no longer explicitly set, so we // need to set the value to 100% nsSVGLength2 length; length.Init(SVGContentUtils::XY, 0xff, 100, nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE); target->SetLength(aName, length); return; } } void SVGUseElement::LookupHref() { nsAutoString href; if (mStringAttributes[HREF].IsExplicitlySet()) { mStringAttributes[HREF].GetAnimValue(href, this); } else { mStringAttributes[XLINK_HREF].GetAnimValue(href, this); } if (href.IsEmpty()) { return; } nsCOMPtr originURI = mOriginal ? mOriginal->GetBaseURI() : GetBaseURI(); nsCOMPtr baseURI = nsContentUtils::IsLocalRefURL(href) ? nsSVGEffects::GetBaseURLForLocalRef(this, originURI) : originURI; nsCOMPtr targetURI; nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), href, GetComposedDoc(), baseURI); // Do not allow 'data:' schemes in elements. // See spec update: https://github.com/w3c/svgwg/pull/901 if (targetURI) { bool isData; mozilla::Unused << targetURI->SchemeIs("data", &isData); if (isData) { return; } } mSource.Reset(this, targetURI); } void SVGUseElement::TriggerReclone() { nsIDocument *doc = GetComposedDoc(); if (!doc) return; nsIPresShell *presShell = doc->GetShell(); if (!presShell) return; presShell->PostRecreateFramesFor(this); } void SVGUseElement::UnlinkSource() { if (mSource.get()) { mSource.get()->RemoveMutationObserver(this); } mSource.Unlink(); } //---------------------------------------------------------------------- // nsSVGElement methods /* virtual */ gfxMatrix SVGUseElement::PrependLocalTransformsTo( const gfxMatrix &aMatrix, SVGTransformTypes aWhich) const { // 'transform' attribute: gfxMatrix fromUserSpace = SVGUseElementBase::PrependLocalTransformsTo(aMatrix, aWhich); if (aWhich == eUserSpaceToParent) { return fromUserSpace; } // our 'x' and 'y' attributes: float x, y; const_cast(this)->GetAnimatedLengthValues(&x, &y, nullptr); gfxMatrix toUserSpace = gfxMatrix::Translation(x, y); if (aWhich == eChildToUserSpace) { return toUserSpace * aMatrix; } MOZ_ASSERT(aWhich == eAllTransforms, "Unknown TransformTypes"); return toUserSpace * fromUserSpace; } /* virtual */ bool SVGUseElement::HasValidDimensions() const { return (!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() || mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) && (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() || mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0); } nsSVGElement::LengthAttributesInfo SVGUseElement::GetLengthInfo() { return LengthAttributesInfo(mLengthAttributes, sLengthInfo, ArrayLength(sLengthInfo)); } nsSVGElement::StringAttributesInfo SVGUseElement::GetStringInfo() { return StringAttributesInfo(mStringAttributes, sStringInfo, ArrayLength(sStringInfo)); } //---------------------------------------------------------------------- // nsIContent methods NS_IMETHODIMP_(bool) SVGUseElement::IsAttributeMapped(const nsIAtom* name) const { static const MappedAttributeEntry* const map[] = { sFEFloodMap, sFiltersMap, sFontSpecificationMap, sGradientStopMap, sLightingEffectsMap, sMarkersMap, sTextContentElementsMap, sViewportsMap }; return FindAttributeDependence(name, map) || SVGUseElementBase::IsAttributeMapped(name); } } // namespace dom } // namespace mozilla