/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/SVGElement.h" #include "mozilla/dom/MutationEventBinding.h" #include "mozilla/dom/MutationObservers.h" #include "mozilla/dom/CSSRuleBinding.h" #include "mozilla/dom/SVGElementBinding.h" #include "mozilla/dom/SVGGeometryElement.h" #include "mozilla/dom/SVGLengthBinding.h" #include "mozilla/dom/SVGSVGElement.h" #include "mozilla/dom/SVGTests.h" #include "mozilla/dom/SVGUnitTypesBinding.h" #include "mozilla/dom/Element.h" #include "mozilla/ArrayUtils.h" #include "mozilla/DebugOnly.h" #include "mozilla/DeclarationBlock.h" #include "mozilla/EventListenerManager.h" #include "mozilla/InternalMutationEvent.h" #include "mozilla/PresShell.h" #include "mozilla/RestyleManager.h" #include "mozilla/SMILAnimationController.h" #include "mozilla/StaticPrefs_layout.h" #include "mozilla/SVGContentUtils.h" #include "mozilla/Unused.h" #include "mozAutoDocUpdate.h" #include "nsAttrValueOrString.h" #include "nsCSSProps.h" #include "nsCSSValue.h" #include "nsContentUtils.h" #include "nsDOMCSSAttrDeclaration.h" #include "nsICSSDeclaration.h" #include "nsIContentInlines.h" #include "mozilla/dom/Document.h" #include "nsError.h" #include "nsGkAtoms.h" #include "nsIFrame.h" #include "nsQueryObject.h" #include "nsLayoutUtils.h" #include "SVGAnimatedNumberList.h" #include "SVGAnimatedLengthList.h" #include "SVGAnimatedPointList.h" #include "SVGAnimatedPathSegList.h" #include "SVGAnimatedTransformList.h" #include "SVGAnimatedBoolean.h" #include "SVGAnimatedEnumeration.h" #include "SVGAnimatedInteger.h" #include "SVGAnimatedIntegerPair.h" #include "SVGAnimatedLength.h" #include "SVGAnimatedNumber.h" #include "SVGAnimatedNumberPair.h" #include "SVGAnimatedOrient.h" #include "SVGAnimatedString.h" #include "SVGAnimatedViewBox.h" #include "SVGGeometryProperty.h" #include "SVGMotionSMILAttr.h" #include // This is needed to ensure correct handling of calls to the // vararg-list methods in this file: // SVGElement::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( mozilla::dom::Element** aResult, already_AddRefed&& aNodeInfo) { RefPtr nodeInfo(aNodeInfo); auto* nim = nodeInfo->NodeInfoManager(); RefPtr it = new (nim) mozilla::dom::SVGElement(nodeInfo.forget()); nsresult rv = it->Init(); if (NS_FAILED(rv)) { return rv; } it.forget(aResult); return rv; } namespace mozilla::dom { using namespace SVGUnitTypes_Binding; NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGElement) // Use the CC variant of this, even though this class does not define // a new CC participant, to make QIing to the CC interfaces faster. NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(SVGElement, SVGElementBase, SVGElement) SVGEnumMapping SVGElement::sSVGUnitTypesMap[] = { {nsGkAtoms::userSpaceOnUse, SVG_UNIT_TYPE_USERSPACEONUSE}, {nsGkAtoms::objectBoundingBox, SVG_UNIT_TYPE_OBJECTBOUNDINGBOX}, {nullptr, 0}}; SVGElement::SVGElement(already_AddRefed&& aNodeInfo) : SVGElementBase(std::move(aNodeInfo)) {} SVGElement::~SVGElement() { OwnerDoc()->UnscheduleSVGForPresAttrEvaluation(this); } JSObject* SVGElement::WrapNode(JSContext* aCx, JS::Handle aGivenProto) { return SVGElement_Binding::Wrap(aCx, this, aGivenProto); } template void SVGElement::AttributesInfo::ResetAll() { for (uint32_t i = 0; i < mCount; ++i) { Reset(i); } } template void SVGElement::AttributesInfo::CopyAllFrom( const AttributesInfo& aOther) { MOZ_DIAGNOSTIC_ASSERT(mCount == aOther.mCount, "Should only be called on clones"); for (uint32_t i = 0; i < mCount; ++i) { mValues[i] = aOther.mValues[i]; } } template <> void SVGElement::LengthAttributesInfo::Reset(uint8_t aAttrEnum) { mValues[aAttrEnum].Init(mInfos[aAttrEnum].mCtxType, aAttrEnum, mInfos[aAttrEnum].mDefaultValue, mInfos[aAttrEnum].mDefaultUnitType); } template <> void SVGElement::LengthListAttributesInfo::Reset(uint8_t aAttrEnum) { mValues[aAttrEnum].ClearBaseValue(aAttrEnum); // caller notifies } template <> void SVGElement::NumberListAttributesInfo::Reset(uint8_t aAttrEnum) { MOZ_ASSERT(aAttrEnum < mCount, "Bad attr enum"); mValues[aAttrEnum].ClearBaseValue(aAttrEnum); // caller notifies } template <> void SVGElement::NumberAttributesInfo::Reset(uint8_t aAttrEnum) { mValues[aAttrEnum].Init(aAttrEnum, mInfos[aAttrEnum].mDefaultValue); } template <> void SVGElement::NumberPairAttributesInfo::Reset(uint8_t aAttrEnum) { mValues[aAttrEnum].Init(aAttrEnum, mInfos[aAttrEnum].mDefaultValue1, mInfos[aAttrEnum].mDefaultValue2); } template <> void SVGElement::IntegerAttributesInfo::Reset(uint8_t aAttrEnum) { mValues[aAttrEnum].Init(aAttrEnum, mInfos[aAttrEnum].mDefaultValue); } template <> void SVGElement::IntegerPairAttributesInfo::Reset(uint8_t aAttrEnum) { mValues[aAttrEnum].Init(aAttrEnum, mInfos[aAttrEnum].mDefaultValue1, mInfos[aAttrEnum].mDefaultValue2); } template <> void SVGElement::BooleanAttributesInfo::Reset(uint8_t aAttrEnum) { mValues[aAttrEnum].Init(aAttrEnum, mInfos[aAttrEnum].mDefaultValue); } template <> void SVGElement::StringListAttributesInfo::Reset(uint8_t aAttrEnum) { mValues[aAttrEnum].Clear(); // caller notifies } template <> void SVGElement::EnumAttributesInfo::Reset(uint8_t aAttrEnum) { mValues[aAttrEnum].Init(aAttrEnum, mInfos[aAttrEnum].mDefaultValue); } template <> void SVGElement::StringAttributesInfo::Reset(uint8_t aAttrEnum) { mValues[aAttrEnum].Init(aAttrEnum); } nsresult SVGElement::CopyInnerTo(mozilla::dom::Element* aDest) { nsresult rv = Element::CopyInnerTo(aDest); NS_ENSURE_SUCCESS(rv, rv); auto* dest = static_cast(aDest); // cloning a node must retain its internal nonce slot if (auto* nonce = static_cast(GetProperty(nsGkAtoms::nonce))) { dest->SetNonce(*nonce); } // If our destination is a print document, copy all the relevant length values // etc so that they match the state of the original node. if (aDest->OwnerDoc()->IsStaticDocument()) { dest->GetLengthInfo().CopyAllFrom(GetLengthInfo()); dest->GetNumberInfo().CopyAllFrom(GetNumberInfo()); dest->GetNumberPairInfo().CopyAllFrom(GetNumberPairInfo()); dest->GetIntegerInfo().CopyAllFrom(GetIntegerInfo()); dest->GetIntegerPairInfo().CopyAllFrom(GetIntegerPairInfo()); dest->GetEnumInfo().CopyAllFrom(GetEnumInfo()); dest->GetStringInfo().CopyAllFrom(GetStringInfo()); dest->GetLengthListInfo().CopyAllFrom(GetLengthListInfo()); dest->GetNumberListInfo().CopyAllFrom(GetNumberListInfo()); } return NS_OK; } //---------------------------------------------------------------------- // SVGElement methods void SVGElement::DidAnimateClass() { // For Servo, snapshot the element before we change it. PresShell* presShell = OwnerDoc()->GetPresShell(); if (presShell) { if (nsPresContext* presContext = presShell->GetPresContext()) { presContext->RestyleManager()->ClassAttributeWillBeChangedBySMIL(this); } } nsAutoString src; mClassAttribute.GetAnimValue(src, this); if (!mClassAnimAttr) { mClassAnimAttr = MakeUnique(); } mClassAnimAttr->ParseAtomArray(src); // FIXME(emilio): This re-selector-matches, but we do the snapshot stuff right // above... Is this needed anymore? if (presShell) { presShell->RestyleForAnimation(this, RestyleHint::RESTYLE_SELF); } } nsresult SVGElement::Init() { // Set up length attributes - can't do this in the constructor // because we can't do a virtual call at that point GetLengthInfo().ResetAll(); GetNumberInfo().ResetAll(); GetNumberPairInfo().ResetAll(); GetIntegerInfo().ResetAll(); GetIntegerPairInfo().ResetAll(); GetBooleanInfo().ResetAll(); GetEnumInfo().ResetAll(); if (SVGAnimatedOrient* orient = GetAnimatedOrient()) { orient->Init(); } if (SVGAnimatedViewBox* viewBox = GetAnimatedViewBox()) { viewBox->Init(); } if (SVGAnimatedPreserveAspectRatio* preserveAspectRatio = GetAnimatedPreserveAspectRatio()) { preserveAspectRatio->Init(); } GetLengthListInfo().ResetAll(); GetNumberListInfo().ResetAll(); // 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). GetStringInfo().ResetAll(); return NS_OK; } //---------------------------------------------------------------------- // Implementation //---------------------------------------------------------------------- // nsIContent methods nsresult SVGElement::BindToTree(BindContext& aContext, nsINode& aParent) { nsresult rv = SVGElementBase::BindToTree(aContext, aParent); NS_ENSURE_SUCCESS(rv, rv); // Hide any nonce from the DOM, but keep the internal value of the // nonce by copying and resetting the internal nonce value. if (HasFlag(NODE_HAS_NONCE_AND_HEADER_CSP) && IsInComposedDoc() && OwnerDoc()->GetBrowsingContext()) { nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( "SVGElement::ResetNonce::Runnable", [self = RefPtr(this)]() { nsAutoString nonce; self->GetNonce(nonce); self->SetAttr(kNameSpaceID_None, nsGkAtoms::nonce, u""_ns, true); self->SetNonce(nonce); })); } return NS_OK; } nsresult SVGElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, const nsAttrValue* aValue, const nsAttrValue* aOldValue, nsIPrincipal* aSubjectPrincipal, 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 // mAttrs->mMappedAttributes, meaning it's shared between // elements), the pointer will dangle. See bug 724680. MOZ_ASSERT(!mAttrs.HasMappedAttrs(), "Unexpected use of nsMappedAttributes within SVG"); // If this is an svg presentation attribute we need to map it into // the content declaration block. // XXX For some reason incremental mapping doesn't work, so for now // just delete the style rule and lazily reconstruct it as needed). if (aNamespaceID == kNameSpaceID_None && IsAttributeMapped(aName)) { mContentDeclarationBlock = nullptr; OwnerDoc()->ScheduleSVGForPresAttrEvaluation(this); } if (IsEventAttributeName(aName) && aValue) { MOZ_ASSERT(aValue->Type() == nsAttrValue::eString, "Expected string value for script body"); SetEventHandler(GetEventNameForAttr(aName), aValue->GetStringValue()); } // The nonce will be copied over to an internal slot and cleared from the // Element within BindToTree to avoid CSS Selector nonce exfiltration if // the CSP list contains a header-delivered CSP. if (nsGkAtoms::nonce == aName && kNameSpaceID_None == aNamespaceID) { if (aValue) { SetNonce(aValue->GetStringValue()); if (OwnerDoc()->GetHasCSPDeliveredThroughHeader()) { SetFlags(NODE_HAS_NONCE_AND_HEADER_CSP); } } else { RemoveNonce(); } } return SVGElementBase::AfterSetAttr(aNamespaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify); } bool SVGElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, const nsAString& aValue, nsIPrincipal* aMaybeScriptedPrincipal, nsAttrValue& aResult) { nsresult rv = NS_OK; bool foundMatch = false; bool didSetResult = false; if (aNamespaceID == kNameSpaceID_None) { // Check for SVGAnimatedLength attribute LengthAttributesInfo lengthInfo = GetLengthInfo(); uint32_t i; for (i = 0; i < lengthInfo.mCount; i++) { if (aAttribute == lengthInfo.mInfos[i].mName) { rv = lengthInfo.mValues[i].SetBaseValueString(aValue, this, false); if (NS_FAILED(rv)) { lengthInfo.Reset(i); } else { aResult.SetTo(lengthInfo.mValues[i], &aValue); didSetResult = true; } foundMatch = true; break; } } if (!foundMatch) { // Check for SVGAnimatedLengthList attribute LengthListAttributesInfo lengthListInfo = GetLengthListInfo(); for (i = 0; i < lengthListInfo.mCount; i++) { if (aAttribute == lengthListInfo.mInfos[i].mName) { rv = lengthListInfo.mValues[i].SetBaseValueString(aValue); if (NS_FAILED(rv)) { lengthListInfo.Reset(i); } else { aResult.SetTo(lengthListInfo.mValues[i].GetBaseValue(), &aValue); didSetResult = true; } foundMatch = true; break; } } } if (!foundMatch) { // Check for SVGAnimatedNumberList attribute NumberListAttributesInfo numberListInfo = GetNumberListInfo(); for (i = 0; i < numberListInfo.mCount; i++) { if (aAttribute == numberListInfo.mInfos[i].mName) { rv = numberListInfo.mValues[i].SetBaseValueString(aValue); if (NS_FAILED(rv)) { numberListInfo.Reset(i); } else { aResult.SetTo(numberListInfo.mValues[i].GetBaseValue(), &aValue); didSetResult = true; } foundMatch = true; break; } } } if (!foundMatch) { // Check for SVGAnimatedPointList attribute if (GetPointListAttrName() == aAttribute) { if (SVGAnimatedPointList* pointList = GetAnimatedPointList()) { 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) { if (SVGAnimatedPathSegList* segList = GetAnimPathSegList()) { 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 SVGAnimatedNumber attribute NumberAttributesInfo numberInfo = GetNumberInfo(); for (i = 0; i < numberInfo.mCount; i++) { if (aAttribute == numberInfo.mInfos[i].mName) { rv = numberInfo.mValues[i].SetBaseValueString(aValue, this); if (NS_FAILED(rv)) { numberInfo.Reset(i); } else { aResult.SetTo(numberInfo.mValues[i].GetBaseValue(), &aValue); didSetResult = true; } foundMatch = true; break; } } } if (!foundMatch) { // Check for SVGAnimatedNumberPair attribute NumberPairAttributesInfo numberPairInfo = GetNumberPairInfo(); for (i = 0; i < numberPairInfo.mCount; i++) { if (aAttribute == numberPairInfo.mInfos[i].mName) { rv = numberPairInfo.mValues[i].SetBaseValueString(aValue, this); if (NS_FAILED(rv)) { numberPairInfo.Reset(i); } else { aResult.SetTo(numberPairInfo.mValues[i], &aValue); didSetResult = true; } foundMatch = true; break; } } } if (!foundMatch) { // Check for SVGAnimatedInteger attribute IntegerAttributesInfo integerInfo = GetIntegerInfo(); for (i = 0; i < integerInfo.mCount; i++) { if (aAttribute == integerInfo.mInfos[i].mName) { rv = integerInfo.mValues[i].SetBaseValueString(aValue, this); if (NS_FAILED(rv)) { integerInfo.Reset(i); } else { aResult.SetTo(integerInfo.mValues[i].GetBaseValue(), &aValue); didSetResult = true; } foundMatch = true; break; } } } if (!foundMatch) { // Check for SVGAnimatedIntegerPair attribute IntegerPairAttributesInfo integerPairInfo = GetIntegerPairInfo(); for (i = 0; i < integerPairInfo.mCount; i++) { if (aAttribute == integerPairInfo.mInfos[i].mName) { rv = integerPairInfo.mValues[i].SetBaseValueString(aValue, this); if (NS_FAILED(rv)) { integerPairInfo.Reset(i); } else { aResult.SetTo(integerPairInfo.mValues[i], &aValue); didSetResult = true; } foundMatch = true; break; } } } if (!foundMatch) { // Check for SVGAnimatedBoolean attribute BooleanAttributesInfo booleanInfo = GetBooleanInfo(); for (i = 0; i < booleanInfo.mCount; i++) { if (aAttribute == booleanInfo.mInfos[i].mName) { nsAtom* valAtom = NS_GetStaticAtom(aValue); rv = valAtom ? booleanInfo.mValues[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 SVGAnimatedEnumeration attribute EnumAttributesInfo enumInfo = GetEnumInfo(); for (i = 0; i < enumInfo.mCount; i++) { if (aAttribute == enumInfo.mInfos[i].mName) { RefPtr valAtom = NS_Atomize(aValue); if (!enumInfo.mValues[i].SetBaseValueAtom(valAtom, this)) { // Exact error value does not matter; we just need to mark the // parse as failed. rv = NS_ERROR_FAILURE; enumInfo.Reset(i); } else { aResult.SetTo(valAtom); didSetResult = true; } foundMatch = true; break; } } } if (!foundMatch) { // Check for conditional processing attributes nsCOMPtr 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.mCount; i++) { if (aAttribute == stringListInfo.mInfos[i].mName) { rv = stringListInfo.mValues[i].SetValue(aValue); if (NS_FAILED(rv)) { stringListInfo.Reset(i); } else { aResult.SetTo(stringListInfo.mValues[i], &aValue); didSetResult = true; } foundMatch = true; break; } } } if (!foundMatch) { // Check for orient attribute if (aAttribute == nsGkAtoms::orient) { SVGAnimatedOrient* orient = GetAnimatedOrient(); if (orient) { rv = orient->SetBaseValueString(aValue, this, false); if (NS_FAILED(rv)) { orient->Init(); } else { aResult.SetTo(*orient, &aValue); didSetResult = true; } foundMatch = true; } // Check for viewBox attribute } else if (aAttribute == nsGkAtoms::viewBox) { SVGAnimatedViewBox* viewBox = GetAnimatedViewBox(); 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 preserveAspectRatio attribute } else if (aAttribute == nsGkAtoms::preserveAspectRatio) { SVGAnimatedPreserveAspectRatio* preserveAspectRatio = GetAnimatedPreserveAspectRatio(); 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 // SVGAnimatedTransformList is/has been allocated: SVGAnimatedTransformList* transformList = GetAnimatedTransformList(DO_ALLOCATE); rv = transformList->SetBaseValueString(aValue, this); 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 (aAttribute == nsGkAtoms::rel) { aResult.ParseAtomArray(aValue); return true; } } if (!foundMatch) { // Check for SVGAnimatedString attribute StringAttributesInfo stringInfo = GetStringInfo(); for (uint32_t i = 0; i < stringInfo.mCount; i++) { if (aNamespaceID == stringInfo.mInfos[i].mNamespaceID && aAttribute == stringInfo.mInfos[i].mName) { stringInfo.mValues[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 SVGElementBase::ParseAttribute(aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult); } void SVGElement::UnsetAttrInternal(int32_t aNamespaceID, nsAtom* 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 declaration block to // force an update if (IsAttributeMapped(aName)) { mContentDeclarationBlock = nullptr; } if (IsEventAttributeName(aName)) { EventListenerManager* manager = GetExistingListenerManager(); if (manager) { nsAtom* eventName = GetEventNameForAttr(aName); manager->RemoveEventHandler(eventName); } return; } // Check if this is a length attribute going away LengthAttributesInfo lenInfo = GetLengthInfo(); for (uint32_t i = 0; i < lenInfo.mCount; i++) { if (aName == lenInfo.mInfos[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.mCount; i++) { if (aName == lengthListInfo.mInfos[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.mCount; i++) { if (aName == numberListInfo.mInfos[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.mCount; i++) { if (aName == numInfo.mInfos[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.mCount; i++) { if (aName == numPairInfo.mInfos[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.mCount; i++) { if (aName == intInfo.mInfos[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.mCount; i++) { if (aName == intPairInfo.mInfos[i].mName) { MaybeSerializeAttrBeforeRemoval(aName, aNotify); intPairInfo.Reset(i); return; } } // Check if this is a boolean attribute going away BooleanAttributesInfo boolInfo = GetBooleanInfo(); for (uint32_t i = 0; i < boolInfo.mCount; i++) { if (aName == boolInfo.mInfos[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.mCount; i++) { if (aName == enumInfo.mInfos[i].mName) { enumInfo.Reset(i); return; } } // Check if this is an orient attribute going away if (aName == nsGkAtoms::orient) { SVGAnimatedOrient* orient = GetAnimatedOrient(); if (orient) { MaybeSerializeAttrBeforeRemoval(aName, aNotify); orient->Init(); return; } } // Check if this is a viewBox attribute going away if (aName == nsGkAtoms::viewBox) { SVGAnimatedViewBox* viewBox = GetAnimatedViewBox(); if (viewBox) { MaybeSerializeAttrBeforeRemoval(aName, aNotify); viewBox->Init(); return; } } // Check if this is a preserveAspectRatio attribute going away if (aName == nsGkAtoms::preserveAspectRatio) { SVGAnimatedPreserveAspectRatio* preserveAspectRatio = GetAnimatedPreserveAspectRatio(); if (preserveAspectRatio) { MaybeSerializeAttrBeforeRemoval(aName, aNotify); preserveAspectRatio->Init(); return; } } // Check if this is a transform list attribute going away if (GetTransformListAttrName() == aName) { SVGAnimatedTransformList* transformList = GetAnimatedTransformList(); if (transformList) { MaybeSerializeAttrBeforeRemoval(aName, aNotify); transformList->ClearBaseValue(); return; } } // Check for conditional processing attributes nsCOMPtr 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.mCount; i++) { if (aName == stringListInfo.mInfos[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.mCount; i++) { if (aNamespaceID == stringInfo.mInfos[i].mNamespaceID && aName == stringInfo.mInfos[i].mName) { stringInfo.Reset(i); return; } } } nsresult SVGElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName, const nsAttrValueOrString* aValue, bool aNotify) { if (!aValue) { UnsetAttrInternal(aNamespaceID, aName, aNotify); } return SVGElementBase::BeforeSetAttr(aNamespaceID, aName, aValue, aNotify); } nsChangeHint SVGElement::GetAttributeChangeHint(const nsAtom* aAttribute, int32_t aModType) const { nsChangeHint retval = SVGElementBase::GetAttributeChangeHint(aAttribute, aModType); nsCOMPtr tests = do_QueryObject(const_cast(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 SVGElement::IsNodeOfType(uint32_t aFlags) const { return false; } void SVGElement::NodeInfoChanged(Document* aOldDoc) { SVGElementBase::NodeInfoChanged(aOldDoc); aOldDoc->UnscheduleSVGForPresAttrEvaluation(this); mContentDeclarationBlock = nullptr; OwnerDoc()->ScheduleSVGForPresAttrEvaluation(this); } NS_IMETHODIMP_(bool) SVGElement::IsAttributeMapped(const nsAtom* name) const { if (name == nsGkAtoms::lang) { return true; } return SVGElementBase::IsAttributeMapped(name); } // PresentationAttributes-FillStroke /* static */ const Element::MappedAttributeEntry SVGElement::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 SVGElement::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::transform_origin}, {nsGkAtoms::visibility}, {nullptr}}; // PresentationAttributes-TextContentElements /* static */ const Element::MappedAttributeEntry SVGElement::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::white_space}, {nsGkAtoms::word_spacing}, {nsGkAtoms::writing_mode}, {nullptr}}; // PresentationAttributes-FontSpecification /* static */ const Element::MappedAttributeEntry SVGElement::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 SVGElement::sGradientStopMap[] = { {nsGkAtoms::stop_color}, {nsGkAtoms::stop_opacity}, {nullptr}}; // PresentationAttributes-Viewports /* static */ const Element::MappedAttributeEntry SVGElement::sViewportsMap[] = { {nsGkAtoms::overflow}, {nsGkAtoms::clip}, {nullptr}}; // PresentationAttributes-Makers /* static */ const Element::MappedAttributeEntry SVGElement::sMarkersMap[] = { {nsGkAtoms::marker_end}, {nsGkAtoms::marker_mid}, {nsGkAtoms::marker_start}, {nullptr}}; // PresentationAttributes-Color /* static */ const Element::MappedAttributeEntry SVGElement::sColorMap[] = { {nsGkAtoms::color}, {nullptr}}; // PresentationAttributes-Filters /* static */ const Element::MappedAttributeEntry SVGElement::sFiltersMap[] = { {nsGkAtoms::colorInterpolationFilters}, {nullptr}}; // PresentationAttributes-feFlood /* static */ const Element::MappedAttributeEntry SVGElement::sFEFloodMap[] = { {nsGkAtoms::flood_color}, {nsGkAtoms::flood_opacity}, {nullptr}}; // PresentationAttributes-LightingEffects /* static */ const Element::MappedAttributeEntry SVGElement::sLightingEffectsMap[] = { {nsGkAtoms::lighting_color}, {nullptr}}; // PresentationAttributes-mask /* static */ const Element::MappedAttributeEntry SVGElement::sMaskMap[] = { {nsGkAtoms::mask_type}, {nullptr}}; //---------------------------------------------------------------------- // Element methods // forwarded to Element implementations //---------------------------------------------------------------------- SVGSVGElement* SVGElement::GetOwnerSVGElement() { nsIContent* ancestor = GetFlattenedTreeParent(); while (ancestor && ancestor->IsSVGElement()) { if (ancestor->IsSVGElement(nsGkAtoms::foreignObject)) { return nullptr; } if (ancestor->IsSVGElement(nsGkAtoms::svg)) { return static_cast(ancestor); } ancestor = ancestor->GetFlattenedTreeParent(); } // we don't have an ancestor element... return nullptr; } SVGElement* SVGElement::GetViewportElement() { return SVGContentUtils::GetNearestViewportElement(this); } already_AddRefed SVGElement::ClassName() { return mClassAttribute.ToDOMAnimatedString(this); } /* static */ bool SVGElement::UpdateDeclarationBlockFromLength( DeclarationBlock& aBlock, nsCSSPropertyID aPropId, const SVGAnimatedLength& aLength, ValToUse aValToUse) { aBlock.AssertMutable(); float value; if (aValToUse == ValToUse::Anim) { value = aLength.GetAnimValInSpecifiedUnits(); } else { MOZ_ASSERT(aValToUse == ValToUse::Base); value = aLength.GetBaseValInSpecifiedUnits(); } // SVG parser doesn't check non-negativity of some parsed value, // we should not pass those to CSS side. if (value < 0 && SVGGeometryProperty::IsNonNegativeGeometryProperty(aPropId)) { return false; } nsCSSUnit cssUnit = SVGGeometryProperty::SpecifiedUnitTypeToCSSUnit( aLength.GetSpecifiedUnitType()); if (cssUnit == eCSSUnit_Percent) { Servo_DeclarationBlock_SetPercentValue(aBlock.Raw(), aPropId, value / 100.f); } else { Servo_DeclarationBlock_SetLengthValue(aBlock.Raw(), aPropId, value, cssUnit); } return true; } /* static */ bool SVGElement::UpdateDeclarationBlockFromPath( DeclarationBlock& aBlock, const SVGAnimatedPathSegList& aPath, ValToUse aValToUse) { aBlock.AssertMutable(); const SVGPathData& pathData = aValToUse == ValToUse::Anim ? aPath.GetAnimValue() : aPath.GetBaseValue(); // SVGPathData::mData is fallible but rust binding accepts nsTArray only, so // we need to point to one or the other. Fortunately, fallible and infallible // array types can be implicitly converted provided they are const. // // FIXME: here we just convert the data structure from cpp verion into rust // version. We don't do any normalization for the path data from d attribute. // Based on the current discussion of https://github.com/w3c/svgwg/issues/321, // we may have to convert the relative commands into absolute commands. // The normalization should be fixed in Bug 1489392. Besides, Bug 1714238 // will use the same data structure, so we may simplify this more. const nsTArray& asInFallibleArray = pathData.RawData(); Servo_DeclarationBlock_SetPathValue(aBlock.Raw(), eCSSProperty_d, &asInFallibleArray); return true; } //------------------------------------------------------------------------ // Helper class: MappedAttrParser, for parsing values of mapped attributes namespace { class MOZ_STACK_CLASS MappedAttrParser { public: explicit MappedAttrParser(SVGElement& aElement) : mElement(aElement) {} ~MappedAttrParser() { MOZ_ASSERT(!mDecl, "If mDecl was initialized, it should have been returned via " "TakeDeclarationBlock (and have its pointer cleared)"); }; // Parses a mapped attribute value. void ParseMappedAttrValue(nsAtom* aMappedAttrName, const nsAString& aMappedAttrValue); void TellStyleAlreadyParsedResult(nsAtom const* aAtom, SVGAnimatedLength const& aLength); void TellStyleAlreadyParsedResult(const SVGAnimatedPathSegList& aPath); // If we've parsed any values for mapped attributes, this method returns the // already_AddRefed css::Declaration that incorporates the parsed // values. Otherwise, this method returns null. already_AddRefed TakeDeclarationBlock() { return mDecl.forget(); } DeclarationBlock& EnsureDeclarationBlock() { if (!mDecl) { mDecl = new DeclarationBlock(); } return *mDecl; } URLExtraData& EnsureExtraData() { if (!mExtraData) { nsCOMPtr referrerInfo = ReferrerInfo::CreateForSVGResources(mElement.OwnerDoc()); mExtraData = MakeRefPtr(mElement.GetBaseURI(), referrerInfo, mElement.NodePrincipal()); } return *mExtraData; } private: // Declaration for storing parsed values (lazily initialized). RefPtr mDecl; // URL data for parsing stuff. Also lazy. RefPtr mExtraData; // For reporting use counters SVGElement& mElement; }; void MappedAttrParser::ParseMappedAttrValue(nsAtom* aMappedAttrName, const nsAString& aMappedAttrValue) { // Get the nsCSSPropertyID ID for our mapped attribute. nsCSSPropertyID propertyID = nsCSSProps::LookupProperty(nsAtomCString(aMappedAttrName)); if (propertyID != eCSSProperty_UNKNOWN) { bool changed = false; // outparam for ParseProperty. NS_ConvertUTF16toUTF8 value(aMappedAttrValue); auto* doc = mElement.OwnerDoc(); changed = Servo_DeclarationBlock_SetPropertyById( EnsureDeclarationBlock().Raw(), propertyID, &value, false, &EnsureExtraData(), ParsingMode::AllowUnitlessLength, doc->GetCompatibilityMode(), doc->CSSLoader(), StyleCssRuleType::Style, {}); // TODO(emilio): If we want to record these from CSSOM more generally, we // can pass the document use counters down the FFI call. For now manually // count them. if (changed && StaticPrefs::layout_css_use_counters_enabled()) { UseCounter useCounter = nsCSSProps::UseCounterFor(propertyID); MOZ_ASSERT(useCounter != eUseCounter_UNKNOWN); doc->SetUseCounter(useCounter); } return; } MOZ_ASSERT(aMappedAttrName == nsGkAtoms::lang, "Only 'lang' should be unrecognized!"); // CSS parser doesn't know about 'lang', so we need to handle it specially. if (aMappedAttrName == nsGkAtoms::lang) { propertyID = eCSSProperty__x_lang; RefPtr atom = NS_Atomize(aMappedAttrValue); Servo_DeclarationBlock_SetIdentStringValue(EnsureDeclarationBlock().Raw(), propertyID, atom); } } void MappedAttrParser::TellStyleAlreadyParsedResult( nsAtom const* aAtom, SVGAnimatedLength const& aLength) { nsCSSPropertyID propertyID = nsCSSProps::LookupProperty(nsAtomCString(aAtom)); SVGElement::UpdateDeclarationBlockFromLength(EnsureDeclarationBlock(), propertyID, aLength, SVGElement::ValToUse::Base); } void MappedAttrParser::TellStyleAlreadyParsedResult( const SVGAnimatedPathSegList& aPath) { SVGElement::UpdateDeclarationBlockFromPath(EnsureDeclarationBlock(), aPath, SVGElement::ValToUse::Base); } } // namespace //---------------------------------------------------------------------- // Implementation Helpers: void SVGElement::UpdateContentDeclarationBlock() { MOZ_ASSERT(!mContentDeclarationBlock, "we already have a content declaration block"); MappedAttrParser mappedAttrParser(*this); bool lengthAffectsStyle = SVGGeometryProperty::ElementMapsLengthsToStyle(this); uint32_t i = 0; while (BorrowedAttrInfo info = GetAttrInfoAt(i++)) { const nsAttrName* attrName = info.mName; if (!attrName->IsAtom() || !IsAttributeMapped(attrName->Atom())) { continue; } // FIXME(emilio): This check is dead, since IsAtom() implies that // NamespaceID() == None. if (attrName->NamespaceID() != kNameSpaceID_None && !attrName->Equals(nsGkAtoms::lang, kNameSpaceID_XML)) { continue; } if (attrName->Equals(nsGkAtoms::lang, kNameSpaceID_None) && HasAttr(kNameSpaceID_XML, nsGkAtoms::lang)) { // xml:lang has precedence, and will get set via Gecko_GetXMLLangValue(). continue; } if (lengthAffectsStyle) { auto const* length = GetAnimatedLength(attrName->Atom()); if (length && length->HasBaseVal()) { // This is an element with geometry property set via SVG attribute, // and the attribute is already successfully parsed. We want to go // through the optimized path to tell the style system the result // directly, rather than let it parse the same thing again. mappedAttrParser.TellStyleAlreadyParsedResult(attrName->Atom(), *length); continue; } } if (attrName->Equals(nsGkAtoms::d, kNameSpaceID_None)) { const auto* path = GetAnimPathSegList(); // Note: Only SVGPathElement has d attribute. MOZ_ASSERT( path, "SVGPathElement should have the non-null SVGAnimatedPathSegList"); // The attribute should have been already successfully parsed. // We want to go through the optimized path to tell the style system // the result directly, rather than let it parse the same thing again. mappedAttrParser.TellStyleAlreadyParsedResult(*path); // Some other notes: // The syntax of CSS d property is different from SVG d attribute. // 1. CSS d proeprty accepts: none | path(); // 2. SVG d attribtue accepts: none | // So we cannot use css parser to parse the SVG d attribute directly. // Besides, |mAttrs.AttrAt(i)| removes the quotes already, so the svg path // in |mAttrs.AttrAt(i)| would be something like `M0,0L1,1z` without the // quotes. So css tokenizer cannot recognize this as a quoted string, and // so svg_path::SVGPathData::parse() doesn't work for this. Fortunately, // we still can rely on the parsed result from // SVGElement::ParseAttribute() for d attribute. continue; } nsAutoString value; info.mValue->ToString(value); mappedAttrParser.ParseMappedAttrValue(attrName->Atom(), value); } mContentDeclarationBlock = mappedAttrParser.TakeDeclarationBlock(); } const DeclarationBlock* SVGElement::GetContentDeclarationBlock() const { return mContentDeclarationBlock; } /** * 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 SVGElement::WillChangeValue( nsAtom* aName, const mozAutoDocUpdate& aProofOfUpdate) { // 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(MutationEvent_Binding::MODIFICATION) : static_cast(MutationEvent_Binding::ADDITION); MutationObservers::NotifyAttributeWillChange(this, kNameSpaceID_None, aName, modType); // 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 SVGAnimatedLength, 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 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 SVGElement::DidChangeValue(nsAtom* aName, const nsAttrValue& aEmptyOrOldValue, nsAttrValue& aNewValue, const mozAutoDocUpdate& aProofOfUpdate) { bool hasListeners = nsContentUtils::HasMutationListeners( this, NS_EVENT_BITS_MUTATION_ATTRMODIFIED, this); uint8_t modType = HasAttr(kNameSpaceID_None, aName) ? static_cast(MutationEvent_Binding::MODIFICATION) : static_cast(MutationEvent_Binding::ADDITION); // 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, nullptr, modType, hasListeners, kNotifyDocumentObservers, kCallAfterSetAttr, GetComposedDoc(), aProofOfUpdate); } void SVGElement::MaybeSerializeAttrBeforeRemoval(nsAtom* aName, bool aNotify) { if (!aNotify || !nsContentUtils::HasMutationListeners( this, NS_EVENT_BITS_MUTATION_ATTRMODIFIED, this)) { return; } const nsAttrValue* attrValue = mAttrs.GetAttr(aName); if (!attrValue) return; nsAutoString serializedValue; attrValue->ToString(serializedValue); nsAttrValue oldAttrValue(serializedValue); bool oldValueSet; mAttrs.SetAndSwapAttr(aName, oldAttrValue, &oldValueSet); } nsAtom* SVGElement::GetEventNameForAttr(nsAtom* aAttr) { if (IsSVGElement(nsGkAtoms::svg)) { if (aAttr == nsGkAtoms::onload) return nsGkAtoms::onSVGLoad; if (aAttr == nsGkAtoms::onscroll) return nsGkAtoms::onSVGScroll; } if (aAttr == nsGkAtoms::onbegin) return nsGkAtoms::onbeginEvent; if (aAttr == nsGkAtoms::onrepeat) return nsGkAtoms::onrepeatEvent; if (aAttr == nsGkAtoms::onend) return nsGkAtoms::onendEvent; return SVGElementBase::GetEventNameForAttr(aAttr); } SVGViewportElement* SVGElement::GetCtx() const { return SVGContentUtils::GetNearestViewportElement(this); } /* virtual */ gfxMatrix SVGElement::PrependLocalTransformsTo(const gfxMatrix& aMatrix, SVGTransformTypes aWhich) const { return aMatrix; } SVGElement::LengthAttributesInfo SVGElement::GetLengthInfo() { return LengthAttributesInfo(nullptr, nullptr, 0); } void SVGElement::SetLength(nsAtom* aName, const SVGAnimatedLength& aLength) { LengthAttributesInfo lengthInfo = GetLengthInfo(); for (uint32_t i = 0; i < lengthInfo.mCount; i++) { if (aName == lengthInfo.mInfos[i].mName) { lengthInfo.mValues[i] = aLength; DidAnimateLength(i); return; } } MOZ_ASSERT(false, "no length found to set"); } nsAttrValue SVGElement::WillChangeLength( uint8_t aAttrEnum, const mozAutoDocUpdate& aProofOfUpdate) { return WillChangeValue(GetLengthInfo().mInfos[aAttrEnum].mName, aProofOfUpdate); } void SVGElement::DidChangeLength(uint8_t aAttrEnum, const nsAttrValue& aEmptyOrOldValue, const mozAutoDocUpdate& aProofOfUpdate) { LengthAttributesInfo info = GetLengthInfo(); NS_ASSERTION(info.mCount > 0, "DidChangeLength on element with no length attribs"); NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); nsAttrValue newValue; newValue.SetTo(info.mValues[aAttrEnum], nullptr); DidChangeValue(info.mInfos[aAttrEnum].mName, aEmptyOrOldValue, newValue, aProofOfUpdate); } void SVGElement::DidAnimateLength(uint8_t aAttrEnum) { // We need to do this here. Normally the SMIL restyle would also cause us to // do this from DidSetComputedStyle, but we don't have that guarantee if our // frame gets reconstructed. ClearAnyCachedPath(); if (SVGGeometryProperty::ElementMapsLengthsToStyle(this)) { nsCSSPropertyID propId = SVGGeometryProperty::AttrEnumToCSSPropId(this, aAttrEnum); // We don't map use element width/height currently. We can remove this // test when we do. if (propId != eCSSProperty_UNKNOWN) { SMILOverrideStyle()->SetSMILValue(propId, GetLengthInfo().mValues[aAttrEnum]); return; } } nsIFrame* frame = GetPrimaryFrame(); if (frame) { LengthAttributesInfo info = GetLengthInfo(); frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, MutationEvent_Binding::SMIL); } } SVGAnimatedLength* SVGElement::GetAnimatedLength(uint8_t aAttrEnum) { LengthAttributesInfo info = GetLengthInfo(); if (aAttrEnum < info.mCount) { return &info.mValues[aAttrEnum]; } MOZ_ASSERT_UNREACHABLE("Bad attrEnum"); return nullptr; } SVGAnimatedLength* SVGElement::GetAnimatedLength(const nsAtom* aAttrName) { LengthAttributesInfo lengthInfo = GetLengthInfo(); for (uint32_t i = 0; i < lengthInfo.mCount; i++) { if (aAttrName == lengthInfo.mInfos[i].mName) { return &lengthInfo.mValues[i]; } } return nullptr; } void SVGElement::GetAnimatedLengthValues(float* aFirst, ...) { LengthAttributesInfo info = GetLengthInfo(); NS_ASSERTION(info.mCount > 0, "GetAnimatedLengthValues on element with no length attribs"); SVGViewportElement* ctx = nullptr; float* f = aFirst; uint32_t i = 0; va_list args; va_start(args, aFirst); while (f && i < info.mCount) { uint8_t type = info.mValues[i].GetSpecifiedUnitType(); if (!ctx) { if (type != SVGLength_Binding::SVG_LENGTHTYPE_NUMBER && type != SVGLength_Binding::SVG_LENGTHTYPE_PX) ctx = GetCtx(); } if (type == SVGLength_Binding::SVG_LENGTHTYPE_EMS || type == SVGLength_Binding::SVG_LENGTHTYPE_EXS) *f = info.mValues[i++].GetAnimValue(this); else *f = info.mValues[i++].GetAnimValue(ctx); f = va_arg(args, float*); } va_end(args); } SVGElement::LengthListAttributesInfo SVGElement::GetLengthListInfo() { return LengthListAttributesInfo(nullptr, nullptr, 0); } nsAttrValue SVGElement::WillChangeLengthList( uint8_t aAttrEnum, const mozAutoDocUpdate& aProofOfUpdate) { return WillChangeValue(GetLengthListInfo().mInfos[aAttrEnum].mName, aProofOfUpdate); } void SVGElement::DidChangeLengthList(uint8_t aAttrEnum, const nsAttrValue& aEmptyOrOldValue, const mozAutoDocUpdate& aProofOfUpdate) { LengthListAttributesInfo info = GetLengthListInfo(); NS_ASSERTION(info.mCount > 0, "DidChangeLengthList on element with no length list attribs"); NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); nsAttrValue newValue; newValue.SetTo(info.mValues[aAttrEnum].GetBaseValue(), nullptr); DidChangeValue(info.mInfos[aAttrEnum].mName, aEmptyOrOldValue, newValue, aProofOfUpdate); } void SVGElement::DidAnimateLengthList(uint8_t aAttrEnum) { nsIFrame* frame = GetPrimaryFrame(); if (frame) { LengthListAttributesInfo info = GetLengthListInfo(); frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, MutationEvent_Binding::SMIL); } } void SVGElement::GetAnimatedLengthListValues(SVGUserUnitList* aFirst, ...) { LengthListAttributesInfo info = GetLengthListInfo(); NS_ASSERTION( info.mCount > 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.mCount) { list->Init(&(info.mValues[i].GetAnimValue()), this, info.mInfos[i].mAxis); ++i; list = va_arg(args, SVGUserUnitList*); } va_end(args); } SVGAnimatedLengthList* SVGElement::GetAnimatedLengthList(uint8_t aAttrEnum) { LengthListAttributesInfo info = GetLengthListInfo(); if (aAttrEnum < info.mCount) { return &(info.mValues[aAttrEnum]); } MOZ_ASSERT_UNREACHABLE("Bad attrEnum"); return nullptr; } SVGElement::NumberListAttributesInfo SVGElement::GetNumberListInfo() { return NumberListAttributesInfo(nullptr, nullptr, 0); } nsAttrValue SVGElement::WillChangeNumberList( uint8_t aAttrEnum, const mozAutoDocUpdate& aProofOfUpdate) { return WillChangeValue(GetNumberListInfo().mInfos[aAttrEnum].mName, aProofOfUpdate); } void SVGElement::DidChangeNumberList(uint8_t aAttrEnum, const nsAttrValue& aEmptyOrOldValue, const mozAutoDocUpdate& aProofOfUpdate) { NumberListAttributesInfo info = GetNumberListInfo(); MOZ_ASSERT(info.mCount > 0, "DidChangeNumberList on element with no number list attribs"); MOZ_ASSERT(aAttrEnum < info.mCount, "aAttrEnum out of range"); nsAttrValue newValue; newValue.SetTo(info.mValues[aAttrEnum].GetBaseValue(), nullptr); DidChangeValue(info.mInfos[aAttrEnum].mName, aEmptyOrOldValue, newValue, aProofOfUpdate); } void SVGElement::DidAnimateNumberList(uint8_t aAttrEnum) { nsIFrame* frame = GetPrimaryFrame(); if (frame) { NumberListAttributesInfo info = GetNumberListInfo(); MOZ_ASSERT(aAttrEnum < info.mCount, "aAttrEnum out of range"); frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, MutationEvent_Binding::SMIL); } } SVGAnimatedNumberList* SVGElement::GetAnimatedNumberList(uint8_t aAttrEnum) { NumberListAttributesInfo info = GetNumberListInfo(); if (aAttrEnum < info.mCount) { return &(info.mValues[aAttrEnum]); } MOZ_ASSERT(false, "Bad attrEnum"); return nullptr; } SVGAnimatedNumberList* SVGElement::GetAnimatedNumberList(nsAtom* aAttrName) { NumberListAttributesInfo info = GetNumberListInfo(); for (uint32_t i = 0; i < info.mCount; i++) { if (aAttrName == info.mInfos[i].mName) { return &info.mValues[i]; } } MOZ_ASSERT(false, "Bad caller"); return nullptr; } nsAttrValue SVGElement::WillChangePointList( const mozAutoDocUpdate& aProofOfUpdate) { MOZ_ASSERT(GetPointListAttrName(), "Changing non-existent point list?"); return WillChangeValue(GetPointListAttrName(), aProofOfUpdate); } void SVGElement::DidChangePointList(const nsAttrValue& aEmptyOrOldValue, const mozAutoDocUpdate& aProofOfUpdate) { MOZ_ASSERT(GetPointListAttrName(), "Changing non-existent point list?"); nsAttrValue newValue; newValue.SetTo(GetAnimatedPointList()->GetBaseValue(), nullptr); DidChangeValue(GetPointListAttrName(), aEmptyOrOldValue, newValue, aProofOfUpdate); } void SVGElement::DidAnimatePointList() { MOZ_ASSERT(GetPointListAttrName(), "Animating non-existent path data?"); ClearAnyCachedPath(); nsIFrame* frame = GetPrimaryFrame(); if (frame) { frame->AttributeChanged(kNameSpaceID_None, GetPointListAttrName(), MutationEvent_Binding::SMIL); } } nsAttrValue SVGElement::WillChangePathSegList( const mozAutoDocUpdate& aProofOfUpdate) { MOZ_ASSERT(GetPathDataAttrName(), "Changing non-existent path seg list?"); return WillChangeValue(GetPathDataAttrName(), aProofOfUpdate); } void SVGElement::DidChangePathSegList(const nsAttrValue& aEmptyOrOldValue, const mozAutoDocUpdate& aProofOfUpdate) { MOZ_ASSERT(GetPathDataAttrName(), "Changing non-existent path seg list?"); nsAttrValue newValue; newValue.SetTo(GetAnimPathSegList()->GetBaseValue(), nullptr); DidChangeValue(GetPathDataAttrName(), aEmptyOrOldValue, newValue, aProofOfUpdate); } void SVGElement::DidAnimatePathSegList() { nsStaticAtom* name = GetPathDataAttrName(); MOZ_ASSERT(name, "Animating non-existent path data?"); ClearAnyCachedPath(); // Notify style we have to update the d property because of SMIL animation. if (StaticPrefs::layout_css_d_property_enabled() && name == nsGkAtoms::d) { SMILOverrideStyle()->SetSMILValue(nsCSSPropertyID::eCSSProperty_d, *GetAnimPathSegList()); return; } if (nsIFrame* frame = GetPrimaryFrame()) { frame->AttributeChanged(kNameSpaceID_None, name, MutationEvent_Binding::SMIL); } } SVGElement::NumberAttributesInfo SVGElement::GetNumberInfo() { return NumberAttributesInfo(nullptr, nullptr, 0); } void SVGElement::DidChangeNumber(uint8_t aAttrEnum) { NumberAttributesInfo info = GetNumberInfo(); NS_ASSERTION(info.mCount > 0, "DidChangeNumber on element with no number attribs"); NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); nsAttrValue attrValue; attrValue.SetTo(info.mValues[aAttrEnum].GetBaseValue(), nullptr); SetParsedAttr(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, nullptr, attrValue, true); } void SVGElement::DidAnimateNumber(uint8_t aAttrEnum) { nsIFrame* frame = GetPrimaryFrame(); if (frame) { NumberAttributesInfo info = GetNumberInfo(); frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, MutationEvent_Binding::SMIL); } } void SVGElement::GetAnimatedNumberValues(float* aFirst, ...) { NumberAttributesInfo info = GetNumberInfo(); NS_ASSERTION(info.mCount > 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.mCount) { *f = info.mValues[i++].GetAnimValue(); f = va_arg(args, float*); } va_end(args); } SVGElement::NumberPairAttributesInfo SVGElement::GetNumberPairInfo() { return NumberPairAttributesInfo(nullptr, nullptr, 0); } nsAttrValue SVGElement::WillChangeNumberPair(uint8_t aAttrEnum) { mozAutoDocUpdate updateBatch(GetComposedDoc(), kDontNotifyDocumentObservers); return WillChangeValue(GetNumberPairInfo().mInfos[aAttrEnum].mName, updateBatch); } void SVGElement::DidChangeNumberPair(uint8_t aAttrEnum, const nsAttrValue& aEmptyOrOldValue) { NumberPairAttributesInfo info = GetNumberPairInfo(); NS_ASSERTION(info.mCount > 0, "DidChangePairNumber on element with no number pair attribs"); NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); nsAttrValue newValue; newValue.SetTo(info.mValues[aAttrEnum], nullptr); mozAutoDocUpdate updateBatch(GetComposedDoc(), kNotifyDocumentObservers); DidChangeValue(info.mInfos[aAttrEnum].mName, aEmptyOrOldValue, newValue, updateBatch); } void SVGElement::DidAnimateNumberPair(uint8_t aAttrEnum) { nsIFrame* frame = GetPrimaryFrame(); if (frame) { NumberPairAttributesInfo info = GetNumberPairInfo(); frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, MutationEvent_Binding::SMIL); } } SVGElement::IntegerAttributesInfo SVGElement::GetIntegerInfo() { return IntegerAttributesInfo(nullptr, nullptr, 0); } void SVGElement::DidChangeInteger(uint8_t aAttrEnum) { IntegerAttributesInfo info = GetIntegerInfo(); NS_ASSERTION(info.mCount > 0, "DidChangeInteger on element with no integer attribs"); NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); nsAttrValue attrValue; attrValue.SetTo(info.mValues[aAttrEnum].GetBaseValue(), nullptr); SetParsedAttr(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, nullptr, attrValue, true); } void SVGElement::DidAnimateInteger(uint8_t aAttrEnum) { nsIFrame* frame = GetPrimaryFrame(); if (frame) { IntegerAttributesInfo info = GetIntegerInfo(); frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, MutationEvent_Binding::SMIL); } } void SVGElement::GetAnimatedIntegerValues(int32_t* aFirst, ...) { IntegerAttributesInfo info = GetIntegerInfo(); NS_ASSERTION(info.mCount > 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.mCount) { *n = info.mValues[i++].GetAnimValue(); n = va_arg(args, int32_t*); } va_end(args); } SVGElement::IntegerPairAttributesInfo SVGElement::GetIntegerPairInfo() { return IntegerPairAttributesInfo(nullptr, nullptr, 0); } nsAttrValue SVGElement::WillChangeIntegerPair( uint8_t aAttrEnum, const mozAutoDocUpdate& aProofOfUpdate) { return WillChangeValue(GetIntegerPairInfo().mInfos[aAttrEnum].mName, aProofOfUpdate); } void SVGElement::DidChangeIntegerPair(uint8_t aAttrEnum, const nsAttrValue& aEmptyOrOldValue, const mozAutoDocUpdate& aProofOfUpdate) { IntegerPairAttributesInfo info = GetIntegerPairInfo(); NS_ASSERTION(info.mCount > 0, "DidChangeIntegerPair on element with no integer pair attribs"); NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); nsAttrValue newValue; newValue.SetTo(info.mValues[aAttrEnum], nullptr); DidChangeValue(info.mInfos[aAttrEnum].mName, aEmptyOrOldValue, newValue, aProofOfUpdate); } void SVGElement::DidAnimateIntegerPair(uint8_t aAttrEnum) { nsIFrame* frame = GetPrimaryFrame(); if (frame) { IntegerPairAttributesInfo info = GetIntegerPairInfo(); frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, MutationEvent_Binding::SMIL); } } SVGElement::BooleanAttributesInfo SVGElement::GetBooleanInfo() { return BooleanAttributesInfo(nullptr, nullptr, 0); } void SVGElement::DidChangeBoolean(uint8_t aAttrEnum) { BooleanAttributesInfo info = GetBooleanInfo(); NS_ASSERTION(info.mCount > 0, "DidChangeBoolean on element with no boolean attribs"); NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); nsAttrValue attrValue(info.mValues[aAttrEnum].GetBaseValueAtom()); SetParsedAttr(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, nullptr, attrValue, true); } void SVGElement::DidAnimateBoolean(uint8_t aAttrEnum) { nsIFrame* frame = GetPrimaryFrame(); if (frame) { BooleanAttributesInfo info = GetBooleanInfo(); frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, MutationEvent_Binding::SMIL); } } SVGElement::EnumAttributesInfo SVGElement::GetEnumInfo() { return EnumAttributesInfo(nullptr, nullptr, 0); } void SVGElement::DidChangeEnum(uint8_t aAttrEnum) { EnumAttributesInfo info = GetEnumInfo(); NS_ASSERTION(info.mCount > 0, "DidChangeEnum on element with no enum attribs"); NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); nsAttrValue attrValue(info.mValues[aAttrEnum].GetBaseValueAtom(this)); SetParsedAttr(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, nullptr, attrValue, true); } void SVGElement::DidAnimateEnum(uint8_t aAttrEnum) { nsIFrame* frame = GetPrimaryFrame(); if (frame) { EnumAttributesInfo info = GetEnumInfo(); frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, MutationEvent_Binding::SMIL); } } SVGAnimatedOrient* SVGElement::GetAnimatedOrient() { return nullptr; } nsAttrValue SVGElement::WillChangeOrient( const mozAutoDocUpdate& aProofOfUpdate) { return WillChangeValue(nsGkAtoms::orient, aProofOfUpdate); } void SVGElement::DidChangeOrient(const nsAttrValue& aEmptyOrOldValue, const mozAutoDocUpdate& aProofOfUpdate) { SVGAnimatedOrient* orient = GetAnimatedOrient(); NS_ASSERTION(orient, "DidChangeOrient on element with no orient attrib"); nsAttrValue newValue; newValue.SetTo(*orient, nullptr); DidChangeValue(nsGkAtoms::orient, aEmptyOrOldValue, newValue, aProofOfUpdate); } void SVGElement::DidAnimateOrient() { nsIFrame* frame = GetPrimaryFrame(); if (frame) { frame->AttributeChanged(kNameSpaceID_None, nsGkAtoms::orient, MutationEvent_Binding::SMIL); } } SVGAnimatedViewBox* SVGElement::GetAnimatedViewBox() { return nullptr; } nsAttrValue SVGElement::WillChangeViewBox( const mozAutoDocUpdate& aProofOfUpdate) { return WillChangeValue(nsGkAtoms::viewBox, aProofOfUpdate); } void SVGElement::DidChangeViewBox(const nsAttrValue& aEmptyOrOldValue, const mozAutoDocUpdate& aProofOfUpdate) { SVGAnimatedViewBox* viewBox = GetAnimatedViewBox(); NS_ASSERTION(viewBox, "DidChangeViewBox on element with no viewBox attrib"); nsAttrValue newValue; newValue.SetTo(*viewBox, nullptr); DidChangeValue(nsGkAtoms::viewBox, aEmptyOrOldValue, newValue, aProofOfUpdate); } void SVGElement::DidAnimateViewBox() { nsIFrame* frame = GetPrimaryFrame(); if (frame) { frame->AttributeChanged(kNameSpaceID_None, nsGkAtoms::viewBox, MutationEvent_Binding::SMIL); } } SVGAnimatedPreserveAspectRatio* SVGElement::GetAnimatedPreserveAspectRatio() { return nullptr; } nsAttrValue SVGElement::WillChangePreserveAspectRatio( const mozAutoDocUpdate& aProofOfUpdate) { return WillChangeValue(nsGkAtoms::preserveAspectRatio, aProofOfUpdate); } void SVGElement::DidChangePreserveAspectRatio( const nsAttrValue& aEmptyOrOldValue, const mozAutoDocUpdate& aProofOfUpdate) { SVGAnimatedPreserveAspectRatio* preserveAspectRatio = GetAnimatedPreserveAspectRatio(); NS_ASSERTION(preserveAspectRatio, "DidChangePreserveAspectRatio on element with no " "preserveAspectRatio attrib"); nsAttrValue newValue; newValue.SetTo(*preserveAspectRatio, nullptr); DidChangeValue(nsGkAtoms::preserveAspectRatio, aEmptyOrOldValue, newValue, aProofOfUpdate); } void SVGElement::DidAnimatePreserveAspectRatio() { nsIFrame* frame = GetPrimaryFrame(); if (frame) { frame->AttributeChanged(kNameSpaceID_None, nsGkAtoms::preserveAspectRatio, MutationEvent_Binding::SMIL); } } nsAttrValue SVGElement::WillChangeTransformList( const mozAutoDocUpdate& aProofOfUpdate) { return WillChangeValue(GetTransformListAttrName(), aProofOfUpdate); } void SVGElement::DidChangeTransformList( const nsAttrValue& aEmptyOrOldValue, const mozAutoDocUpdate& aProofOfUpdate) { 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, aProofOfUpdate); } void SVGElement::DidAnimateTransformList(int32_t aModType) { MOZ_ASSERT(GetTransformListAttrName(), "Animating non-existent transform data?"); nsIFrame* frame = GetPrimaryFrame(); if (frame) { nsAtom* transformAttr = GetTransformListAttrName(); frame->AttributeChanged(kNameSpaceID_None, transformAttr, aModType); // When script changes the 'transform' attribute, Element::SetAttrAndNotify // will call MutationObservers::NotifyAttributeChanged, 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 // MutationObservers::NotifyAttributeChanged dispatches would be // inappropriate anyway), so we need to post the change event ourself. nsChangeHint changeHint = GetAttributeChangeHint(transformAttr, aModType); if (changeHint) { nsLayoutUtils::PostRestyleEvent(this, RestyleHint{0}, changeHint); } } } SVGElement::StringAttributesInfo SVGElement::GetStringInfo() { return StringAttributesInfo(nullptr, nullptr, 0); } void SVGElement::GetStringBaseValue(uint8_t aAttrEnum, nsAString& aResult) const { SVGElement::StringAttributesInfo info = const_cast(this)->GetStringInfo(); NS_ASSERTION(info.mCount > 0, "GetBaseValue on element with no string attribs"); NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); GetAttr(info.mInfos[aAttrEnum].mNamespaceID, info.mInfos[aAttrEnum].mName, aResult); } void SVGElement::SetStringBaseValue(uint8_t aAttrEnum, const nsAString& aValue) { SVGElement::StringAttributesInfo info = GetStringInfo(); NS_ASSERTION(info.mCount > 0, "SetBaseValue on element with no string attribs"); NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); SetAttr(info.mInfos[aAttrEnum].mNamespaceID, info.mInfos[aAttrEnum].mName, aValue, true); } void SVGElement::DidAnimateString(uint8_t aAttrEnum) { nsIFrame* frame = GetPrimaryFrame(); if (frame) { StringAttributesInfo info = GetStringInfo(); frame->AttributeChanged(info.mInfos[aAttrEnum].mNamespaceID, info.mInfos[aAttrEnum].mName, MutationEvent_Binding::SMIL); } } SVGElement::StringListAttributesInfo SVGElement::GetStringListInfo() { return StringListAttributesInfo(nullptr, nullptr, 0); } nsAttrValue SVGElement::WillChangeStringList( bool aIsConditionalProcessingAttribute, uint8_t aAttrEnum, const mozAutoDocUpdate& aProofOfUpdate) { nsStaticAtom* name; if (aIsConditionalProcessingAttribute) { nsCOMPtr tests(do_QueryInterface(this)); name = tests->GetAttrName(aAttrEnum); } else { name = GetStringListInfo().mInfos[aAttrEnum].mName; } return WillChangeValue(name, aProofOfUpdate); } void SVGElement::DidChangeStringList(bool aIsConditionalProcessingAttribute, uint8_t aAttrEnum, const nsAttrValue& aEmptyOrOldValue, const mozAutoDocUpdate& aProofOfUpdate) { nsStaticAtom* name; nsAttrValue newValue; nsCOMPtr tests; if (aIsConditionalProcessingAttribute) { tests = do_QueryObject(this); name = tests->GetAttrName(aAttrEnum); tests->GetAttrValue(aAttrEnum, newValue); } else { StringListAttributesInfo info = GetStringListInfo(); NS_ASSERTION(info.mCount > 0, "DidChangeStringList on element with no string list attribs"); NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); name = info.mInfos[aAttrEnum].mName; newValue.SetTo(info.mValues[aAttrEnum], nullptr); } DidChangeValue(name, aEmptyOrOldValue, newValue, aProofOfUpdate); if (aIsConditionalProcessingAttribute) { tests->MaybeInvalidate(); } } nsresult SVGElement::ReportAttributeParseFailure(Document* aDocument, nsAtom* aAttribute, const nsAString& aValue) { AutoTArray strings; strings.AppendElement(nsDependentAtomString(aAttribute)); strings.AppendElement(aValue); return SVGContentUtils::ReportToConsole(aDocument, "AttributeParseWarning", strings); } UniquePtr SVGElement::GetAnimatedAttr(int32_t aNamespaceID, nsAtom* aName) { if (aNamespaceID == kNameSpaceID_None) { // 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 MakeUnique(this); } // Lengths: LengthAttributesInfo info = GetLengthInfo(); for (uint32_t i = 0; i < info.mCount; i++) { if (aName == info.mInfos[i].mName) { return info.mValues[i].ToSMILAttr(this); } } // Numbers: { NumberAttributesInfo info = GetNumberInfo(); for (uint32_t i = 0; i < info.mCount; i++) { if (aName == info.mInfos[i].mName) { return info.mValues[i].ToSMILAttr(this); } } } // Number Pairs: { NumberPairAttributesInfo info = GetNumberPairInfo(); for (uint32_t i = 0; i < info.mCount; i++) { if (aName == info.mInfos[i].mName) { return info.mValues[i].ToSMILAttr(this); } } } // Integers: { IntegerAttributesInfo info = GetIntegerInfo(); for (uint32_t i = 0; i < info.mCount; i++) { if (aName == info.mInfos[i].mName) { return info.mValues[i].ToSMILAttr(this); } } } // Integer Pairs: { IntegerPairAttributesInfo info = GetIntegerPairInfo(); for (uint32_t i = 0; i < info.mCount; i++) { if (aName == info.mInfos[i].mName) { return info.mValues[i].ToSMILAttr(this); } } } // Enumerations: { EnumAttributesInfo info = GetEnumInfo(); for (uint32_t i = 0; i < info.mCount; i++) { if (aName == info.mInfos[i].mName) { return info.mValues[i].ToSMILAttr(this); } } } // Booleans: { BooleanAttributesInfo info = GetBooleanInfo(); for (uint32_t i = 0; i < info.mCount; i++) { if (aName == info.mInfos[i].mName) { return info.mValues[i].ToSMILAttr(this); } } } // orient: if (aName == nsGkAtoms::orient) { SVGAnimatedOrient* orient = GetAnimatedOrient(); return orient ? orient->ToSMILAttr(this) : nullptr; } // viewBox: if (aName == nsGkAtoms::viewBox) { SVGAnimatedViewBox* viewBox = GetAnimatedViewBox(); return viewBox ? viewBox->ToSMILAttr(this) : nullptr; } // preserveAspectRatio: if (aName == nsGkAtoms::preserveAspectRatio) { SVGAnimatedPreserveAspectRatio* preserveAspectRatio = GetAnimatedPreserveAspectRatio(); return preserveAspectRatio ? preserveAspectRatio->ToSMILAttr(this) : nullptr; } // NumberLists: { NumberListAttributesInfo info = GetNumberListInfo(); for (uint32_t i = 0; i < info.mCount; i++) { if (aName == info.mInfos[i].mName) { MOZ_ASSERT(i <= UCHAR_MAX, "Too many attributes"); return info.mValues[i].ToSMILAttr(this, uint8_t(i)); } } } // LengthLists: { LengthListAttributesInfo info = GetLengthListInfo(); for (uint32_t i = 0; i < info.mCount; i++) { if (aName == info.mInfos[i].mName) { MOZ_ASSERT(i <= UCHAR_MAX, "Too many attributes"); return info.mValues[i].ToSMILAttr(this, uint8_t(i), info.mInfos[i].mAxis, info.mInfos[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.mCount; i++) { if (aNamespaceID == info.mInfos[i].mNamespaceID && aName == info.mInfos[i].mName) { return info.mValues[i].ToSMILAttr(this); } } } return nullptr; } void SVGElement::AnimationNeedsResample() { Document* doc = GetComposedDoc(); if (doc && doc->HasAnimationController()) { doc->GetAnimationController()->SetResampleNeeded(); } } void SVGElement::FlushAnimations() { Document* doc = GetComposedDoc(); if (doc && doc->HasAnimationController()) { doc->GetAnimationController()->FlushResampleRequests(); } } void SVGElement::AddSizeOfExcludingThis(nsWindowSizes& aSizes, size_t* aNodeSize) const { Element::AddSizeOfExcludingThis(aSizes, aNodeSize); // These are owned by the element and not referenced from the stylesheets. // They're referenced from the rule tree, but the rule nodes don't measure // their style source (since they're non-owning), so unconditionally reporting // them even though it's a refcounted object is ok. if (mContentDeclarationBlock) { aSizes.mLayoutSvgMappedDeclarations += mContentDeclarationBlock->SizeofIncludingThis( aSizes.mState.mMallocSizeOf); } } } // namespace mozilla::dom