Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#include "mozilla/dom/SVGElement.h"
#include "mozilla/AlreadyAddRefed.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/SVGObserverUtils.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 <stdarg.h>
// This is needed to ensure correct handling of calls to the
// vararg-list methods in this file:
// SVGElement::GetAnimated{Length,Number,Integer}Values
static_assert(sizeof(void*) == sizeof(nullptr),
"nullptr should be the correct size");
nsresult NS_NewSVGElement(
mozilla::dom::Element** aResult,
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) {
RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo);
auto* nim = nodeInfo->NodeInfoManager();
RefPtr<mozilla::dom::SVGElement> 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<mozilla::dom::NodeInfo>&& aNodeInfo)
: SVGElementBase(std::move(aNodeInfo)) {}
SVGElement::~SVGElement() = default;
JSObject* SVGElement::WrapNode(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return SVGElement_Binding::Wrap(aCx, this, aGivenProto);
}
template <typename Value, typename Info>
void SVGElement::AttributesInfo<Value, Info>::ResetAll() {
for (uint32_t i = 0; i < mCount; ++i) {
Reset(i);
}
}
template <typename Value, typename Info>
void SVGElement::AttributesInfo<Value, Info>::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<SVGElement*>(aDest);
// cloning a node must retain its internal nonce slot
if (auto* nonce = static_cast<nsString*>(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() ||
aDest->OwnerDoc()->CloningForSVGUse()) {
LengthAttributesInfo lengthInfo = GetLengthInfo();
dest->GetLengthInfo().CopyAllFrom(lengthInfo);
if (SVGGeometryProperty::ElementMapsLengthsToStyle(this)) {
for (uint32_t i = 0; i < lengthInfo.mCount; i++) {
nsCSSPropertyID propId =
SVGGeometryProperty::AttrEnumToCSSPropId(this, i);
// We don't map use element width/height currently. We can remove this
// test when we do.
if (propId != eCSSProperty_UNKNOWN &&
lengthInfo.mValues[i].IsAnimated()) {
dest->SMILOverrideStyle()->SetSMILValue(propId,
lengthInfo.mValues[i]);
}
}
}
dest->GetNumberInfo().CopyAllFrom(GetNumberInfo());
dest->GetNumberPairInfo().CopyAllFrom(GetNumberPairInfo());
dest->GetIntegerInfo().CopyAllFrom(GetIntegerInfo());
dest->GetIntegerPairInfo().CopyAllFrom(GetIntegerPairInfo());
dest->GetBooleanInfo().CopyAllFrom(GetBooleanInfo());
if (const auto* orient = GetAnimatedOrient()) {
*dest->GetAnimatedOrient() = *orient;
}
if (const auto* viewBox = GetAnimatedViewBox()) {
*dest->GetAnimatedViewBox() = *viewBox;
}
if (const auto* preserveAspectRatio = GetAnimatedPreserveAspectRatio()) {
*dest->GetAnimatedPreserveAspectRatio() = *preserveAspectRatio;
}
dest->GetEnumInfo().CopyAllFrom(GetEnumInfo());
dest->GetStringInfo().CopyAllFrom(GetStringInfo());
dest->GetLengthListInfo().CopyAllFrom(GetLengthListInfo());
dest->GetNumberListInfo().CopyAllFrom(GetNumberListInfo());
if (const auto* pointList = GetAnimatedPointList()) {
*dest->GetAnimatedPointList() = *pointList;
}
if (const auto* pathSegList = GetAnimPathSegList()) {
*dest->GetAnimPathSegList() = *pathSegList;
if (pathSegList->IsAnimating()) {
dest->SMILOverrideStyle()->SetSMILValue(eCSSProperty_d, *pathSegList);
}
}
if (const auto* transformList = GetAnimatedTransformList()) {
*dest->GetAnimatedTransformList(DO_ALLOCATE) = *transformList;
}
if (const auto* animateMotionTransform = GetAnimateMotionTransform()) {
dest->SetAnimateMotionTransform(animateMotionTransform);
}
if (const auto* smilOverrideStyleDecoration =
GetSMILOverrideStyleDeclaration()) {
RefPtr<DeclarationBlock> declClone = smilOverrideStyleDecoration->Clone();
declClone->SetDirty();
dest->SetSMILOverrideStyleDeclaration(*declClone);
}
}
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<nsAttrValue>();
}
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);
}
DidAnimateAttribute(kNameSpaceID_None, nsGkAtoms::_class);
}
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<SVGElement>(this)]() {
nsAutoString nonce;
self->GetNonce(nonce);
self->SetAttr(kNameSpaceID_None, nsGkAtoms::nonce, u""_ns, true);
self->SetNonce(nonce);
}));
}
return NS_OK;
}
void SVGElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
const nsAttrValue* aValue,
const nsAttrValue* aOldValue,
nsIPrincipal* aSubjectPrincipal, bool aNotify) {
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<nsAtom> 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<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.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 (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<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.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;
}
}
}
void SVGElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
const nsAttrValue* 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<SVGTests> tests = do_QueryObject(const_cast<SVGElement*>(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;
}
void SVGElement::NodeInfoChanged(Document* aOldDoc) {
SVGElementBase::NodeInfoChanged(aOldDoc);
}
NS_IMETHODIMP_(bool)
SVGElement::IsAttributeMapped(const nsAtom* name) const {
if (name == nsGkAtoms::lang) {
return true;
}
if (IsSVGAnimationElement()) {
return SVGElementBase::IsAttributeMapped(name);
}
static const MappedAttributeEntry attributes[] = {
// Properties that we don't support are commented out.
// { nsGkAtoms::alignment_baseline },
// { nsGkAtoms::baseline_shift },
{nsGkAtoms::clip},
{nsGkAtoms::clip_path},
{nsGkAtoms::clip_rule},
{nsGkAtoms::color},
{nsGkAtoms::colorInterpolation},
{nsGkAtoms::colorInterpolationFilters},
{nsGkAtoms::cursor},
{nsGkAtoms::direction},
{nsGkAtoms::display},
{nsGkAtoms::dominant_baseline},
{nsGkAtoms::fill},
{nsGkAtoms::fill_opacity},
{nsGkAtoms::fill_rule},
{nsGkAtoms::filter},
{nsGkAtoms::flood_color},
{nsGkAtoms::flood_opacity},
{nsGkAtoms::font_family},
{nsGkAtoms::font_size},
{nsGkAtoms::font_size_adjust},
{nsGkAtoms::font_stretch},
{nsGkAtoms::font_style},
{nsGkAtoms::font_variant},
{nsGkAtoms::fontWeight},
{nsGkAtoms::image_rendering},
{nsGkAtoms::letter_spacing},
{nsGkAtoms::lighting_color},
{nsGkAtoms::marker_end},
{nsGkAtoms::marker_mid},
{nsGkAtoms::marker_start},
{nsGkAtoms::mask},
{nsGkAtoms::mask_type},
{nsGkAtoms::opacity},
{nsGkAtoms::overflow},
{nsGkAtoms::paint_order},
{nsGkAtoms::pointer_events},
{nsGkAtoms::shape_rendering},
{nsGkAtoms::stop_color},
{nsGkAtoms::stop_opacity},
{nsGkAtoms::stroke},
{nsGkAtoms::stroke_dasharray},
{nsGkAtoms::stroke_dashoffset},
{nsGkAtoms::stroke_linecap},
{nsGkAtoms::stroke_linejoin},
{nsGkAtoms::stroke_miterlimit},
{nsGkAtoms::stroke_opacity},
{nsGkAtoms::stroke_width},
{nsGkAtoms::text_anchor},
{nsGkAtoms::text_decoration},
{nsGkAtoms::text_rendering},
{nsGkAtoms::transform_origin},
{nsGkAtoms::unicode_bidi},
{nsGkAtoms::vector_effect},
{nsGkAtoms::visibility},
{nsGkAtoms::white_space},
{nsGkAtoms::word_spacing},
{nsGkAtoms::writing_mode},
{nullptr}};
static const MappedAttributeEntry* const map[] = {attributes};
return FindAttributeDependence(name, map) ||
SVGElementBase::IsAttributeMapped(name);
}
//----------------------------------------------------------------------
// Element methods
// forwarded to Element implementations
//----------------------------------------------------------------------
SVGSVGElement* SVGElement::GetOwnerSVGElement() {
nsIContent* ancestor = GetFlattenedTreeParent();
while (ancestor && ancestor->IsSVGElement()) {
if (ancestor->IsSVGElement(nsGkAtoms::foreignObject)) {
return nullptr;
}
if (auto* svg = SVGSVGElement::FromNode(ancestor)) {
return svg;
}
ancestor = ancestor->GetFlattenedTreeParent();
}
// we don't have an ancestor <svg> element...
return nullptr;
}
SVGElement* SVGElement::GetViewportElement() {
return SVGContentUtils::GetNearestViewportElement(this);
}
already_AddRefed<DOMSVGAnimatedString> SVGElement::ClassName() {
return mClassAttribute.ToDOMAnimatedString(this);
}
/* static */
bool SVGElement::UpdateDeclarationBlockFromLength(
StyleLockedDeclarationBlock& aBlock, nsCSSPropertyID aPropId,
const SVGAnimatedLength& aLength, ValToUse aValToUse) {
float value;
uint8_t units;
if (aValToUse == ValToUse::Anim) {
value = aLength.GetAnimValInSpecifiedUnits();
units = aLength.GetAnimUnitType();
} else {
MOZ_ASSERT(aValToUse == ValToUse::Base);
value = aLength.GetBaseValInSpecifiedUnits();
units = aLength.GetBaseUnitType();
}
// 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 = SVGLength::SpecifiedUnitTypeToCSSUnit(units);
if (cssUnit == eCSSUnit_Percent) {
Servo_DeclarationBlock_SetPercentValue(&aBlock, aPropId, value / 100.f);
} else {
Servo_DeclarationBlock_SetLengthValue(&aBlock, aPropId, value, cssUnit);
}
return true;
}
/* static */
bool SVGElement::UpdateDeclarationBlockFromPath(
StyleLockedDeclarationBlock& aBlock, const SVGAnimatedPathSegList& aPath,
ValToUse aValToUse) {
const SVGPathData& pathData =
aValToUse == ValToUse::Anim ? aPath.GetAnimValue() : aPath.GetBaseValue();
// we may have to convert the relative commands into absolute commands.
Servo_DeclarationBlock_SetPathValue(&aBlock, eCSSProperty_d,
&pathData.RawData());
return true;
}
template <typename Float>
static StyleTransformOperation MatrixToTransformOperation(
const gfx::BaseMatrix<Float>& aMatrix) {
return StyleTransformOperation::Matrix(StyleGenericMatrix<float>{
.a = float(aMatrix._11),
.b = float(aMatrix._12),
.c = float(aMatrix._21),
.d = float(aMatrix._22),
.e = float(aMatrix._31),
.f = float(aMatrix._32),
});
}
static void SVGTransformToCSS(const SVGTransform& aTransform,
nsTArray<StyleTransformOperation>& aOut) {
switch (aTransform.Type()) {
case dom::SVGTransform_Binding::SVG_TRANSFORM_SCALE: {
const auto& m = aTransform.GetMatrix();
aOut.AppendElement(StyleTransformOperation::Scale(m._11, m._22));
return;
}
case dom::SVGTransform_Binding::SVG_TRANSFORM_TRANSLATE: {
auto p = aTransform.GetMatrix().GetTranslation();
aOut.AppendElement(StyleTransformOperation::Translate(
LengthPercentage::FromPixels(CSSCoord(p.x)),
LengthPercentage::FromPixels(CSSCoord(p.y))));
return;
}
case dom::SVGTransform_Binding::SVG_TRANSFORM_ROTATE: {
float cx, cy;
aTransform.GetRotationOrigin(cx, cy);
const StyleAngle angle{aTransform.Angle()};
const bool hasOrigin = cx != 0.0f || cy != 0.0f;
if (hasOrigin) {
aOut.AppendElement(StyleTransformOperation::Translate(
LengthPercentage::FromPixels(cx),
LengthPercentage::FromPixels(cy)));
}
aOut.AppendElement(StyleTransformOperation::Rotate(angle));
if (hasOrigin) {
aOut.AppendElement(StyleTransformOperation::Translate(
LengthPercentage::FromPixels(-cx),
LengthPercentage::FromPixels(-cy)));
}
return;
}
case dom::SVGTransform_Binding::SVG_TRANSFORM_SKEWX:
aOut.AppendElement(StyleTransformOperation::SkewX({aTransform.Angle()}));
return;
case dom::SVGTransform_Binding::SVG_TRANSFORM_SKEWY:
aOut.AppendElement(StyleTransformOperation::SkewY({aTransform.Angle()}));
return;
case dom::SVGTransform_Binding::SVG_TRANSFORM_MATRIX: {
aOut.AppendElement(MatrixToTransformOperation(aTransform.GetMatrix()));
return;
}
case dom::SVGTransform_Binding::SVG_TRANSFORM_UNKNOWN:
default:
MOZ_CRASH("Bad SVGTransform?");
}
}
/* static */
bool SVGElement::UpdateDeclarationBlockFromTransform(
StyleLockedDeclarationBlock& aBlock,
const SVGAnimatedTransformList* aTransform,
const gfx::Matrix* aAnimateMotionTransform, ValToUse aValToUse) {
MOZ_ASSERT(aTransform || aAnimateMotionTransform);
AutoTArray<StyleTransformOperation, 5> operations;
if (aAnimateMotionTransform) {
operations.AppendElement(
MatrixToTransformOperation(*aAnimateMotionTransform));
}
if (aTransform) {
const SVGTransformList& transforms = aValToUse == ValToUse::Anim
? aTransform->GetAnimValue()
: aTransform->GetBaseValue();
// TODO: Maybe make SVGTransform use StyleTransformOperation directly?
for (size_t i = 0, len = transforms.Length(); i < len; ++i) {
SVGTransformToCSS(transforms[i], operations);
}
}
Servo_DeclarationBlock_SetTransform(&aBlock, eCSSProperty_transform,
&operations);
return true;
}
//------------------------------------------------------------------------
// Helper class: MappedAttrParser, for parsing values of mapped attributes
namespace {
class MOZ_STACK_CLASS MappedAttrParser {
public:
explicit MappedAttrParser(SVGElement& aElement,
StyleLockedDeclarationBlock* aDecl)
: mElement(aElement), mDecl(aDecl) {
if (mDecl) {
Servo_DeclarationBlock_Clear(mDecl);
}
}
~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&);
void TellStyleAlreadyParsedResult(const SVGAnimatedTransformList&);
// If we've parsed any values for mapped attributes, this method returns the
// already_AddRefed declaration block that incorporates the parsed values.
// Otherwise, this method returns null.
already_AddRefed<StyleLockedDeclarationBlock> TakeDeclarationBlock() {
return mDecl.forget();
}
StyleLockedDeclarationBlock& EnsureDeclarationBlock() {
if (!mDecl) {
mDecl = Servo_DeclarationBlock_CreateEmpty().Consume();
}
return *mDecl;
}
URLExtraData& EnsureExtraData() {
if (!mExtraData) {
mExtraData = mElement.GetURLDataForStyleAttr();
}
return *mExtraData;
}
private:
// For reporting use counters
SVGElement& mElement;
// Declaration for storing parsed values (lazily initialized).
RefPtr<StyleLockedDeclarationBlock> mDecl;
// URL data for parsing stuff. Also lazy.
RefPtr<URLExtraData> mExtraData;
};
void MappedAttrParser::ParseMappedAttrValue(nsAtom* aMappedAttrName,
const nsAString& aMappedAttrValue) {
// Get the nsCSSPropertyID ID for our mapped attribute.
nsCSSPropertyID propertyID =
nsCSSProps::LookupProperty(nsAutoAtomCString(aMappedAttrName));
if (propertyID != eCSSProperty_UNKNOWN) {
bool changed = false; // outparam for ParseProperty.
NS_ConvertUTF16toUTF8 value(aMappedAttrValue);
auto* doc = mElement.OwnerDoc();
changed = Servo_DeclarationBlock_SetPropertyById(
&EnsureDeclarationBlock(), propertyID, &value, false,