Mypal/dom/svg/nsSVGElement.cpp

2774 lines
83 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/ArrayUtils.h"
#include "mozilla/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 "mozAutoDocUpdate.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
bool oldValueSet;
rv = mAttrsAndChildren.SetAndSwapAttr(nsGkAtoms::style, attrValue,
&oldValueSet);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
nsSVGElement::AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
const nsAttrValue* aValue,
const nsAttrValue* aOldValue, 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, aOldValue,
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, because if GetParsedAttr
// returns non-null its return value is what will get passed to BeforeSetAttr,
// not matter what our mutation listener situation is.
//
// Also, we should be careful to always return this value to benefit from
// return value optimization.
nsAttrValue emptyOrOldAttrValue;
const nsAttrValue* attrValue = GetParsedAttr(aName);
// 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);
// 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");
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);
nsIDocument* document = GetComposedDoc();
mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL,
kNotifyDocumentObservers);
// XXX Really, the fourth argument to SetAttrAndNotify should be null if
// aEmptyOrOldValue does not represent the actual previous value of the
// attribute, but currently SVG elements do not even use the old attribute
// value in |AfterSetAttr|, so this should be ok.
SetAttrAndNotify(kNameSpaceID_None, aName, nullptr, &aEmptyOrOldValue,
aNewValue, modType, hasListeners, kNotifyDocumentObservers,
kCallAfterSetAttr, document, updateBatch);
}
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);
bool oldValueSet;
mAttrsAndChildren.SetAndSwapAttr(aName, oldAttrValue, &oldValueSet);
}
/* 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();
}
}