summaryrefslogtreecommitdiffstats
path: root/dom/svg/SVGAnimationElement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/svg/SVGAnimationElement.cpp')
-rw-r--r--dom/svg/SVGAnimationElement.cpp488
1 files changed, 488 insertions, 0 deletions
diff --git a/dom/svg/SVGAnimationElement.cpp b/dom/svg/SVGAnimationElement.cpp
new file mode 100644
index 000000000..d6550c96e
--- /dev/null
+++ b/dom/svg/SVGAnimationElement.cpp
@@ -0,0 +1,488 @@
+/* -*- 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/SVGAnimationElement.h"
+#include "mozilla/dom/SVGSVGElement.h"
+#include "nsSMILTimeContainer.h"
+#include "nsSMILAnimationController.h"
+#include "nsSMILAnimationFunction.h"
+#include "nsContentUtils.h"
+#include "nsIContentInlines.h"
+#include "nsIURI.h"
+#include "prtime.h"
+
+namespace mozilla {
+namespace dom {
+
+//----------------------------------------------------------------------
+// nsISupports methods
+
+NS_IMPL_ADDREF_INHERITED(SVGAnimationElement, SVGAnimationElementBase)
+NS_IMPL_RELEASE_INHERITED(SVGAnimationElement, SVGAnimationElementBase)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGAnimationElement)
+ NS_INTERFACE_MAP_ENTRY(mozilla::dom::SVGTests)
+NS_INTERFACE_MAP_END_INHERITING(SVGAnimationElementBase)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(SVGAnimationElement,
+ SVGAnimationElementBase,
+ mHrefTarget, mTimedElement)
+
+//----------------------------------------------------------------------
+// Implementation
+
+SVGAnimationElement::SVGAnimationElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : SVGAnimationElementBase(aNodeInfo),
+ mHrefTarget(this)
+{
+}
+
+SVGAnimationElement::~SVGAnimationElement()
+{
+}
+
+nsresult
+SVGAnimationElement::Init()
+{
+ nsresult rv = SVGAnimationElementBase::Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mTimedElement.SetAnimationElement(this);
+ AnimationFunction().SetAnimationElement(this);
+ mTimedElement.SetTimeClient(&AnimationFunction());
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+
+const nsAttrValue*
+SVGAnimationElement::GetAnimAttr(nsIAtom* aName) const
+{
+ return mAttrsAndChildren.GetAttr(aName, kNameSpaceID_None);
+}
+
+bool
+SVGAnimationElement::GetAnimAttr(nsIAtom* aAttName,
+ nsAString& aResult) const
+{
+ return GetAttr(kNameSpaceID_None, aAttName, aResult);
+}
+
+bool
+SVGAnimationElement::HasAnimAttr(nsIAtom* aAttName) const
+{
+ return HasAttr(kNameSpaceID_None, aAttName);
+}
+
+Element*
+SVGAnimationElement::GetTargetElementContent()
+{
+ if (HasAttr(kNameSpaceID_XLink, nsGkAtoms::href) ||
+ HasAttr(kNameSpaceID_None, nsGkAtoms::href)) {
+ return mHrefTarget.get();
+ }
+ MOZ_ASSERT(!mHrefTarget.get(),
+ "We shouldn't have a href target "
+ "if we don't have an xlink:href or href attribute");
+
+ // No "href" or "xlink:href" attribute --> I should target my parent.
+ nsIContent* parent = GetFlattenedTreeParent();
+ return parent && parent->IsElement() ? parent->AsElement() : nullptr;
+}
+
+bool
+SVGAnimationElement::GetTargetAttributeName(int32_t *aNamespaceID,
+ nsIAtom **aLocalName) const
+{
+ const nsAttrValue* nameAttr
+ = mAttrsAndChildren.GetAttr(nsGkAtoms::attributeName);
+
+ if (!nameAttr)
+ return false;
+
+ NS_ASSERTION(nameAttr->Type() == nsAttrValue::eAtom,
+ "attributeName should have been parsed as an atom");
+
+ return NS_SUCCEEDED(nsContentUtils::SplitQName(
+ this, nsDependentAtomString(nameAttr->GetAtomValue()),
+ aNamespaceID, aLocalName));
+}
+
+nsSMILTargetAttrType
+SVGAnimationElement::GetTargetAttributeType() const
+{
+ nsIContent::AttrValuesArray typeValues[] = { &nsGkAtoms::css,
+ &nsGkAtoms::XML,
+ nullptr};
+ nsSMILTargetAttrType smilTypes[] = { eSMILTargetAttrType_CSS,
+ eSMILTargetAttrType_XML };
+ int32_t index = FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::attributeType,
+ typeValues,
+ eCaseMatters);
+ return (index >= 0) ? smilTypes[index] : eSMILTargetAttrType_auto;
+}
+
+nsSMILTimedElement&
+SVGAnimationElement::TimedElement()
+{
+ return mTimedElement;
+}
+
+nsSVGElement*
+SVGAnimationElement::GetTargetElement()
+{
+ FlushAnimations();
+
+ // We'll just call the other GetTargetElement method, and QI to the right type
+ nsIContent* target = GetTargetElementContent();
+
+ return (target && target->IsSVGElement())
+ ? static_cast<nsSVGElement*>(target) : nullptr;
+}
+
+float
+SVGAnimationElement::GetStartTime(ErrorResult& rv)
+{
+ FlushAnimations();
+
+ nsSMILTimeValue startTime = mTimedElement.GetStartTime();
+ if (!startTime.IsDefinite()) {
+ rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return 0.f;
+ }
+
+ return float(double(startTime.GetMillis()) / PR_MSEC_PER_SEC);
+}
+
+float
+SVGAnimationElement::GetCurrentTime()
+{
+ // Not necessary to call FlushAnimations() for this
+
+ nsSMILTimeContainer* root = GetTimeContainer();
+ if (root) {
+ return float(double(root->GetCurrentTime()) / PR_MSEC_PER_SEC);
+ }
+
+ return 0.0f;
+}
+
+float
+SVGAnimationElement::GetSimpleDuration(ErrorResult& rv)
+{
+ // Not necessary to call FlushAnimations() for this
+
+ nsSMILTimeValue simpleDur = mTimedElement.GetSimpleDuration();
+ if (!simpleDur.IsDefinite()) {
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return 0.f;
+ }
+
+ return float(double(simpleDur.GetMillis()) / PR_MSEC_PER_SEC);
+}
+
+//----------------------------------------------------------------------
+// nsIContent methods
+
+nsresult
+SVGAnimationElement::BindToTree(nsIDocument* aDocument,
+ nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ MOZ_ASSERT(!mHrefTarget.get(),
+ "Shouldn't have href-target yet (or it should've been cleared)");
+ nsresult rv = SVGAnimationElementBase::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // XXXdholbert is GetCtx (as a check for SVG parent) still needed here?
+ if (!GetCtx()) {
+ // No use proceeding. We don't have an SVG parent (yet) so we won't be able
+ // to register ourselves etc. Maybe next time we'll have more luck.
+ // (This sort of situation will arise a lot when trees are being constructed
+ // piece by piece via script)
+ return NS_OK;
+ }
+
+ // Add myself to the animation controller's master set of animation elements.
+ if (aDocument) {
+ nsSMILAnimationController *controller = aDocument->GetAnimationController();
+ if (controller) {
+ controller->RegisterAnimationElement(this);
+ }
+ const nsAttrValue* href =
+ HasAttr(kNameSpaceID_None, nsGkAtoms::href)
+ ? mAttrsAndChildren.GetAttr(nsGkAtoms::href, kNameSpaceID_None)
+ : mAttrsAndChildren.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink);
+ if (href) {
+ nsAutoString hrefStr;
+ href->ToString(hrefStr);
+
+ // 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.
+ UpdateHrefTarget(aParent, hrefStr);
+ }
+
+ mTimedElement.BindToTree(aParent);
+ }
+
+ AnimationNeedsResample();
+
+ return NS_OK;
+}
+
+void
+SVGAnimationElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ nsSMILAnimationController *controller = OwnerDoc()->GetAnimationController();
+ if (controller) {
+ controller->UnregisterAnimationElement(this);
+ }
+
+ mHrefTarget.Unlink();
+ mTimedElement.DissolveReferences();
+
+ AnimationNeedsResample();
+
+ SVGAnimationElementBase::UnbindFromTree(aDeep, aNullParent);
+}
+
+bool
+SVGAnimationElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ // Deal with target-related attributes here
+ if (aAttribute == nsGkAtoms::attributeName ||
+ aAttribute == nsGkAtoms::attributeType) {
+ aResult.ParseAtom(aValue);
+ AnimationNeedsResample();
+ return true;
+ }
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ // First let the animation function try to parse it...
+ bool foundMatch =
+ AnimationFunction().SetAttr(aAttribute, aValue, aResult, &rv);
+
+ // ... and if that didn't recognize the attribute, let the timed element
+ // try to parse it.
+ if (!foundMatch) {
+ foundMatch =
+ mTimedElement.SetAttr(aAttribute, aValue, aResult, this, &rv);
+ }
+
+ if (foundMatch) {
+ AnimationNeedsResample();
+ if (NS_FAILED(rv)) {
+ ReportAttributeParseFailure(OwnerDoc(), aAttribute, aValue);
+ return false;
+ }
+ return true;
+ }
+ }
+
+ return SVGAnimationElementBase::ParseAttribute(aNamespaceID, aAttribute,
+ aValue, aResult);
+}
+
+nsresult
+SVGAnimationElement::AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ nsresult rv =
+ SVGAnimationElementBase::AfterSetAttr(aNamespaceID, aName, aValue,
+ aNotify);
+
+ if (SVGTests::IsConditionalProcessingAttribute(aName)) {
+ bool isDisabled = !SVGTests::PassesConditionalProcessingTests();
+ if (mTimedElement.SetIsDisabled(isDisabled)) {
+ AnimationNeedsResample();
+ }
+ }
+
+ if (!((aNamespaceID == kNameSpaceID_None ||
+ aNamespaceID == kNameSpaceID_XLink) &&
+ aName == nsGkAtoms::href)) {
+ return rv;
+ }
+
+ if (!aValue) {
+ if (aNamespaceID == kNameSpaceID_None) {
+ mHrefTarget.Unlink();
+ AnimationTargetChanged();
+
+ // After unsetting href, we may still have xlink:href, so we
+ // should try to add it back.
+ const nsAttrValue* xlinkHref =
+ mAttrsAndChildren.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink);
+ if (xlinkHref) {
+ UpdateHrefTarget(this, xlinkHref->GetStringValue());
+ }
+ } else if (!HasAttr(kNameSpaceID_None, nsGkAtoms::href)) {
+ mHrefTarget.Unlink();
+ AnimationTargetChanged();
+ } // else: we unset xlink:href, but we still have href attribute, so keep
+ // mHrefTarget linking to href.
+ } else if (IsInUncomposedDoc() &&
+ !(aNamespaceID == kNameSpaceID_XLink &&
+ HasAttr(kNameSpaceID_None, nsGkAtoms::href))) {
+ // 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*.
+ MOZ_ASSERT(aValue->Type() == nsAttrValue::eString,
+ "Expected href attribute to be string type");
+ UpdateHrefTarget(this, aValue->GetStringValue());
+ } // else: we're not yet in a document -- we'll update the target on
+ // next BindToTree call.
+
+ return rv;
+}
+
+nsresult
+SVGAnimationElement::UnsetAttr(int32_t aNamespaceID,
+ nsIAtom* aAttribute, bool aNotify)
+{
+ nsresult rv = SVGAnimationElementBase::UnsetAttr(aNamespaceID, aAttribute,
+ aNotify);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (AnimationFunction().UnsetAttr(aAttribute) ||
+ mTimedElement.UnsetAttr(aAttribute)) {
+ AnimationNeedsResample();
+ }
+ }
+
+ return NS_OK;
+}
+
+bool
+SVGAnimationElement::IsNodeOfType(uint32_t aFlags) const
+{
+ return !(aFlags & ~(eCONTENT | eANIMATION));
+}
+
+//----------------------------------------------------------------------
+// SVGTests methods
+
+bool
+SVGAnimationElement::IsInChromeDoc() const
+{
+ return nsContentUtils::IsChromeDoc(OwnerDoc());
+}
+
+//----------------------------------------------------------------------
+// SVG utility methods
+
+void
+SVGAnimationElement::ActivateByHyperlink()
+{
+ FlushAnimations();
+
+ // The behavior for when the target is an animation element is defined in
+ // SMIL Animation:
+ // http://www.w3.org/TR/smil-animation/#HyperlinkSemantics
+ nsSMILTimeValue seekTime = mTimedElement.GetHyperlinkTime();
+ if (seekTime.IsDefinite()) {
+ nsSMILTimeContainer* timeContainer = GetTimeContainer();
+ if (timeContainer) {
+ timeContainer->SetCurrentTime(seekTime.GetMillis());
+ AnimationNeedsResample();
+ // As with SVGSVGElement::SetCurrentTime, we need to trigger
+ // a synchronous sample now.
+ FlushAnimations();
+ }
+ // else, silently fail. We mustn't be part of an SVG document fragment that
+ // is attached to the document tree so there's nothing we can do here
+ } else {
+ IgnoredErrorResult rv;
+ BeginElement(rv);
+ }
+}
+
+//----------------------------------------------------------------------
+// Implementation helpers
+
+nsSMILTimeContainer*
+SVGAnimationElement::GetTimeContainer()
+{
+ SVGSVGElement *element = SVGContentUtils::GetOuterSVGElement(this);
+
+ if (element) {
+ return element->GetTimedDocumentRoot();
+ }
+
+ return nullptr;
+}
+
+void
+SVGAnimationElement::BeginElementAt(float offset, ErrorResult& rv)
+{
+ // Make sure the timegraph is up-to-date
+ FlushAnimations();
+
+ // This will fail if we're not attached to a time container (SVG document
+ // fragment).
+ rv = mTimedElement.BeginElementAt(offset);
+ if (rv.Failed())
+ return;
+
+ AnimationNeedsResample();
+ // Force synchronous sample so that events resulting from this call arrive in
+ // the expected order and we get an up-to-date paint.
+ FlushAnimations();
+}
+
+void
+SVGAnimationElement::EndElementAt(float offset, ErrorResult& rv)
+{
+ // Make sure the timegraph is up-to-date
+ FlushAnimations();
+
+ rv = mTimedElement.EndElementAt(offset);
+ if (rv.Failed())
+ return;
+
+ AnimationNeedsResample();
+ // Force synchronous sample
+ FlushAnimations();
+}
+
+bool
+SVGAnimationElement::IsEventAttributeName(nsIAtom* aName)
+{
+ return nsContentUtils::IsEventAttributeName(aName, EventNameType_SMIL);
+}
+
+void
+SVGAnimationElement::UpdateHrefTarget(nsIContent* aNodeForContext,
+ const nsAString& aHrefStr)
+{
+ nsCOMPtr<nsIURI> targetURI;
+ nsCOMPtr<nsIURI> baseURI = GetBaseURI();
+ nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI),
+ aHrefStr, OwnerDoc(), baseURI);
+ mHrefTarget.Reset(aNodeForContext, targetURI);
+ AnimationTargetChanged();
+}
+
+void
+SVGAnimationElement::AnimationTargetChanged()
+{
+ mTimedElement.HandleTargetElementChange(GetTargetElementContent());
+ AnimationNeedsResample();
+}
+
+} // namespace dom
+} // namespace mozilla