summaryrefslogtreecommitdiffstats
path: root/dom/svg/SVGUseElement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/svg/SVGUseElement.cpp')
-rw-r--r--dom/svg/SVGUseElement.cpp519
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