diff options
Diffstat (limited to 'dom/svg/SVGUseElement.cpp')
-rw-r--r-- | dom/svg/SVGUseElement.cpp | 519 |
1 files changed, 519 insertions, 0 deletions
diff --git a/dom/svg/SVGUseElement.cpp b/dom/svg/SVGUseElement.cpp new file mode 100644 index 000000000..4911e2cac --- /dev/null +++ b/dom/svg/SVGUseElement.cpp @@ -0,0 +1,519 @@ +/* -*- 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/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" + +NS_IMPL_NS_NEW_NAMESPACED_SVG_ELEMENT(Use) + +namespace mozilla { +namespace dom { + +JSObject* +SVGUseElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> 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<mozilla::dom::NodeInfo>& aNodeInfo) + : SVGUseElementBase(aNodeInfo), mSource(this) +{ +} + +SVGUseElement::~SVGUseElement() +{ + UnlinkSource(); +} + +//---------------------------------------------------------------------- +// nsIDOMNode methods + +nsresult +SVGUseElement::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const +{ + *aResult = nullptr; + already_AddRefed<mozilla::dom::NodeInfo> ni = RefPtr<mozilla::dom::NodeInfo>(aNodeInfo).forget(); + SVGUseElement *it = new SVGUseElement(ni); + + nsCOMPtr<nsINode> kungFuDeathGrip(it); + nsresult rv1 = it->Init(); + nsresult rv2 = const_cast<SVGUseElement*>(this)->CopyInnerTo(it); + + // SVGUseElement specific portion - record who we cloned from + it->mOriginal = const_cast<SVGUseElement*>(this); + + if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2)) { + kungFuDeathGrip.swap(*aResult); + } + + return NS_FAILED(rv1) ? rv1 : rv2; +} + +already_AddRefed<SVGAnimatedString> +SVGUseElement::Href() +{ + return mStringAttributes[HREF].IsExplicitlySet() + ? mStringAttributes[HREF].ToDOMAnimatedString(this) + : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- + +already_AddRefed<SVGAnimatedLength> +SVGUseElement::X() +{ + return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); +} + +already_AddRefed<SVGAnimatedLength> +SVGUseElement::Y() +{ + return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); +} + +already_AddRefed<SVGAnimatedLength> +SVGUseElement::Width() +{ + return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed<SVGAnimatedLength> +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<nsIMutationObserver> 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 <use> + // 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<nsIContent> content = GetParent(); + content; + content = content->GetParent()) { + if (content->IsSVGElement(nsGkAtoms::use) && + static_cast<SVGUseElement*>(content.get())->mOriginal == mOriginal) { + return nullptr; + } + } + } + + nsCOMPtr<nsINode> newnode; + nsCOMArray<nsINode> unused; + nsNodeInfoManager* nodeInfoManager = + targetContent->OwnerDoc() == OwnerDoc() ? + nullptr : OwnerDoc()->NodeInfoManager(); + nsNodeUtils::Clone(targetContent, true, nodeInfoManager, unused, + getter_AddRefs(newnode)); + + nsCOMPtr<nsIContent> 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<mozilla::dom::NodeInfo> nodeInfo; + nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::svg, nullptr, + kNameSpaceID_SVG, + nsIDOMNode::ELEMENT_NODE); + + nsCOMPtr<nsIContent> 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<nsIContent> 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<nsSVGElement*>(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<nsIURI> 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<void*>(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<nsSVGElement*>(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<nsIURI> targetURI; + nsCOMPtr<nsIURI> baseURI = mOriginal ? mOriginal->GetBaseURI() : GetBaseURI(); + nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), href, + GetComposedDoc(), baseURI); + + 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<SVGUseElement*>(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 |