diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/svg/nsSVGElement.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/svg/nsSVGElement.cpp')
-rw-r--r-- | dom/svg/nsSVGElement.cpp | 2756 |
1 files changed, 2756 insertions, 0 deletions
diff --git a/dom/svg/nsSVGElement.cpp b/dom/svg/nsSVGElement.cpp new file mode 100644 index 000000000..ce849acf0 --- /dev/null +++ b/dom/svg/nsSVGElement.cpp @@ -0,0 +1,2756 @@ +/* -*- 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/DebugOnly.h" +#include "mozilla/Unused.h" + +#include "nsSVGElement.h" + +#include "mozilla/dom/SVGSVGElement.h" +#include "mozilla/dom/SVGTests.h" +#include "nsContentUtils.h" +#include "nsICSSDeclaration.h" +#include "nsIDocument.h" +#include "nsIDOMMutationEvent.h" +#include "nsSVGPathGeometryElement.h" +#include "mozilla/InternalMutationEvent.h" +#include "nsError.h" +#include "nsIPresShell.h" +#include "nsGkAtoms.h" +#include "mozilla/css/StyleRule.h" +#include "nsRuleWalker.h" +#include "mozilla/css/Declaration.h" +#include "nsCSSProps.h" +#include "nsCSSParser.h" +#include "mozilla/EventListenerManager.h" +#include "nsLayoutUtils.h" +#include "nsSVGAnimatedTransformList.h" +#include "nsSVGLength2.h" +#include "nsSVGNumber2.h" +#include "nsSVGNumberPair.h" +#include "nsSVGInteger.h" +#include "nsSVGIntegerPair.h" +#include "nsSVGAngle.h" +#include "nsSVGBoolean.h" +#include "nsSVGEnum.h" +#include "nsSVGViewBox.h" +#include "nsSVGString.h" +#include "mozilla/dom/SVGAnimatedEnumeration.h" +#include "SVGAnimatedNumberList.h" +#include "SVGAnimatedLengthList.h" +#include "SVGAnimatedPointList.h" +#include "SVGAnimatedPathSegList.h" +#include "SVGContentUtils.h" +#include "nsIFrame.h" +#include "nsQueryObject.h" +#include <stdarg.h> +#include "nsSMILMappedAttribute.h" +#include "SVGMotionSMILAttr.h" +#include "nsAttrValueOrString.h" +#include "nsSMILAnimationController.h" +#include "mozilla/dom/SVGElementBinding.h" +#include "mozilla/Unused.h" +#include "mozilla/RestyleManagerHandle.h" +#include "mozilla/RestyleManagerHandleInlines.h" + +using namespace mozilla; +using namespace mozilla::dom; + +// This is needed to ensure correct handling of calls to the +// vararg-list methods in this file: +// nsSVGElement::GetAnimated{Length,Number,Integer}Values +// See bug 547964 for details: +static_assert(sizeof(void*) == sizeof(nullptr), + "nullptr should be the correct size"); + +nsresult +NS_NewSVGElement(Element **aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) +{ + RefPtr<nsSVGElement> it = new nsSVGElement(aNodeInfo); + nsresult rv = it->Init(); + + if (NS_FAILED(rv)) { + return rv; + } + + it.forget(aResult); + return rv; +} + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(nsSVGElement) + +nsSVGEnumMapping nsSVGElement::sSVGUnitTypesMap[] = { + {&nsGkAtoms::userSpaceOnUse, SVG_UNIT_TYPE_USERSPACEONUSE}, + {&nsGkAtoms::objectBoundingBox, SVG_UNIT_TYPE_OBJECTBOUNDINGBOX}, + {nullptr, 0} +}; + +nsSVGElement::nsSVGElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo) + : nsSVGElementBase(aNodeInfo) +{ +} + +JSObject* +nsSVGElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) +{ + return SVGElementBinding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- + +NS_IMETHODIMP +nsSVGElement::GetSVGClassName(nsISupports** aClassName) +{ + *aClassName = ClassName().take(); + return NS_OK; +} + +NS_IMETHODIMP +nsSVGElement::GetStyle(nsIDOMCSSStyleDeclaration** aStyle) +{ + NS_ADDREF(*aStyle = Style()); + return NS_OK; +} + +//---------------------------------------------------------------------- +// nsSVGElement methods + +void +nsSVGElement::DidAnimateClass() +{ + nsAutoString src; + mClassAttribute.GetAnimValue(src, this); + if (!mClassAnimAttr) { + mClassAnimAttr = new nsAttrValue(); + } + mClassAnimAttr->ParseAtomArray(src); + + nsIPresShell* shell = OwnerDoc()->GetShell(); + if (shell) { + shell->RestyleForAnimation(this, eRestyle_Self); + } +} + +nsresult +nsSVGElement::Init() +{ + // Set up length attributes - can't do this in the constructor + // because we can't do a virtual call at that point + + LengthAttributesInfo lengthInfo = GetLengthInfo(); + + uint32_t i; + for (i = 0; i < lengthInfo.mLengthCount; i++) { + lengthInfo.Reset(i); + } + + NumberAttributesInfo numberInfo = GetNumberInfo(); + + for (i = 0; i < numberInfo.mNumberCount; i++) { + numberInfo.Reset(i); + } + + NumberPairAttributesInfo numberPairInfo = GetNumberPairInfo(); + + for (i = 0; i < numberPairInfo.mNumberPairCount; i++) { + numberPairInfo.Reset(i); + } + + IntegerAttributesInfo integerInfo = GetIntegerInfo(); + + for (i = 0; i < integerInfo.mIntegerCount; i++) { + integerInfo.Reset(i); + } + + IntegerPairAttributesInfo integerPairInfo = GetIntegerPairInfo(); + + for (i = 0; i < integerPairInfo.mIntegerPairCount; i++) { + integerPairInfo.Reset(i); + } + + AngleAttributesInfo angleInfo = GetAngleInfo(); + + for (i = 0; i < angleInfo.mAngleCount; i++) { + angleInfo.Reset(i); + } + + BooleanAttributesInfo booleanInfo = GetBooleanInfo(); + + for (i = 0; i < booleanInfo.mBooleanCount; i++) { + booleanInfo.Reset(i); + } + + EnumAttributesInfo enumInfo = GetEnumInfo(); + + for (i = 0; i < enumInfo.mEnumCount; i++) { + enumInfo.Reset(i); + } + + nsSVGViewBox *viewBox = GetViewBox(); + + if (viewBox) { + viewBox->Init(); + } + + SVGAnimatedPreserveAspectRatio *preserveAspectRatio = + GetPreserveAspectRatio(); + + if (preserveAspectRatio) { + preserveAspectRatio->Init(); + } + + LengthListAttributesInfo lengthListInfo = GetLengthListInfo(); + + for (i = 0; i < lengthListInfo.mLengthListCount; i++) { + lengthListInfo.Reset(i); + } + + NumberListAttributesInfo numberListInfo = GetNumberListInfo(); + + for (i = 0; i < numberListInfo.mNumberListCount; i++) { + numberListInfo.Reset(i); + } + + // No need to reset SVGPointList since the default value is always the same + // (an empty list). + + // No need to reset SVGPathData since the default value is always the same + // (an empty list). + + StringAttributesInfo stringInfo = GetStringInfo(); + + for (i = 0; i < stringInfo.mStringCount; i++) { + stringInfo.Reset(i); + } + + return NS_OK; +} + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ISUPPORTS_INHERITED(nsSVGElement, nsSVGElementBase, + nsIDOMNode, nsIDOMElement, + nsIDOMSVGElement) + +//---------------------------------------------------------------------- +// Implementation + +//---------------------------------------------------------------------- +// nsIContent methods + +nsresult +nsSVGElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, + nsIContent* aBindingParent, + bool aCompileEventHandlers) +{ + nsresult rv = nsSVGElementBase::BindToTree(aDocument, aParent, + aBindingParent, + aCompileEventHandlers); + NS_ENSURE_SUCCESS(rv, rv); + + if (!MayHaveStyle()) { + return NS_OK; + } + const nsAttrValue* oldVal = mAttrsAndChildren.GetAttr(nsGkAtoms::style); + + if (oldVal && oldVal->Type() == nsAttrValue::eCSSDeclaration) { + // we need to force a reparse because the baseURI of the document + // may have changed, and in particular because we may be clones of + // XBL anonymous content now being bound to the document we should + // render in and due to the hacky way in which we implement the + // interaction of XBL and SVG resources. Once we have a sane + // ownerDocument on XBL anonymous content, this can all go away. + nsAttrValue attrValue; + nsAutoString stringValue; + oldVal->ToString(stringValue); + // Force in data doc, since we already have a style rule + ParseStyleAttribute(stringValue, attrValue, true); + // Don't bother going through SetInlineStyleDeclaration; we don't + // want to fire off mutation events or document notifications anyway + rv = mAttrsAndChildren.SetAndSwapAttr(nsGkAtoms::style, attrValue); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult +nsSVGElement::AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName, + const nsAttrValue* aValue, bool aNotify) +{ + // We don't currently use nsMappedAttributes within SVG. If this changes, we + // need to be very careful because some nsAttrValues used by SVG point to + // member data of SVG elements and if an nsAttrValue outlives the SVG element + // whose data it points to (by virtue of being stored in + // mAttrsAndChildren->mMappedAttributes, meaning it's shared between + // elements), the pointer will dangle. See bug 724680. + MOZ_ASSERT(!mAttrsAndChildren.HasMappedAttrs(), + "Unexpected use of nsMappedAttributes within SVG"); + + // If this is an svg presentation attribute we need to map it into + // the content stylerule. + // XXX For some reason incremental mapping doesn't work, so for now + // just delete the style rule and lazily reconstruct it in + // GetContentStyleRule() + if (aNamespaceID == kNameSpaceID_None && IsAttributeMapped(aName)) { + mContentStyleRule = nullptr; + } + + if (IsEventAttributeName(aName) && aValue) { + MOZ_ASSERT(aValue->Type() == nsAttrValue::eString, + "Expected string value for script body"); + nsresult rv = SetEventHandler(GetEventNameForAttr(aName), + aValue->GetStringValue()); + NS_ENSURE_SUCCESS(rv, rv); + } + + return nsSVGElementBase::AfterSetAttr(aNamespaceID, aName, aValue, aNotify); +} + +bool +nsSVGElement::ParseAttribute(int32_t aNamespaceID, + nsIAtom* aAttribute, + const nsAString& aValue, + nsAttrValue& aResult) +{ + nsresult rv = NS_OK; + bool foundMatch = false; + bool didSetResult = false; + + if (aNamespaceID == kNameSpaceID_None) { + // Check for nsSVGLength2 attribute + LengthAttributesInfo lengthInfo = GetLengthInfo(); + + uint32_t i; + for (i = 0; i < lengthInfo.mLengthCount; i++) { + if (aAttribute == *lengthInfo.mLengthInfo[i].mName) { + rv = lengthInfo.mLengths[i].SetBaseValueString(aValue, this, false); + if (NS_FAILED(rv)) { + lengthInfo.Reset(i); + } else { + aResult.SetTo(lengthInfo.mLengths[i], &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + + if (!foundMatch) { + // Check for SVGAnimatedLengthList attribute + LengthListAttributesInfo lengthListInfo = GetLengthListInfo(); + for (i = 0; i < lengthListInfo.mLengthListCount; i++) { + if (aAttribute == *lengthListInfo.mLengthListInfo[i].mName) { + rv = lengthListInfo.mLengthLists[i].SetBaseValueString(aValue); + if (NS_FAILED(rv)) { + lengthListInfo.Reset(i); + } else { + aResult.SetTo(lengthListInfo.mLengthLists[i].GetBaseValue(), + &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedNumberList attribute + NumberListAttributesInfo numberListInfo = GetNumberListInfo(); + for (i = 0; i < numberListInfo.mNumberListCount; i++) { + if (aAttribute == *numberListInfo.mNumberListInfo[i].mName) { + rv = numberListInfo.mNumberLists[i].SetBaseValueString(aValue); + if (NS_FAILED(rv)) { + numberListInfo.Reset(i); + } else { + aResult.SetTo(numberListInfo.mNumberLists[i].GetBaseValue(), + &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedPointList attribute + if (GetPointListAttrName() == aAttribute) { + SVGAnimatedPointList* pointList = GetAnimatedPointList(); + if (pointList) { + pointList->SetBaseValueString(aValue); + // The spec says we parse everything up to the failure, so we DON'T + // need to check the result of SetBaseValueString or call + // pointList->ClearBaseValue() if it fails + aResult.SetTo(pointList->GetBaseValue(), &aValue); + didSetResult = true; + foundMatch = true; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedPathSegList attribute + if (GetPathDataAttrName() == aAttribute) { + SVGAnimatedPathSegList* segList = GetAnimPathSegList(); + if (segList) { + segList->SetBaseValueString(aValue); + // The spec says we parse everything up to the failure, so we DON'T + // need to check the result of SetBaseValueString or call + // segList->ClearBaseValue() if it fails + aResult.SetTo(segList->GetBaseValue(), &aValue); + didSetResult = true; + foundMatch = true; + } + } + } + + if (!foundMatch) { + // Check for nsSVGNumber2 attribute + NumberAttributesInfo numberInfo = GetNumberInfo(); + for (i = 0; i < numberInfo.mNumberCount; i++) { + if (aAttribute == *numberInfo.mNumberInfo[i].mName) { + rv = numberInfo.mNumbers[i].SetBaseValueString(aValue, this); + if (NS_FAILED(rv)) { + numberInfo.Reset(i); + } else { + aResult.SetTo(numberInfo.mNumbers[i].GetBaseValue(), &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for nsSVGNumberPair attribute + NumberPairAttributesInfo numberPairInfo = GetNumberPairInfo(); + for (i = 0; i < numberPairInfo.mNumberPairCount; i++) { + if (aAttribute == *numberPairInfo.mNumberPairInfo[i].mName) { + rv = numberPairInfo.mNumberPairs[i].SetBaseValueString(aValue, this); + if (NS_FAILED(rv)) { + numberPairInfo.Reset(i); + } else { + aResult.SetTo(numberPairInfo.mNumberPairs[i], &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for nsSVGInteger attribute + IntegerAttributesInfo integerInfo = GetIntegerInfo(); + for (i = 0; i < integerInfo.mIntegerCount; i++) { + if (aAttribute == *integerInfo.mIntegerInfo[i].mName) { + rv = integerInfo.mIntegers[i].SetBaseValueString(aValue, this); + if (NS_FAILED(rv)) { + integerInfo.Reset(i); + } else { + aResult.SetTo(integerInfo.mIntegers[i].GetBaseValue(), &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for nsSVGIntegerPair attribute + IntegerPairAttributesInfo integerPairInfo = GetIntegerPairInfo(); + for (i = 0; i < integerPairInfo.mIntegerPairCount; i++) { + if (aAttribute == *integerPairInfo.mIntegerPairInfo[i].mName) { + rv = + integerPairInfo.mIntegerPairs[i].SetBaseValueString(aValue, this); + if (NS_FAILED(rv)) { + integerPairInfo.Reset(i); + } else { + aResult.SetTo(integerPairInfo.mIntegerPairs[i], &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for nsSVGAngle attribute + AngleAttributesInfo angleInfo = GetAngleInfo(); + for (i = 0; i < angleInfo.mAngleCount; i++) { + if (aAttribute == *angleInfo.mAngleInfo[i].mName) { + rv = angleInfo.mAngles[i].SetBaseValueString(aValue, this, false); + if (NS_FAILED(rv)) { + angleInfo.Reset(i); + } else { + aResult.SetTo(angleInfo.mAngles[i], &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for nsSVGBoolean attribute + BooleanAttributesInfo booleanInfo = GetBooleanInfo(); + for (i = 0; i < booleanInfo.mBooleanCount; i++) { + if (aAttribute == *booleanInfo.mBooleanInfo[i].mName) { + nsIAtom *valAtom = NS_GetStaticAtom(aValue); + rv = valAtom ? booleanInfo.mBooleans[i].SetBaseValueAtom(valAtom, this) : + NS_ERROR_DOM_SYNTAX_ERR; + if (NS_FAILED(rv)) { + booleanInfo.Reset(i); + } else { + aResult.SetTo(valAtom); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for nsSVGEnum attribute + EnumAttributesInfo enumInfo = GetEnumInfo(); + for (i = 0; i < enumInfo.mEnumCount; i++) { + if (aAttribute == *enumInfo.mEnumInfo[i].mName) { + nsCOMPtr<nsIAtom> valAtom = NS_Atomize(aValue); + rv = enumInfo.mEnums[i].SetBaseValueAtom(valAtom, this); + if (NS_FAILED(rv)) { + enumInfo.Reset(i); + } else { + aResult.SetTo(valAtom); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for conditional processing attributes + nsCOMPtr<SVGTests> tests = do_QueryObject(this); + if (tests && tests->ParseConditionalProcessingAttribute( + aAttribute, aValue, aResult)) { + foundMatch = true; + } + } + + if (!foundMatch) { + // Check for StringList attribute + StringListAttributesInfo stringListInfo = GetStringListInfo(); + for (i = 0; i < stringListInfo.mStringListCount; i++) { + if (aAttribute == *stringListInfo.mStringListInfo[i].mName) { + rv = stringListInfo.mStringLists[i].SetValue(aValue); + if (NS_FAILED(rv)) { + stringListInfo.Reset(i); + } else { + aResult.SetTo(stringListInfo.mStringLists[i], &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for nsSVGViewBox attribute + if (aAttribute == nsGkAtoms::viewBox) { + nsSVGViewBox* viewBox = GetViewBox(); + if (viewBox) { + rv = viewBox->SetBaseValueString(aValue, this, false); + if (NS_FAILED(rv)) { + viewBox->Init(); + } else { + aResult.SetTo(*viewBox, &aValue); + didSetResult = true; + } + foundMatch = true; + } + // Check for SVGAnimatedPreserveAspectRatio attribute + } else if (aAttribute == nsGkAtoms::preserveAspectRatio) { + SVGAnimatedPreserveAspectRatio *preserveAspectRatio = + GetPreserveAspectRatio(); + if (preserveAspectRatio) { + rv = preserveAspectRatio->SetBaseValueString(aValue, this, false); + if (NS_FAILED(rv)) { + preserveAspectRatio->Init(); + } else { + aResult.SetTo(*preserveAspectRatio, &aValue); + didSetResult = true; + } + foundMatch = true; + } + // Check for SVGAnimatedTransformList attribute + } else if (GetTransformListAttrName() == aAttribute) { + // The transform attribute is being set, so we must ensure that the + // nsSVGAnimatedTransformList is/has been allocated: + nsSVGAnimatedTransformList *transformList = + GetAnimatedTransformList(DO_ALLOCATE); + rv = transformList->SetBaseValueString(aValue); + if (NS_FAILED(rv)) { + transformList->ClearBaseValue(); + } else { + aResult.SetTo(transformList->GetBaseValue(), &aValue); + didSetResult = true; + } + foundMatch = true; + } else if (aAttribute == nsGkAtoms::tabindex) { + didSetResult = aResult.ParseIntValue(aValue); + foundMatch = true; + } + } + + if (aAttribute == nsGkAtoms::_class) { + mClassAttribute.SetBaseValue(aValue, this, false); + aResult.ParseAtomArray(aValue); + return true; + } + } + + if (!foundMatch) { + // Check for nsSVGString attribute + StringAttributesInfo stringInfo = GetStringInfo(); + for (uint32_t i = 0; i < stringInfo.mStringCount; i++) { + if (aNamespaceID == stringInfo.mStringInfo[i].mNamespaceID && + aAttribute == *stringInfo.mStringInfo[i].mName) { + stringInfo.mStrings[i].SetBaseValue(aValue, this, false); + foundMatch = true; + break; + } + } + } + + if (foundMatch) { + if (NS_FAILED(rv)) { + ReportAttributeParseFailure(OwnerDoc(), aAttribute, aValue); + return false; + } + if (!didSetResult) { + aResult.SetTo(aValue); + } + return true; + } + + return nsSVGElementBase::ParseAttribute(aNamespaceID, aAttribute, aValue, + aResult); +} + +void +nsSVGElement::UnsetAttrInternal(int32_t aNamespaceID, nsIAtom* aName, + bool aNotify) +{ + // XXXbz there's a bunch of redundancy here with AfterSetAttr. + // Maybe consolidate? + + if (aNamespaceID == kNameSpaceID_None) { + // If this is an svg presentation attribute, remove rule to force an update + if (IsAttributeMapped(aName)) + mContentStyleRule = nullptr; + + if (IsEventAttributeName(aName)) { + EventListenerManager* manager = GetExistingListenerManager(); + if (manager) { + nsIAtom* eventName = GetEventNameForAttr(aName); + manager->RemoveEventHandler(eventName, EmptyString()); + } + return; + } + + // Check if this is a length attribute going away + LengthAttributesInfo lenInfo = GetLengthInfo(); + + for (uint32_t i = 0; i < lenInfo.mLengthCount; i++) { + if (aName == *lenInfo.mLengthInfo[i].mName) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + lenInfo.Reset(i); + return; + } + } + + // Check if this is a length list attribute going away + LengthListAttributesInfo lengthListInfo = GetLengthListInfo(); + + for (uint32_t i = 0; i < lengthListInfo.mLengthListCount; i++) { + if (aName == *lengthListInfo.mLengthListInfo[i].mName) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + lengthListInfo.Reset(i); + return; + } + } + + // Check if this is a number list attribute going away + NumberListAttributesInfo numberListInfo = GetNumberListInfo(); + + for (uint32_t i = 0; i < numberListInfo.mNumberListCount; i++) { + if (aName == *numberListInfo.mNumberListInfo[i].mName) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + numberListInfo.Reset(i); + return; + } + } + + // Check if this is a point list attribute going away + if (GetPointListAttrName() == aName) { + SVGAnimatedPointList *pointList = GetAnimatedPointList(); + if (pointList) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + pointList->ClearBaseValue(); + return; + } + } + + // Check if this is a path segment list attribute going away + if (GetPathDataAttrName() == aName) { + SVGAnimatedPathSegList *segList = GetAnimPathSegList(); + if (segList) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + segList->ClearBaseValue(); + return; + } + } + + // Check if this is a number attribute going away + NumberAttributesInfo numInfo = GetNumberInfo(); + + for (uint32_t i = 0; i < numInfo.mNumberCount; i++) { + if (aName == *numInfo.mNumberInfo[i].mName) { + numInfo.Reset(i); + return; + } + } + + // Check if this is a number pair attribute going away + NumberPairAttributesInfo numPairInfo = GetNumberPairInfo(); + + for (uint32_t i = 0; i < numPairInfo.mNumberPairCount; i++) { + if (aName == *numPairInfo.mNumberPairInfo[i].mName) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + numPairInfo.Reset(i); + return; + } + } + + // Check if this is an integer attribute going away + IntegerAttributesInfo intInfo = GetIntegerInfo(); + + for (uint32_t i = 0; i < intInfo.mIntegerCount; i++) { + if (aName == *intInfo.mIntegerInfo[i].mName) { + intInfo.Reset(i); + return; + } + } + + // Check if this is an integer pair attribute going away + IntegerPairAttributesInfo intPairInfo = GetIntegerPairInfo(); + + for (uint32_t i = 0; i < intPairInfo.mIntegerPairCount; i++) { + if (aName == *intPairInfo.mIntegerPairInfo[i].mName) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + intPairInfo.Reset(i); + return; + } + } + + // Check if this is an angle attribute going away + AngleAttributesInfo angleInfo = GetAngleInfo(); + + for (uint32_t i = 0; i < angleInfo.mAngleCount; i++) { + if (aName == *angleInfo.mAngleInfo[i].mName) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + angleInfo.Reset(i); + return; + } + } + + // Check if this is a boolean attribute going away + BooleanAttributesInfo boolInfo = GetBooleanInfo(); + + for (uint32_t i = 0; i < boolInfo.mBooleanCount; i++) { + if (aName == *boolInfo.mBooleanInfo[i].mName) { + boolInfo.Reset(i); + return; + } + } + + // Check if this is an enum attribute going away + EnumAttributesInfo enumInfo = GetEnumInfo(); + + for (uint32_t i = 0; i < enumInfo.mEnumCount; i++) { + if (aName == *enumInfo.mEnumInfo[i].mName) { + enumInfo.Reset(i); + return; + } + } + + // Check if this is a nsViewBox attribute going away + if (aName == nsGkAtoms::viewBox) { + nsSVGViewBox* viewBox = GetViewBox(); + if (viewBox) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + viewBox->Init(); + return; + } + } + + // Check if this is a preserveAspectRatio attribute going away + if (aName == nsGkAtoms::preserveAspectRatio) { + SVGAnimatedPreserveAspectRatio *preserveAspectRatio = + GetPreserveAspectRatio(); + if (preserveAspectRatio) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + preserveAspectRatio->Init(); + return; + } + } + + // Check if this is a transform list attribute going away + if (GetTransformListAttrName() == aName) { + nsSVGAnimatedTransformList *transformList = GetAnimatedTransformList(); + if (transformList) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + transformList->ClearBaseValue(); + return; + } + } + + // Check for conditional processing attributes + nsCOMPtr<SVGTests> tests = do_QueryObject(this); + if (tests && tests->IsConditionalProcessingAttribute(aName)) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + tests->UnsetAttr(aName); + return; + } + + // Check if this is a string list attribute going away + StringListAttributesInfo stringListInfo = GetStringListInfo(); + + for (uint32_t i = 0; i < stringListInfo.mStringListCount; i++) { + if (aName == *stringListInfo.mStringListInfo[i].mName) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + stringListInfo.Reset(i); + return; + } + } + + if (aName == nsGkAtoms::_class) { + mClassAttribute.Init(); + return; + } + } + + // Check if this is a string attribute going away + StringAttributesInfo stringInfo = GetStringInfo(); + + for (uint32_t i = 0; i < stringInfo.mStringCount; i++) { + if (aNamespaceID == stringInfo.mStringInfo[i].mNamespaceID && + aName == *stringInfo.mStringInfo[i].mName) { + stringInfo.Reset(i); + return; + } + } +} + +nsresult +nsSVGElement::UnsetAttr(int32_t aNamespaceID, nsIAtom* aName, + bool aNotify) +{ + UnsetAttrInternal(aNamespaceID, aName, aNotify); + return nsSVGElementBase::UnsetAttr(aNamespaceID, aName, aNotify); +} + +nsChangeHint +nsSVGElement::GetAttributeChangeHint(const nsIAtom* aAttribute, + int32_t aModType) const +{ + nsChangeHint retval = + nsSVGElementBase::GetAttributeChangeHint(aAttribute, aModType); + + nsCOMPtr<SVGTests> tests = do_QueryObject(const_cast<nsSVGElement*>(this)); + if (tests && tests->IsConditionalProcessingAttribute(aAttribute)) { + // It would be nice to only reconstruct the frame if the value returned by + // SVGTests::PassesConditionalProcessingTests has changed, but we don't + // know that + retval |= nsChangeHint_ReconstructFrame; + } + return retval; +} + +bool +nsSVGElement::IsNodeOfType(uint32_t aFlags) const +{ + return !(aFlags & ~eCONTENT); +} + +NS_IMETHODIMP +nsSVGElement::WalkContentStyleRules(nsRuleWalker* aRuleWalker) +{ +#ifdef DEBUG +// printf("nsSVGElement(%p)::WalkContentStyleRules()\n", this); +#endif + if (!mContentStyleRule) + UpdateContentStyleRule(); + + if (mContentStyleRule) { + css::Declaration* declaration = mContentStyleRule->GetDeclaration(); + declaration->SetImmutable(); + aRuleWalker->Forward(declaration); + } + + return NS_OK; +} + +void +nsSVGElement::WalkAnimatedContentStyleRules(nsRuleWalker* aRuleWalker) +{ + // Update & walk the animated content style rule, to include style from + // animated mapped attributes. But first, get nsPresContext to check + // whether this is a "no-animation restyle". (This should match the check + // in nsHTMLCSSStyleSheet::RulesMatching(), where we determine whether to + // apply the SMILOverrideStyle.) + RestyleManagerHandle restyleManager = + aRuleWalker->PresContext()->RestyleManager(); + MOZ_ASSERT(restyleManager->IsGecko(), + "stylo: Servo-backed style system should not be calling " + "WalkAnimatedContentStyleRules"); + if (!restyleManager->AsGecko()->SkipAnimationRules()) { + // update/walk the animated content style rule. + css::StyleRule* animContentStyleRule = GetAnimatedContentStyleRule(); + if (!animContentStyleRule) { + UpdateAnimatedContentStyleRule(); + animContentStyleRule = GetAnimatedContentStyleRule(); + } + if (animContentStyleRule) { + css::Declaration* declaration = animContentStyleRule->GetDeclaration(); + declaration->SetImmutable(); + aRuleWalker->Forward(declaration); + } + } +} + +NS_IMETHODIMP_(bool) +nsSVGElement::IsAttributeMapped(const nsIAtom* name) const +{ + if (name == nsGkAtoms::lang) { + return true; + } + return nsSVGElementBase::IsAttributeMapped(name); +} + +// PresentationAttributes-FillStroke +/* static */ const Element::MappedAttributeEntry +nsSVGElement::sFillStrokeMap[] = { + { &nsGkAtoms::fill }, + { &nsGkAtoms::fill_opacity }, + { &nsGkAtoms::fill_rule }, + { &nsGkAtoms::paint_order }, + { &nsGkAtoms::stroke }, + { &nsGkAtoms::stroke_dasharray }, + { &nsGkAtoms::stroke_dashoffset }, + { &nsGkAtoms::stroke_linecap }, + { &nsGkAtoms::stroke_linejoin }, + { &nsGkAtoms::stroke_miterlimit }, + { &nsGkAtoms::stroke_opacity }, + { &nsGkAtoms::stroke_width }, + { &nsGkAtoms::vector_effect }, + { nullptr } +}; + +// PresentationAttributes-Graphics +/* static */ const Element::MappedAttributeEntry +nsSVGElement::sGraphicsMap[] = { + { &nsGkAtoms::clip_path }, + { &nsGkAtoms::clip_rule }, + { &nsGkAtoms::colorInterpolation }, + { &nsGkAtoms::cursor }, + { &nsGkAtoms::display }, + { &nsGkAtoms::filter }, + { &nsGkAtoms::image_rendering }, + { &nsGkAtoms::mask }, + { &nsGkAtoms::opacity }, + { &nsGkAtoms::pointer_events }, + { &nsGkAtoms::shape_rendering }, + { &nsGkAtoms::text_rendering }, + { &nsGkAtoms::visibility }, + { nullptr } +}; + +// PresentationAttributes-TextContentElements +/* static */ const Element::MappedAttributeEntry +nsSVGElement::sTextContentElementsMap[] = { + // Properties that we don't support are commented out. + // { &nsGkAtoms::alignment_baseline }, + // { &nsGkAtoms::baseline_shift }, + { &nsGkAtoms::direction }, + { &nsGkAtoms::dominant_baseline }, + { &nsGkAtoms::letter_spacing }, + { &nsGkAtoms::text_anchor }, + { &nsGkAtoms::text_decoration }, + { &nsGkAtoms::unicode_bidi }, + { &nsGkAtoms::word_spacing }, + { &nsGkAtoms::writing_mode }, + { nullptr } +}; + +// PresentationAttributes-FontSpecification +/* static */ const Element::MappedAttributeEntry +nsSVGElement::sFontSpecificationMap[] = { + { &nsGkAtoms::font_family }, + { &nsGkAtoms::font_size }, + { &nsGkAtoms::font_size_adjust }, + { &nsGkAtoms::font_stretch }, + { &nsGkAtoms::font_style }, + { &nsGkAtoms::font_variant }, + { &nsGkAtoms::fontWeight }, + { nullptr } +}; + +// PresentationAttributes-GradientStop +/* static */ const Element::MappedAttributeEntry +nsSVGElement::sGradientStopMap[] = { + { &nsGkAtoms::stop_color }, + { &nsGkAtoms::stop_opacity }, + { nullptr } +}; + +// PresentationAttributes-Viewports +/* static */ const Element::MappedAttributeEntry +nsSVGElement::sViewportsMap[] = { + { &nsGkAtoms::overflow }, + { &nsGkAtoms::clip }, + { nullptr } +}; + +// PresentationAttributes-Makers +/* static */ const Element::MappedAttributeEntry +nsSVGElement::sMarkersMap[] = { + { &nsGkAtoms::marker_end }, + { &nsGkAtoms::marker_mid }, + { &nsGkAtoms::marker_start }, + { nullptr } +}; + +// PresentationAttributes-Color +/* static */ const Element::MappedAttributeEntry +nsSVGElement::sColorMap[] = { + { &nsGkAtoms::color }, + { nullptr } +}; + +// PresentationAttributes-Filters +/* static */ const Element::MappedAttributeEntry +nsSVGElement::sFiltersMap[] = { + { &nsGkAtoms::colorInterpolationFilters }, + { nullptr } +}; + +// PresentationAttributes-feFlood +/* static */ const Element::MappedAttributeEntry +nsSVGElement::sFEFloodMap[] = { + { &nsGkAtoms::flood_color }, + { &nsGkAtoms::flood_opacity }, + { nullptr } +}; + +// PresentationAttributes-LightingEffects +/* static */ const Element::MappedAttributeEntry +nsSVGElement::sLightingEffectsMap[] = { + { &nsGkAtoms::lighting_color }, + { nullptr } +}; + +// PresentationAttributes-mask +/* static */ const Element::MappedAttributeEntry +nsSVGElement::sMaskMap[] = { + { &nsGkAtoms::mask_type }, + { nullptr } +}; + +//---------------------------------------------------------------------- +// nsIDOMElement methods + +// forwarded to Element implementations + + +//---------------------------------------------------------------------- +// nsIDOMSVGElement methods + +NS_IMETHODIMP +nsSVGElement::GetOwnerSVGElement(nsIDOMSVGElement * *aOwnerSVGElement) +{ + NS_IF_ADDREF(*aOwnerSVGElement = GetOwnerSVGElement()); + return NS_OK; +} + +SVGSVGElement* +nsSVGElement::GetOwnerSVGElement() +{ + return GetCtx(); // this may return nullptr +} + +NS_IMETHODIMP +nsSVGElement::GetViewportElement(nsIDOMSVGElement * *aViewportElement) +{ + nsSVGElement* elem = GetViewportElement(); + NS_ADDREF(*aViewportElement = elem); + return NS_OK; +} + +nsSVGElement* +nsSVGElement::GetViewportElement() +{ + return SVGContentUtils::GetNearestViewportElement(this); +} + +already_AddRefed<SVGAnimatedString> +nsSVGElement::ClassName() +{ + return mClassAttribute.ToDOMAnimatedString(this); +} + +bool +nsSVGElement::IsFocusableInternal(int32_t* aTabIndex, bool) +{ + int32_t index = TabIndex(); + + if (index == -1) { + return false; + } + + *aTabIndex = index; + return true; +} + +//------------------------------------------------------------------------ +// Helper class: MappedAttrParser, for parsing values of mapped attributes + +namespace { + +class MOZ_STACK_CLASS MappedAttrParser { +public: + MappedAttrParser(css::Loader* aLoader, + nsIURI* aDocURI, + already_AddRefed<nsIURI> aBaseURI, + nsSVGElement* aElement); + ~MappedAttrParser(); + + // Parses a mapped attribute value. + void ParseMappedAttrValue(nsIAtom* aMappedAttrName, + const nsAString& aMappedAttrValue); + + // If we've parsed any values for mapped attributes, this method returns + // a new already_AddRefed css::StyleRule that incorporates the parsed + // values. Otherwise, this method returns null. + already_AddRefed<css::StyleRule> CreateStyleRule(); + +private: + // MEMBER DATA + // ----------- + nsCSSParser mParser; + + // Arguments for nsCSSParser::ParseProperty + nsIURI* mDocURI; + nsCOMPtr<nsIURI> mBaseURI; + + // Declaration for storing parsed values (lazily initialized) + css::Declaration* mDecl; + + // For reporting use counters + nsSVGElement* mElement; +}; + +MappedAttrParser::MappedAttrParser(css::Loader* aLoader, + nsIURI* aDocURI, + already_AddRefed<nsIURI> aBaseURI, + nsSVGElement* aElement) + : mParser(aLoader), mDocURI(aDocURI), mBaseURI(aBaseURI), + mDecl(nullptr), mElement(aElement) +{ +} + +MappedAttrParser::~MappedAttrParser() +{ + MOZ_ASSERT(!mDecl, + "If mDecl was initialized, it should have been converted " + "into a style rule (and had its pointer cleared)"); +} + +void +MappedAttrParser::ParseMappedAttrValue(nsIAtom* aMappedAttrName, + const nsAString& aMappedAttrValue) +{ + if (!mDecl) { + mDecl = new css::Declaration(); + mDecl->InitializeEmpty(); + } + + // Get the nsCSSPropertyID ID for our mapped attribute. + nsCSSPropertyID propertyID = + nsCSSProps::LookupProperty(nsDependentAtomString(aMappedAttrName), + CSSEnabledState::eForAllContent); + if (propertyID != eCSSProperty_UNKNOWN) { + bool changed = false; // outparam for ParseProperty. + mParser.ParseProperty(propertyID, aMappedAttrValue, mDocURI, mBaseURI, + mElement->NodePrincipal(), mDecl, &changed, false, true); + if (changed) { + // The normal reporting of use counters by the nsCSSParser won't happen + // since it doesn't have a sheet. + if (nsCSSProps::IsShorthand(propertyID)) { + CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, propertyID, + CSSEnabledState::eForAllContent) { + UseCounter useCounter = nsCSSProps::UseCounterFor(*subprop); + if (useCounter != eUseCounter_UNKNOWN) { + mElement->OwnerDoc()->SetDocumentAndPageUseCounter(useCounter); + } + } + } else { + UseCounter useCounter = nsCSSProps::UseCounterFor(propertyID); + if (useCounter != eUseCounter_UNKNOWN) { + mElement->OwnerDoc()->SetDocumentAndPageUseCounter(useCounter); + } + } + } + return; + } + MOZ_ASSERT(aMappedAttrName == nsGkAtoms::lang, + "Only 'lang' should be unrecognized!"); + // nsCSSParser doesn't know about 'lang', so we need to handle it specially. + if (aMappedAttrName == nsGkAtoms::lang) { + propertyID = eCSSProperty__x_lang; + nsCSSExpandedDataBlock block; + mDecl->ExpandTo(&block); + nsCSSValue cssValue(PromiseFlatString(aMappedAttrValue), eCSSUnit_Ident); + block.AddLonghandProperty(propertyID, cssValue); + mDecl->ValueAppended(propertyID); + mDecl->CompressFrom(&block); + } +} + +already_AddRefed<css::StyleRule> +MappedAttrParser::CreateStyleRule() +{ + if (!mDecl) { + return nullptr; // No mapped attributes were parsed + } + + RefPtr<css::StyleRule> rule = new css::StyleRule(nullptr, mDecl, 0, 0); + mDecl = nullptr; // We no longer own the declaration -- drop our pointer to it + return rule.forget(); +} + +} // namespace + +//---------------------------------------------------------------------- +// Implementation Helpers: + +void +nsSVGElement::UpdateContentStyleRule() +{ + NS_ASSERTION(!mContentStyleRule, "we already have a content style rule"); + + uint32_t attrCount = mAttrsAndChildren.AttrCount(); + if (!attrCount) { + // nothing to do + return; + } + + nsIDocument* doc = OwnerDoc(); + MappedAttrParser mappedAttrParser(doc->CSSLoader(), doc->GetDocumentURI(), + GetBaseURI(), this); + + for (uint32_t i = 0; i < attrCount; ++i) { + const nsAttrName* attrName = mAttrsAndChildren.AttrNameAt(i); + if (!attrName->IsAtom() || !IsAttributeMapped(attrName->Atom())) + continue; + + if (attrName->NamespaceID() != kNameSpaceID_None && + !attrName->Equals(nsGkAtoms::lang, kNameSpaceID_XML)) { + continue; + } + + if (attrName->Equals(nsGkAtoms::lang, kNameSpaceID_None) && + HasAttr(kNameSpaceID_XML, nsGkAtoms::lang)) { + continue; // xml:lang has precedence + } + + if (IsSVGElement(nsGkAtoms::svg)) { + // Special case: we don't want <svg> 'width'/'height' mapped into style + // if the attribute value isn't a valid <length> according to SVG (which + // only supports a subset of the CSS <length> values). We don't enforce + // this by checking the attribute value in SVGSVGElement:: + // IsAttributeMapped since we don't want that method to depend on the + // value of the attribute that is being checked. Rather we just prevent + // the actual mapping here, as necessary. + if (attrName->Atom() == nsGkAtoms::width && + !GetAnimatedLength(nsGkAtoms::width)->HasBaseVal()) { + continue; + } + if (attrName->Atom() == nsGkAtoms::height && + !GetAnimatedLength(nsGkAtoms::height)->HasBaseVal()) { + continue; + } + } + + nsAutoString value; + mAttrsAndChildren.AttrAt(i)->ToString(value); + mappedAttrParser.ParseMappedAttrValue(attrName->Atom(), value); + } + mContentStyleRule = mappedAttrParser.CreateStyleRule(); +} + +static void +ParseMappedAttrAnimValueCallback(void* aObject, + nsIAtom* aPropertyName, + void* aPropertyValue, + void* aData) +{ + MOZ_ASSERT(aPropertyName != SMIL_MAPPED_ATTR_STYLERULE_ATOM, + "animated content style rule should have been removed " + "from properties table already (we're rebuilding it now)"); + + MappedAttrParser* mappedAttrParser = static_cast<MappedAttrParser*>(aData); + MOZ_ASSERT(mappedAttrParser, "parser should be non-null"); + + nsStringBuffer* animValBuf = static_cast<nsStringBuffer*>(aPropertyValue); + MOZ_ASSERT(animValBuf, "animated value should be non-null"); + + nsString animValStr; + nsContentUtils::PopulateStringFromStringBuffer(animValBuf, animValStr); + + mappedAttrParser->ParseMappedAttrValue(aPropertyName, animValStr); +} + +// Callback for freeing animated content style rule, in property table. +static void +ReleaseStyleRule(void* aObject, /* unused */ + nsIAtom* aPropertyName, + void* aPropertyValue, + void* aData /* unused */) +{ + MOZ_ASSERT(aPropertyName == SMIL_MAPPED_ATTR_STYLERULE_ATOM, + "unexpected property name, for animated content style rule"); + css::StyleRule* styleRule = static_cast<css::StyleRule*>(aPropertyValue); + MOZ_ASSERT(styleRule, "unexpected null style rule"); + styleRule->Release(); +} + +void +nsSVGElement::UpdateAnimatedContentStyleRule() +{ + MOZ_ASSERT(!GetAnimatedContentStyleRule(), + "Animated content style rule already set"); + + nsIDocument* doc = OwnerDoc(); + if (!doc) { + NS_ERROR("SVG element without owner document"); + return; + } + + MappedAttrParser mappedAttrParser(doc->CSSLoader(), doc->GetDocumentURI(), + GetBaseURI(), this); + doc->PropertyTable(SMIL_MAPPED_ATTR_ANIMVAL)-> + Enumerate(this, ParseMappedAttrAnimValueCallback, &mappedAttrParser); + + RefPtr<css::StyleRule> + animContentStyleRule(mappedAttrParser.CreateStyleRule()); + + if (animContentStyleRule) { +#ifdef DEBUG + nsresult rv = +#endif + SetProperty(SMIL_MAPPED_ATTR_ANIMVAL, + SMIL_MAPPED_ATTR_STYLERULE_ATOM, + animContentStyleRule.get(), + ReleaseStyleRule); + Unused << animContentStyleRule.forget(); + MOZ_ASSERT(rv == NS_OK, + "SetProperty failed (or overwrote something)"); + } +} + +css::StyleRule* +nsSVGElement::GetAnimatedContentStyleRule() +{ + return + static_cast<css::StyleRule*>(GetProperty(SMIL_MAPPED_ATTR_ANIMVAL, + SMIL_MAPPED_ATTR_STYLERULE_ATOM, + nullptr)); +} + +/** + * Helper methods for the type-specific WillChangeXXX methods. + * + * This method sends out appropriate pre-change notifications so that selector + * restyles (e.g. due to changes that cause |elem[attr="val"]| to start/stop + * matching) work, and it returns an nsAttrValue that _may_ contain the + * attribute's pre-change value. + * + * The nsAttrValue returned by this method depends on whether there are + * mutation event listeners listening for changes to this element's attributes. + * If not, then the object returned is empty. If there are, then the + * nsAttrValue returned contains a serialized copy of the attribute's value + * prior to the change, and this object should be passed to the corresponding + * DidChangeXXX method call (assuming a WillChangeXXX call is required for the + * SVG type - see comment below). This is necessary so that the 'prevValue' + * property of the mutation event that is dispatched will correctly contain the + * old value. + * + * The reason we need to serialize the old value if there are mutation + * event listeners is because the underlying nsAttrValue for the attribute + * points directly to a parsed representation of the attribute (e.g. an + * SVGAnimatedLengthList*) that is a member of the SVG element. That object + * will have changed by the time DidChangeXXX has been called, so without the + * serialization of the old attribute value that we provide, DidChangeXXX + * would have no way to get the old value to pass to SetAttrAndNotify. + * + * We only return the old value when there are mutation event listeners because + * it's not needed otherwise, and because it's expensive to serialize the old + * value. This is especially true for list type attributes, which may be built + * up via the SVG DOM resulting in a large number of Will/DidModifyXXX calls + * before the script finally finishes setting the attribute. + * + * Note that unlike using SetParsedAttr, using Will/DidChangeXXX does NOT check + * and filter out redundant changes. Before calling WillChangeXXX, the caller + * should check whether the new and old values are actually the same, and skip + * calling Will/DidChangeXXX if they are. + * + * Also note that not all SVG types use this scheme. For types that can be + * represented by an nsAttrValue without pointing back to an SVG object (e.g. + * enums, booleans, integers) we can simply use SetParsedAttr which will do all + * of the above for us. For such types there is no matching WillChangeXXX + * method, only DidChangeXXX which calls SetParsedAttr. + */ +nsAttrValue +nsSVGElement::WillChangeValue(nsIAtom* aName) +{ + // We need an empty attr value: + // a) to pass to BeforeSetAttr when GetParsedAttr returns nullptr + // b) to store the old value in the case we have mutation listeners + // We can use the same value for both purposes since (a) happens before (b). + // Also, we should be careful to always return this value to benefit from + // return value optimization. + nsAttrValue emptyOrOldAttrValue; + const nsAttrValue* attrValue = GetParsedAttr(aName); + + // This is not strictly correct--the attribute value parameter for + // BeforeSetAttr should reflect the value that *will* be set but that implies + // allocating, e.g. an extra nsSVGLength2, and isn't necessary at the moment + // since no SVG elements overload BeforeSetAttr. For now we just pass the + // current value. + nsAttrValueOrString attrStringOrValue(attrValue ? *attrValue + : emptyOrOldAttrValue); + DebugOnly<nsresult> rv = + BeforeSetAttr(kNameSpaceID_None, aName, &attrStringOrValue, + kNotifyDocumentObservers); + // SVG elements aren't expected to overload BeforeSetAttr in such a way that + // it may fail. So long as this is the case we don't need to check and pass on + // the return value which simplifies the calling code significantly. + MOZ_ASSERT(NS_SUCCEEDED(rv), "Unexpected failure from BeforeSetAttr"); + + // We only need to set the old value if we have listeners since otherwise it + // isn't used. + if (attrValue && + nsContentUtils::HasMutationListeners(this, + NS_EVENT_BITS_MUTATION_ATTRMODIFIED, + this)) { + emptyOrOldAttrValue.SetToSerialized(*attrValue); + } + + uint8_t modType = attrValue + ? static_cast<uint8_t>(nsIDOMMutationEvent::MODIFICATION) + : static_cast<uint8_t>(nsIDOMMutationEvent::ADDITION); + nsNodeUtils::AttributeWillChange(this, kNameSpaceID_None, aName, modType, + nullptr); + + return emptyOrOldAttrValue; +} + +/** + * Helper methods for the type-specific DidChangeXXX methods. + * + * aEmptyOrOldValue will normally be the object returned from the corresponding + * WillChangeXXX call. This is because: + * a) WillChangeXXX will ensure the object is set when we have mutation + * listeners, and + * b) WillChangeXXX will ensure the object represents a serialized version of + * the old attribute value so that the value doesn't change when the + * underlying SVG type is updated. + * + * aNewValue is replaced with the old value. + */ +void +nsSVGElement::DidChangeValue(nsIAtom* aName, + const nsAttrValue& aEmptyOrOldValue, + nsAttrValue& aNewValue) +{ + bool hasListeners = + nsContentUtils::HasMutationListeners(this, + NS_EVENT_BITS_MUTATION_ATTRMODIFIED, + this); + uint8_t modType = HasAttr(kNameSpaceID_None, aName) + ? static_cast<uint8_t>(nsIDOMMutationEvent::MODIFICATION) + : static_cast<uint8_t>(nsIDOMMutationEvent::ADDITION); + SetAttrAndNotify(kNameSpaceID_None, aName, nullptr, aEmptyOrOldValue, + aNewValue, modType, hasListeners, kNotifyDocumentObservers, + kCallAfterSetAttr); +} + +void +nsSVGElement::MaybeSerializeAttrBeforeRemoval(nsIAtom* aName, bool aNotify) +{ + if (!aNotify || + !nsContentUtils::HasMutationListeners(this, + NS_EVENT_BITS_MUTATION_ATTRMODIFIED, + this)) { + return; + } + + const nsAttrValue* attrValue = mAttrsAndChildren.GetAttr(aName); + if (!attrValue) + return; + + nsAutoString serializedValue; + attrValue->ToString(serializedValue); + nsAttrValue oldAttrValue(serializedValue); + mAttrsAndChildren.SetAndSwapAttr(aName, oldAttrValue); +} + +/* static */ +nsIAtom* nsSVGElement::GetEventNameForAttr(nsIAtom* aAttr) +{ + if (aAttr == nsGkAtoms::onload) + return nsGkAtoms::onSVGLoad; + if (aAttr == nsGkAtoms::onunload) + return nsGkAtoms::onSVGUnload; + if (aAttr == nsGkAtoms::onresize) + return nsGkAtoms::onSVGResize; + if (aAttr == nsGkAtoms::onscroll) + return nsGkAtoms::onSVGScroll; + if (aAttr == nsGkAtoms::onzoom) + return nsGkAtoms::onSVGZoom; + if (aAttr == nsGkAtoms::onbegin) + return nsGkAtoms::onbeginEvent; + if (aAttr == nsGkAtoms::onrepeat) + return nsGkAtoms::onrepeatEvent; + if (aAttr == nsGkAtoms::onend) + return nsGkAtoms::onendEvent; + + return aAttr; +} + +SVGSVGElement * +nsSVGElement::GetCtx() const +{ + nsIContent* ancestor = GetFlattenedTreeParent(); + + while (ancestor && ancestor->IsSVGElement()) { + if (ancestor->IsSVGElement(nsGkAtoms::foreignObject)) { + return nullptr; + } + if (ancestor->IsSVGElement(nsGkAtoms::svg)) { + return static_cast<SVGSVGElement*>(ancestor); + } + ancestor = ancestor->GetFlattenedTreeParent(); + } + + // we don't have an ancestor <svg> element... + return nullptr; +} + +/* virtual */ gfxMatrix +nsSVGElement::PrependLocalTransformsTo( + const gfxMatrix &aMatrix, SVGTransformTypes aWhich) const +{ + return aMatrix; +} + +nsSVGElement::LengthAttributesInfo +nsSVGElement::GetLengthInfo() +{ + return LengthAttributesInfo(nullptr, nullptr, 0); +} + +void nsSVGElement::LengthAttributesInfo::Reset(uint8_t aAttrEnum) +{ + mLengths[aAttrEnum].Init(mLengthInfo[aAttrEnum].mCtxType, + aAttrEnum, + mLengthInfo[aAttrEnum].mDefaultValue, + mLengthInfo[aAttrEnum].mDefaultUnitType); +} + +void +nsSVGElement::SetLength(nsIAtom* aName, const nsSVGLength2 &aLength) +{ + LengthAttributesInfo lengthInfo = GetLengthInfo(); + + for (uint32_t i = 0; i < lengthInfo.mLengthCount; i++) { + if (aName == *lengthInfo.mLengthInfo[i].mName) { + lengthInfo.mLengths[i] = aLength; + DidAnimateLength(i); + return; + } + } + MOZ_ASSERT(false, "no length found to set"); +} + +nsAttrValue +nsSVGElement::WillChangeLength(uint8_t aAttrEnum) +{ + return WillChangeValue(*GetLengthInfo().mLengthInfo[aAttrEnum].mName); +} + +void +nsSVGElement::DidChangeLength(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue) +{ + LengthAttributesInfo info = GetLengthInfo(); + + NS_ASSERTION(info.mLengthCount > 0, + "DidChangeLength on element with no length attribs"); + NS_ASSERTION(aAttrEnum < info.mLengthCount, "aAttrEnum out of range"); + + nsAttrValue newValue; + newValue.SetTo(info.mLengths[aAttrEnum], nullptr); + + DidChangeValue(*info.mLengthInfo[aAttrEnum].mName, aEmptyOrOldValue, + newValue); +} + +void +nsSVGElement::DidAnimateLength(uint8_t aAttrEnum) +{ + ClearAnyCachedPath(); + + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + LengthAttributesInfo info = GetLengthInfo(); + frame->AttributeChanged(kNameSpaceID_None, + *info.mLengthInfo[aAttrEnum].mName, + nsIDOMMutationEvent::MODIFICATION); + } +} + +nsSVGLength2* +nsSVGElement::GetAnimatedLength(const nsIAtom *aAttrName) +{ + LengthAttributesInfo lengthInfo = GetLengthInfo(); + + for (uint32_t i = 0; i < lengthInfo.mLengthCount; i++) { + if (aAttrName == *lengthInfo.mLengthInfo[i].mName) { + return &lengthInfo.mLengths[i]; + } + } + MOZ_ASSERT(false, "no matching length found"); + return nullptr; +} + +void +nsSVGElement::GetAnimatedLengthValues(float *aFirst, ...) +{ + LengthAttributesInfo info = GetLengthInfo(); + + NS_ASSERTION(info.mLengthCount > 0, + "GetAnimatedLengthValues on element with no length attribs"); + + SVGSVGElement *ctx = nullptr; + + float *f = aFirst; + uint32_t i = 0; + + va_list args; + va_start(args, aFirst); + + while (f && i < info.mLengthCount) { + uint8_t type = info.mLengths[i].GetSpecifiedUnitType(); + if (!ctx) { + if (type != nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER && + type != nsIDOMSVGLength::SVG_LENGTHTYPE_PX) + ctx = GetCtx(); + } + if (type == nsIDOMSVGLength::SVG_LENGTHTYPE_EMS || + type == nsIDOMSVGLength::SVG_LENGTHTYPE_EXS) + *f = info.mLengths[i++].GetAnimValue(this); + else + *f = info.mLengths[i++].GetAnimValue(ctx); + f = va_arg(args, float*); + } + + va_end(args); +} + +nsSVGElement::LengthListAttributesInfo +nsSVGElement::GetLengthListInfo() +{ + return LengthListAttributesInfo(nullptr, nullptr, 0); +} + +void +nsSVGElement::LengthListAttributesInfo::Reset(uint8_t aAttrEnum) +{ + mLengthLists[aAttrEnum].ClearBaseValue(aAttrEnum); + // caller notifies +} + +nsAttrValue +nsSVGElement::WillChangeLengthList(uint8_t aAttrEnum) +{ + return WillChangeValue(*GetLengthListInfo().mLengthListInfo[aAttrEnum].mName); +} + +void +nsSVGElement::DidChangeLengthList(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue) +{ + LengthListAttributesInfo info = GetLengthListInfo(); + + NS_ASSERTION(info.mLengthListCount > 0, + "DidChangeLengthList on element with no length list attribs"); + NS_ASSERTION(aAttrEnum < info.mLengthListCount, "aAttrEnum out of range"); + + nsAttrValue newValue; + newValue.SetTo(info.mLengthLists[aAttrEnum].GetBaseValue(), nullptr); + + DidChangeValue(*info.mLengthListInfo[aAttrEnum].mName, aEmptyOrOldValue, + newValue); +} + +void +nsSVGElement::DidAnimateLengthList(uint8_t aAttrEnum) +{ + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + LengthListAttributesInfo info = GetLengthListInfo(); + frame->AttributeChanged(kNameSpaceID_None, + *info.mLengthListInfo[aAttrEnum].mName, + nsIDOMMutationEvent::MODIFICATION); + } +} + +void +nsSVGElement::GetAnimatedLengthListValues(SVGUserUnitList *aFirst, ...) +{ + LengthListAttributesInfo info = GetLengthListInfo(); + + NS_ASSERTION(info.mLengthListCount > 0, + "GetAnimatedLengthListValues on element with no length list attribs"); + + SVGUserUnitList *list = aFirst; + uint32_t i = 0; + + va_list args; + va_start(args, aFirst); + + while (list && i < info.mLengthListCount) { + list->Init(&(info.mLengthLists[i].GetAnimValue()), this, info.mLengthListInfo[i].mAxis); + ++i; + list = va_arg(args, SVGUserUnitList*); + } + + va_end(args); +} + +SVGAnimatedLengthList* +nsSVGElement::GetAnimatedLengthList(uint8_t aAttrEnum) +{ + LengthListAttributesInfo info = GetLengthListInfo(); + if (aAttrEnum < info.mLengthListCount) { + return &(info.mLengthLists[aAttrEnum]); + } + NS_NOTREACHED("Bad attrEnum"); + return nullptr; +} + + +nsSVGElement::NumberListAttributesInfo +nsSVGElement::GetNumberListInfo() +{ + return NumberListAttributesInfo(nullptr, nullptr, 0); +} + +void +nsSVGElement::NumberListAttributesInfo::Reset(uint8_t aAttrEnum) +{ + MOZ_ASSERT(aAttrEnum < mNumberListCount, "Bad attr enum"); + mNumberLists[aAttrEnum].ClearBaseValue(aAttrEnum); + // caller notifies +} + +nsAttrValue +nsSVGElement::WillChangeNumberList(uint8_t aAttrEnum) +{ + return WillChangeValue(*GetNumberListInfo().mNumberListInfo[aAttrEnum].mName); +} + +void +nsSVGElement::DidChangeNumberList(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue) +{ + NumberListAttributesInfo info = GetNumberListInfo(); + + MOZ_ASSERT(info.mNumberListCount > 0, + "DidChangeNumberList on element with no number list attribs"); + MOZ_ASSERT(aAttrEnum < info.mNumberListCount, + "aAttrEnum out of range"); + + nsAttrValue newValue; + newValue.SetTo(info.mNumberLists[aAttrEnum].GetBaseValue(), nullptr); + + DidChangeValue(*info.mNumberListInfo[aAttrEnum].mName, aEmptyOrOldValue, + newValue); +} + +void +nsSVGElement::DidAnimateNumberList(uint8_t aAttrEnum) +{ + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + NumberListAttributesInfo info = GetNumberListInfo(); + MOZ_ASSERT(aAttrEnum < info.mNumberListCount, "aAttrEnum out of range"); + + frame->AttributeChanged(kNameSpaceID_None, + *info.mNumberListInfo[aAttrEnum].mName, + nsIDOMMutationEvent::MODIFICATION); + } +} + +SVGAnimatedNumberList* +nsSVGElement::GetAnimatedNumberList(uint8_t aAttrEnum) +{ + NumberListAttributesInfo info = GetNumberListInfo(); + if (aAttrEnum < info.mNumberListCount) { + return &(info.mNumberLists[aAttrEnum]); + } + MOZ_ASSERT(false, "Bad attrEnum"); + return nullptr; +} + +SVGAnimatedNumberList* +nsSVGElement::GetAnimatedNumberList(nsIAtom *aAttrName) +{ + NumberListAttributesInfo info = GetNumberListInfo(); + for (uint32_t i = 0; i < info.mNumberListCount; i++) { + if (aAttrName == *info.mNumberListInfo[i].mName) { + return &info.mNumberLists[i]; + } + } + MOZ_ASSERT(false, "Bad caller"); + return nullptr; +} + +nsAttrValue +nsSVGElement::WillChangePointList() +{ + MOZ_ASSERT(GetPointListAttrName(), + "Changing non-existent point list?"); + return WillChangeValue(GetPointListAttrName()); +} + +void +nsSVGElement::DidChangePointList(const nsAttrValue& aEmptyOrOldValue) +{ + MOZ_ASSERT(GetPointListAttrName(), + "Changing non-existent point list?"); + + nsAttrValue newValue; + newValue.SetTo(GetAnimatedPointList()->GetBaseValue(), nullptr); + + DidChangeValue(GetPointListAttrName(), aEmptyOrOldValue, newValue); +} + +void +nsSVGElement::DidAnimatePointList() +{ + MOZ_ASSERT(GetPointListAttrName(), + "Animating non-existent path data?"); + + ClearAnyCachedPath(); + + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + frame->AttributeChanged(kNameSpaceID_None, + GetPointListAttrName(), + nsIDOMMutationEvent::MODIFICATION); + } +} + +nsAttrValue +nsSVGElement::WillChangePathSegList() +{ + MOZ_ASSERT(GetPathDataAttrName(), + "Changing non-existent path seg list?"); + return WillChangeValue(GetPathDataAttrName()); +} + +void +nsSVGElement::DidChangePathSegList(const nsAttrValue& aEmptyOrOldValue) +{ + MOZ_ASSERT(GetPathDataAttrName(), + "Changing non-existent path seg list?"); + + nsAttrValue newValue; + newValue.SetTo(GetAnimPathSegList()->GetBaseValue(), nullptr); + + DidChangeValue(GetPathDataAttrName(), aEmptyOrOldValue, newValue); +} + +void +nsSVGElement::DidAnimatePathSegList() +{ + MOZ_ASSERT(GetPathDataAttrName(), + "Animating non-existent path data?"); + + ClearAnyCachedPath(); + + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + frame->AttributeChanged(kNameSpaceID_None, + GetPathDataAttrName(), + nsIDOMMutationEvent::MODIFICATION); + } +} + +nsSVGElement::NumberAttributesInfo +nsSVGElement::GetNumberInfo() +{ + return NumberAttributesInfo(nullptr, nullptr, 0); +} + +void nsSVGElement::NumberAttributesInfo::Reset(uint8_t aAttrEnum) +{ + mNumbers[aAttrEnum].Init(aAttrEnum, + mNumberInfo[aAttrEnum].mDefaultValue); +} + +void +nsSVGElement::DidChangeNumber(uint8_t aAttrEnum) +{ + NumberAttributesInfo info = GetNumberInfo(); + + NS_ASSERTION(info.mNumberCount > 0, + "DidChangeNumber on element with no number attribs"); + NS_ASSERTION(aAttrEnum < info.mNumberCount, "aAttrEnum out of range"); + + nsAttrValue attrValue; + attrValue.SetTo(info.mNumbers[aAttrEnum].GetBaseValue(), nullptr); + + SetParsedAttr(kNameSpaceID_None, *info.mNumberInfo[aAttrEnum].mName, nullptr, + attrValue, true); +} + +void +nsSVGElement::DidAnimateNumber(uint8_t aAttrEnum) +{ + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + NumberAttributesInfo info = GetNumberInfo(); + frame->AttributeChanged(kNameSpaceID_None, + *info.mNumberInfo[aAttrEnum].mName, + nsIDOMMutationEvent::MODIFICATION); + } +} + +void +nsSVGElement::GetAnimatedNumberValues(float *aFirst, ...) +{ + NumberAttributesInfo info = GetNumberInfo(); + + NS_ASSERTION(info.mNumberCount > 0, + "GetAnimatedNumberValues on element with no number attribs"); + + float *f = aFirst; + uint32_t i = 0; + + va_list args; + va_start(args, aFirst); + + while (f && i < info.mNumberCount) { + *f = info.mNumbers[i++].GetAnimValue(); + f = va_arg(args, float*); + } + va_end(args); +} + +nsSVGElement::NumberPairAttributesInfo +nsSVGElement::GetNumberPairInfo() +{ + return NumberPairAttributesInfo(nullptr, nullptr, 0); +} + +void nsSVGElement::NumberPairAttributesInfo::Reset(uint8_t aAttrEnum) +{ + mNumberPairs[aAttrEnum].Init(aAttrEnum, + mNumberPairInfo[aAttrEnum].mDefaultValue1, + mNumberPairInfo[aAttrEnum].mDefaultValue2); +} + +nsAttrValue +nsSVGElement::WillChangeNumberPair(uint8_t aAttrEnum) +{ + return WillChangeValue(*GetNumberPairInfo().mNumberPairInfo[aAttrEnum].mName); +} + +void +nsSVGElement::DidChangeNumberPair(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue) +{ + NumberPairAttributesInfo info = GetNumberPairInfo(); + + NS_ASSERTION(info.mNumberPairCount > 0, + "DidChangePairNumber on element with no number pair attribs"); + NS_ASSERTION(aAttrEnum < info.mNumberPairCount, "aAttrEnum out of range"); + + nsAttrValue newValue; + newValue.SetTo(info.mNumberPairs[aAttrEnum], nullptr); + + DidChangeValue(*info.mNumberPairInfo[aAttrEnum].mName, aEmptyOrOldValue, + newValue); +} + +void +nsSVGElement::DidAnimateNumberPair(uint8_t aAttrEnum) +{ + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + NumberPairAttributesInfo info = GetNumberPairInfo(); + frame->AttributeChanged(kNameSpaceID_None, + *info.mNumberPairInfo[aAttrEnum].mName, + nsIDOMMutationEvent::MODIFICATION); + } +} + +nsSVGElement::IntegerAttributesInfo +nsSVGElement::GetIntegerInfo() +{ + return IntegerAttributesInfo(nullptr, nullptr, 0); +} + +void nsSVGElement::IntegerAttributesInfo::Reset(uint8_t aAttrEnum) +{ + mIntegers[aAttrEnum].Init(aAttrEnum, + mIntegerInfo[aAttrEnum].mDefaultValue); +} + +void +nsSVGElement::DidChangeInteger(uint8_t aAttrEnum) +{ + IntegerAttributesInfo info = GetIntegerInfo(); + + NS_ASSERTION(info.mIntegerCount > 0, + "DidChangeInteger on element with no integer attribs"); + NS_ASSERTION(aAttrEnum < info.mIntegerCount, "aAttrEnum out of range"); + + nsAttrValue attrValue; + attrValue.SetTo(info.mIntegers[aAttrEnum].GetBaseValue(), nullptr); + + SetParsedAttr(kNameSpaceID_None, *info.mIntegerInfo[aAttrEnum].mName, nullptr, + attrValue, true); +} + +void +nsSVGElement::DidAnimateInteger(uint8_t aAttrEnum) +{ + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + IntegerAttributesInfo info = GetIntegerInfo(); + frame->AttributeChanged(kNameSpaceID_None, + *info.mIntegerInfo[aAttrEnum].mName, + nsIDOMMutationEvent::MODIFICATION); + } +} + +void +nsSVGElement::GetAnimatedIntegerValues(int32_t *aFirst, ...) +{ + IntegerAttributesInfo info = GetIntegerInfo(); + + NS_ASSERTION(info.mIntegerCount > 0, + "GetAnimatedIntegerValues on element with no integer attribs"); + + int32_t *n = aFirst; + uint32_t i = 0; + + va_list args; + va_start(args, aFirst); + + while (n && i < info.mIntegerCount) { + *n = info.mIntegers[i++].GetAnimValue(); + n = va_arg(args, int32_t*); + } + va_end(args); +} + +nsSVGElement::IntegerPairAttributesInfo +nsSVGElement::GetIntegerPairInfo() +{ + return IntegerPairAttributesInfo(nullptr, nullptr, 0); +} + +void nsSVGElement::IntegerPairAttributesInfo::Reset(uint8_t aAttrEnum) +{ + mIntegerPairs[aAttrEnum].Init(aAttrEnum, + mIntegerPairInfo[aAttrEnum].mDefaultValue1, + mIntegerPairInfo[aAttrEnum].mDefaultValue2); +} + +nsAttrValue +nsSVGElement::WillChangeIntegerPair(uint8_t aAttrEnum) +{ + return WillChangeValue( + *GetIntegerPairInfo().mIntegerPairInfo[aAttrEnum].mName); +} + +void +nsSVGElement::DidChangeIntegerPair(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue) +{ + IntegerPairAttributesInfo info = GetIntegerPairInfo(); + + NS_ASSERTION(info.mIntegerPairCount > 0, + "DidChangeIntegerPair on element with no integer pair attribs"); + NS_ASSERTION(aAttrEnum < info.mIntegerPairCount, "aAttrEnum out of range"); + + nsAttrValue newValue; + newValue.SetTo(info.mIntegerPairs[aAttrEnum], nullptr); + + DidChangeValue(*info.mIntegerPairInfo[aAttrEnum].mName, aEmptyOrOldValue, + newValue); +} + +void +nsSVGElement::DidAnimateIntegerPair(uint8_t aAttrEnum) +{ + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + IntegerPairAttributesInfo info = GetIntegerPairInfo(); + frame->AttributeChanged(kNameSpaceID_None, + *info.mIntegerPairInfo[aAttrEnum].mName, + nsIDOMMutationEvent::MODIFICATION); + } +} + +nsSVGElement::AngleAttributesInfo +nsSVGElement::GetAngleInfo() +{ + return AngleAttributesInfo(nullptr, nullptr, 0); +} + +void nsSVGElement::AngleAttributesInfo::Reset(uint8_t aAttrEnum) +{ + mAngles[aAttrEnum].Init(aAttrEnum, + mAngleInfo[aAttrEnum].mDefaultValue, + mAngleInfo[aAttrEnum].mDefaultUnitType); +} + +nsAttrValue +nsSVGElement::WillChangeAngle(uint8_t aAttrEnum) +{ + return WillChangeValue(*GetAngleInfo().mAngleInfo[aAttrEnum].mName); +} + +void +nsSVGElement::DidChangeAngle(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue) +{ + AngleAttributesInfo info = GetAngleInfo(); + + NS_ASSERTION(info.mAngleCount > 0, + "DidChangeAngle on element with no angle attribs"); + NS_ASSERTION(aAttrEnum < info.mAngleCount, "aAttrEnum out of range"); + + nsAttrValue newValue; + newValue.SetTo(info.mAngles[aAttrEnum], nullptr); + + DidChangeValue(*info.mAngleInfo[aAttrEnum].mName, aEmptyOrOldValue, newValue); +} + +void +nsSVGElement::DidAnimateAngle(uint8_t aAttrEnum) +{ + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + AngleAttributesInfo info = GetAngleInfo(); + frame->AttributeChanged(kNameSpaceID_None, + *info.mAngleInfo[aAttrEnum].mName, + nsIDOMMutationEvent::MODIFICATION); + } +} + +nsSVGElement::BooleanAttributesInfo +nsSVGElement::GetBooleanInfo() +{ + return BooleanAttributesInfo(nullptr, nullptr, 0); +} + +void nsSVGElement::BooleanAttributesInfo::Reset(uint8_t aAttrEnum) +{ + mBooleans[aAttrEnum].Init(aAttrEnum, + mBooleanInfo[aAttrEnum].mDefaultValue); +} + +void +nsSVGElement::DidChangeBoolean(uint8_t aAttrEnum) +{ + BooleanAttributesInfo info = GetBooleanInfo(); + + NS_ASSERTION(info.mBooleanCount > 0, + "DidChangeBoolean on element with no boolean attribs"); + NS_ASSERTION(aAttrEnum < info.mBooleanCount, "aAttrEnum out of range"); + + nsAttrValue attrValue(info.mBooleans[aAttrEnum].GetBaseValueAtom()); + SetParsedAttr(kNameSpaceID_None, *info.mBooleanInfo[aAttrEnum].mName, nullptr, + attrValue, true); +} + +void +nsSVGElement::DidAnimateBoolean(uint8_t aAttrEnum) +{ + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + BooleanAttributesInfo info = GetBooleanInfo(); + frame->AttributeChanged(kNameSpaceID_None, + *info.mBooleanInfo[aAttrEnum].mName, + nsIDOMMutationEvent::MODIFICATION); + } +} + +nsSVGElement::EnumAttributesInfo +nsSVGElement::GetEnumInfo() +{ + return EnumAttributesInfo(nullptr, nullptr, 0); +} + +void nsSVGElement::EnumAttributesInfo::Reset(uint8_t aAttrEnum) +{ + mEnums[aAttrEnum].Init(aAttrEnum, + mEnumInfo[aAttrEnum].mDefaultValue); +} + +void +nsSVGElement::DidChangeEnum(uint8_t aAttrEnum) +{ + EnumAttributesInfo info = GetEnumInfo(); + + NS_ASSERTION(info.mEnumCount > 0, + "DidChangeEnum on element with no enum attribs"); + NS_ASSERTION(aAttrEnum < info.mEnumCount, "aAttrEnum out of range"); + + nsAttrValue attrValue(info.mEnums[aAttrEnum].GetBaseValueAtom(this)); + SetParsedAttr(kNameSpaceID_None, *info.mEnumInfo[aAttrEnum].mName, nullptr, + attrValue, true); +} + +void +nsSVGElement::DidAnimateEnum(uint8_t aAttrEnum) +{ + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + EnumAttributesInfo info = GetEnumInfo(); + frame->AttributeChanged(kNameSpaceID_None, + *info.mEnumInfo[aAttrEnum].mName, + nsIDOMMutationEvent::MODIFICATION); + } +} + +nsSVGViewBox * +nsSVGElement::GetViewBox() +{ + return nullptr; +} + +nsAttrValue +nsSVGElement::WillChangeViewBox() +{ + return WillChangeValue(nsGkAtoms::viewBox); +} + +void +nsSVGElement::DidChangeViewBox(const nsAttrValue& aEmptyOrOldValue) +{ + nsSVGViewBox *viewBox = GetViewBox(); + + NS_ASSERTION(viewBox, "DidChangeViewBox on element with no viewBox attrib"); + + nsAttrValue newValue; + newValue.SetTo(*viewBox, nullptr); + + DidChangeValue(nsGkAtoms::viewBox, aEmptyOrOldValue, newValue); +} + +void +nsSVGElement::DidAnimateViewBox() +{ + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + frame->AttributeChanged(kNameSpaceID_None, + nsGkAtoms::viewBox, + nsIDOMMutationEvent::MODIFICATION); + } +} + +SVGAnimatedPreserveAspectRatio * +nsSVGElement::GetPreserveAspectRatio() +{ + return nullptr; +} + +nsAttrValue +nsSVGElement::WillChangePreserveAspectRatio() +{ + return WillChangeValue(nsGkAtoms::preserveAspectRatio); +} + +void +nsSVGElement::DidChangePreserveAspectRatio(const nsAttrValue& aEmptyOrOldValue) +{ + SVGAnimatedPreserveAspectRatio *preserveAspectRatio = + GetPreserveAspectRatio(); + + NS_ASSERTION(preserveAspectRatio, + "DidChangePreserveAspectRatio on element with no " + "preserveAspectRatio attrib"); + + nsAttrValue newValue; + newValue.SetTo(*preserveAspectRatio, nullptr); + + DidChangeValue(nsGkAtoms::preserveAspectRatio, aEmptyOrOldValue, newValue); +} + +void +nsSVGElement::DidAnimatePreserveAspectRatio() +{ + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + frame->AttributeChanged(kNameSpaceID_None, + nsGkAtoms::preserveAspectRatio, + nsIDOMMutationEvent::MODIFICATION); + } +} + +nsAttrValue +nsSVGElement::WillChangeTransformList() +{ + return WillChangeValue(GetTransformListAttrName()); +} + +void +nsSVGElement::DidChangeTransformList(const nsAttrValue& aEmptyOrOldValue) +{ + MOZ_ASSERT(GetTransformListAttrName(), + "Changing non-existent transform list?"); + + // The transform attribute is being set, so we must ensure that the + // SVGAnimatedTransformList is/has been allocated: + nsAttrValue newValue; + newValue.SetTo(GetAnimatedTransformList(DO_ALLOCATE)->GetBaseValue(), nullptr); + + DidChangeValue(GetTransformListAttrName(), aEmptyOrOldValue, newValue); +} + +void +nsSVGElement::DidAnimateTransformList(int32_t aModType) +{ + MOZ_ASSERT(GetTransformListAttrName(), + "Animating non-existent transform data?"); + + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + nsIAtom *transformAttr = GetTransformListAttrName(); + frame->AttributeChanged(kNameSpaceID_None, + transformAttr, + aModType); + // When script changes the 'transform' attribute, Element::SetAttrAndNotify + // will call nsNodeUtills::AttributeChanged, under which + // SVGTransformableElement::GetAttributeChangeHint will be called and an + // appropriate change event posted to update our frame's overflow rects. + // The SetAttrAndNotify doesn't happen for transform changes caused by + // 'animateTransform' though (and sending out the mutation events that + // nsNodeUtills::AttributeChanged dispatches would be inappropriate + // anyway), so we need to post the change event ourself. + nsChangeHint changeHint = GetAttributeChangeHint(transformAttr, aModType); + if (changeHint) { + nsLayoutUtils::PostRestyleEvent(this, nsRestyleHint(0), changeHint); + } + } +} + +nsSVGElement::StringAttributesInfo +nsSVGElement::GetStringInfo() +{ + return StringAttributesInfo(nullptr, nullptr, 0); +} + +void nsSVGElement::StringAttributesInfo::Reset(uint8_t aAttrEnum) +{ + mStrings[aAttrEnum].Init(aAttrEnum); +} + +void nsSVGElement::GetStringBaseValue(uint8_t aAttrEnum, nsAString& aResult) const +{ + nsSVGElement::StringAttributesInfo info = const_cast<nsSVGElement*>(this)->GetStringInfo(); + + NS_ASSERTION(info.mStringCount > 0, + "GetBaseValue on element with no string attribs"); + + NS_ASSERTION(aAttrEnum < info.mStringCount, "aAttrEnum out of range"); + + GetAttr(info.mStringInfo[aAttrEnum].mNamespaceID, + *info.mStringInfo[aAttrEnum].mName, aResult); +} + +void nsSVGElement::SetStringBaseValue(uint8_t aAttrEnum, const nsAString& aValue) +{ + nsSVGElement::StringAttributesInfo info = GetStringInfo(); + + NS_ASSERTION(info.mStringCount > 0, + "SetBaseValue on element with no string attribs"); + + NS_ASSERTION(aAttrEnum < info.mStringCount, "aAttrEnum out of range"); + + SetAttr(info.mStringInfo[aAttrEnum].mNamespaceID, + *info.mStringInfo[aAttrEnum].mName, aValue, true); +} + +void +nsSVGElement::DidAnimateString(uint8_t aAttrEnum) +{ + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + StringAttributesInfo info = GetStringInfo(); + frame->AttributeChanged(info.mStringInfo[aAttrEnum].mNamespaceID, + *info.mStringInfo[aAttrEnum].mName, + nsIDOMMutationEvent::MODIFICATION); + } +} + +nsSVGElement::StringListAttributesInfo +nsSVGElement::GetStringListInfo() +{ + return StringListAttributesInfo(nullptr, nullptr, 0); +} + +nsAttrValue +nsSVGElement::WillChangeStringList(bool aIsConditionalProcessingAttribute, + uint8_t aAttrEnum) +{ + nsIAtom* name; + if (aIsConditionalProcessingAttribute) { + nsCOMPtr<SVGTests> tests(do_QueryInterface(static_cast<nsIDOMSVGElement*>(this))); + name = tests->GetAttrName(aAttrEnum); + } else { + name = *GetStringListInfo().mStringListInfo[aAttrEnum].mName; + } + return WillChangeValue(name); +} + +void +nsSVGElement::DidChangeStringList(bool aIsConditionalProcessingAttribute, + uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue) +{ + nsIAtom* name; + nsAttrValue newValue; + nsCOMPtr<SVGTests> tests; + + if (aIsConditionalProcessingAttribute) { + tests = do_QueryObject(this); + name = tests->GetAttrName(aAttrEnum); + tests->GetAttrValue(aAttrEnum, newValue); + } else { + StringListAttributesInfo info = GetStringListInfo(); + + NS_ASSERTION(info.mStringListCount > 0, + "DidChangeStringList on element with no string list attribs"); + NS_ASSERTION(aAttrEnum < info.mStringListCount, "aAttrEnum out of range"); + + name = *info.mStringListInfo[aAttrEnum].mName; + newValue.SetTo(info.mStringLists[aAttrEnum], nullptr); + } + + DidChangeValue(name, aEmptyOrOldValue, newValue); + + if (aIsConditionalProcessingAttribute) { + tests->MaybeInvalidate(); + } +} + +void +nsSVGElement::StringListAttributesInfo::Reset(uint8_t aAttrEnum) +{ + mStringLists[aAttrEnum].Clear(); + // caller notifies +} + +nsresult +nsSVGElement::ReportAttributeParseFailure(nsIDocument* aDocument, + nsIAtom* aAttribute, + const nsAString& aValue) +{ + const nsAFlatString& attributeValue = PromiseFlatString(aValue); + const char16_t *strings[] = { aAttribute->GetUTF16String(), + attributeValue.get() }; + return SVGContentUtils::ReportToConsole(aDocument, + "AttributeParseWarning", + strings, ArrayLength(strings)); +} + +void +nsSVGElement::RecompileScriptEventListeners() +{ + int32_t i, count = mAttrsAndChildren.AttrCount(); + for (i = 0; i < count; ++i) { + const nsAttrName *name = mAttrsAndChildren.AttrNameAt(i); + + // Eventlistenener-attributes are always in the null namespace + if (!name->IsAtom()) { + continue; + } + + nsIAtom *attr = name->Atom(); + if (!IsEventAttributeName(attr)) { + continue; + } + + nsAutoString value; + GetAttr(kNameSpaceID_None, attr, value); + SetEventHandler(GetEventNameForAttr(attr), value, true); + } +} + +nsISMILAttr* +nsSVGElement::GetAnimatedAttr(int32_t aNamespaceID, nsIAtom* aName) +{ + if (aNamespaceID == kNameSpaceID_None) { + // We check mapped-into-style attributes first so that animations + // targeting width/height on outer-<svg> don't appear to be ignored + // because we returned a nsISMILAttr for the corresponding + // SVGAnimatedLength. + + // Mapped attributes: + if (IsAttributeMapped(aName)) { + nsCSSPropertyID prop = + nsCSSProps::LookupProperty(nsDependentAtomString(aName), + CSSEnabledState::eForAllContent); + // Check IsPropertyAnimatable to avoid attributes that... + // - map to explicitly unanimatable properties (e.g. 'direction') + // - map to unsupported attributes (e.g. 'glyph-orientation-horizontal') + if (nsSMILCSSProperty::IsPropertyAnimatable(prop)) { + return new nsSMILMappedAttribute(prop, this); + } + } + + // Transforms: + if (GetTransformListAttrName() == aName) { + // The transform attribute is being animated, so we must ensure that the + // SVGAnimatedTransformList is/has been allocated: + return GetAnimatedTransformList(DO_ALLOCATE)->ToSMILAttr(this); + } + + // Motion (fake 'attribute' for animateMotion) + if (aName == nsGkAtoms::mozAnimateMotionDummyAttr) { + return new SVGMotionSMILAttr(this); + } + + // Lengths: + LengthAttributesInfo info = GetLengthInfo(); + for (uint32_t i = 0; i < info.mLengthCount; i++) { + if (aName == *info.mLengthInfo[i].mName) { + return info.mLengths[i].ToSMILAttr(this); + } + } + + // Numbers: + { + NumberAttributesInfo info = GetNumberInfo(); + for (uint32_t i = 0; i < info.mNumberCount; i++) { + if (aName == *info.mNumberInfo[i].mName) { + return info.mNumbers[i].ToSMILAttr(this); + } + } + } + + // Number Pairs: + { + NumberPairAttributesInfo info = GetNumberPairInfo(); + for (uint32_t i = 0; i < info.mNumberPairCount; i++) { + if (aName == *info.mNumberPairInfo[i].mName) { + return info.mNumberPairs[i].ToSMILAttr(this); + } + } + } + + // Integers: + { + IntegerAttributesInfo info = GetIntegerInfo(); + for (uint32_t i = 0; i < info.mIntegerCount; i++) { + if (aName == *info.mIntegerInfo[i].mName) { + return info.mIntegers[i].ToSMILAttr(this); + } + } + } + + // Integer Pairs: + { + IntegerPairAttributesInfo info = GetIntegerPairInfo(); + for (uint32_t i = 0; i < info.mIntegerPairCount; i++) { + if (aName == *info.mIntegerPairInfo[i].mName) { + return info.mIntegerPairs[i].ToSMILAttr(this); + } + } + } + + // Enumerations: + { + EnumAttributesInfo info = GetEnumInfo(); + for (uint32_t i = 0; i < info.mEnumCount; i++) { + if (aName == *info.mEnumInfo[i].mName) { + return info.mEnums[i].ToSMILAttr(this); + } + } + } + + // Booleans: + { + BooleanAttributesInfo info = GetBooleanInfo(); + for (uint32_t i = 0; i < info.mBooleanCount; i++) { + if (aName == *info.mBooleanInfo[i].mName) { + return info.mBooleans[i].ToSMILAttr(this); + } + } + } + + // Angles: + { + AngleAttributesInfo info = GetAngleInfo(); + for (uint32_t i = 0; i < info.mAngleCount; i++) { + if (aName == *info.mAngleInfo[i].mName) { + return info.mAngles[i].ToSMILAttr(this); + } + } + } + + // viewBox: + if (aName == nsGkAtoms::viewBox) { + nsSVGViewBox *viewBox = GetViewBox(); + return viewBox ? viewBox->ToSMILAttr(this) : nullptr; + } + + // preserveAspectRatio: + if (aName == nsGkAtoms::preserveAspectRatio) { + SVGAnimatedPreserveAspectRatio *preserveAspectRatio = + GetPreserveAspectRatio(); + return preserveAspectRatio ? + preserveAspectRatio->ToSMILAttr(this) : nullptr; + } + + // NumberLists: + { + NumberListAttributesInfo info = GetNumberListInfo(); + for (uint32_t i = 0; i < info.mNumberListCount; i++) { + if (aName == *info.mNumberListInfo[i].mName) { + MOZ_ASSERT(i <= UCHAR_MAX, "Too many attributes"); + return info.mNumberLists[i].ToSMILAttr(this, uint8_t(i)); + } + } + } + + // LengthLists: + { + LengthListAttributesInfo info = GetLengthListInfo(); + for (uint32_t i = 0; i < info.mLengthListCount; i++) { + if (aName == *info.mLengthListInfo[i].mName) { + MOZ_ASSERT(i <= UCHAR_MAX, "Too many attributes"); + return info.mLengthLists[i].ToSMILAttr(this, + uint8_t(i), + info.mLengthListInfo[i].mAxis, + info.mLengthListInfo[i].mCouldZeroPadList); + } + } + } + + // PointLists: + { + if (GetPointListAttrName() == aName) { + SVGAnimatedPointList *pointList = GetAnimatedPointList(); + if (pointList) { + return pointList->ToSMILAttr(this); + } + } + } + + // PathSegLists: + { + if (GetPathDataAttrName() == aName) { + SVGAnimatedPathSegList *segList = GetAnimPathSegList(); + if (segList) { + return segList->ToSMILAttr(this); + } + } + } + + if (aName == nsGkAtoms::_class) { + return mClassAttribute.ToSMILAttr(this); + } + } + + // Strings + { + StringAttributesInfo info = GetStringInfo(); + for (uint32_t i = 0; i < info.mStringCount; i++) { + if (aNamespaceID == info.mStringInfo[i].mNamespaceID && + aName == *info.mStringInfo[i].mName) { + return info.mStrings[i].ToSMILAttr(this); + } + } + } + + return nullptr; +} + +void +nsSVGElement::AnimationNeedsResample() +{ + nsIDocument* doc = GetComposedDoc(); + if (doc && doc->HasAnimationController()) { + doc->GetAnimationController()->SetResampleNeeded(); + } +} + +void +nsSVGElement::FlushAnimations() +{ + nsIDocument* doc = GetComposedDoc(); + if (doc && doc->HasAnimationController()) { + doc->GetAnimationController()->FlushResampleRequests(); + } +} |