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
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* representation of a value for a SMIL-animated CSS property */
#include "SMILCSSValueType.h"
#include "nsComputedDOMStyle.h"
#include "nsColor.h"
#include "nsCSSProps.h"
#include "nsCSSValue.h"
#include "nsDebug.h"
#include "nsPresContextInlines.h"
#include "nsPresContext.h"
#include "nsString.h"
#include "nsStyleUtil.h"
#include "mozilla/DeclarationBlock.h"
#include "mozilla/PresShell.h"
#include "mozilla/PresShellInlines.h"
#include "mozilla/ServoBindings.h"
#include "mozilla/StyleAnimationValue.h"
#include "mozilla/ServoCSSParser.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/SMILParserUtils.h"
#include "mozilla/SMILValue.h"
#include "mozilla/dom/BaseKeyframeTypesBinding.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Element.h"
using namespace mozilla::dom;
namespace mozilla {
using ServoAnimationValues = CopyableAutoTArray<RefPtr<StyleAnimationValue>, 1>;
/*static*/
SMILCSSValueType SMILCSSValueType::sSingleton;
struct ValueWrapper {
ValueWrapper(nsCSSPropertyID aPropID, const AnimationValue& aValue)
: mPropID(aPropID) {
MOZ_ASSERT(!aValue.IsNull());
mServoValues.AppendElement(aValue.mServo);
}
ValueWrapper(nsCSSPropertyID aPropID,
const RefPtr<StyleAnimationValue>& aValue)
: mPropID(aPropID), mServoValues{(aValue)} {}
ValueWrapper(nsCSSPropertyID aPropID, ServoAnimationValues&& aValues)
: mPropID(aPropID), mServoValues{std::move(aValues)} {}
bool operator==(const ValueWrapper& aOther) const {
if (mPropID != aOther.mPropID) {
return false;
}
MOZ_ASSERT(!mServoValues.IsEmpty());
size_t len = mServoValues.Length();
if (len != aOther.mServoValues.Length()) {
return false;
}
for (size_t i = 0; i < len; i++) {
if (!Servo_AnimationValue_DeepEqual(mServoValues[i],
aOther.mServoValues[i])) {
return false;
}
}
return true;
}
bool operator!=(const ValueWrapper& aOther) const {
return !(*this == aOther);
}
nsCSSPropertyID mPropID;
ServoAnimationValues mServoValues;
};
// Helper Methods
// --------------
// If one argument is null, this method updates it to point to "zero"
// for the other argument's Unit (if applicable; otherwise, we return false).
//
// If neither argument is null, this method simply returns true.
//
// If both arguments are null, this method returns false.
//
// |aZeroValueStorage| should be a reference to a
// RefPtr<StyleAnimationValue>. This is used where we may need to allocate a
// new ServoAnimationValue to represent the appropriate zero value.
//
// Returns true on success, or otherwise.
static bool FinalizeServoAnimationValues(
const RefPtr<StyleAnimationValue>*& aValue1,
const RefPtr<StyleAnimationValue>*& aValue2,
RefPtr<StyleAnimationValue>& aZeroValueStorage) {
if (!aValue1 && !aValue2) {
return false;
}
// Are we missing either val? (If so, it's an implied 0 in other val's units)
if (!aValue1) {
aZeroValueStorage = Servo_AnimationValues_GetZeroValue(*aValue2).Consume();
aValue1 = &aZeroValueStorage;
} else if (!aValue2) {
aZeroValueStorage = Servo_AnimationValues_GetZeroValue(*aValue1).Consume();
aValue2 = &aZeroValueStorage;
}
return *aValue1 && *aValue2;
}
static ValueWrapper* ExtractValueWrapper(SMILValue& aValue) {
return static_cast<ValueWrapper*>(aValue.mU.mPtr);
}
static const ValueWrapper* ExtractValueWrapper(const SMILValue& aValue) {
return static_cast<const ValueWrapper*>(aValue.mU.mPtr);
}
// Class methods
// -------------
void SMILCSSValueType::Init(SMILValue& aValue) const {
MOZ_ASSERT(aValue.IsNull(), "Unexpected SMIL value type");
aValue.mU.mPtr = nullptr;
aValue.mType = this;
}
void SMILCSSValueType::Destroy(SMILValue& aValue) const {
MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL value type");
delete static_cast<ValueWrapper*>(aValue.mU.mPtr);
aValue.mType = SMILNullType::Singleton();
}
nsresult SMILCSSValueType::Assign(SMILValue& aDest,
const SMILValue& aSrc) const {
MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types");
MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL value type");
const ValueWrapper* srcWrapper = ExtractValueWrapper(aSrc);
ValueWrapper* destWrapper = ExtractValueWrapper(aDest);
if (srcWrapper) {
if (!destWrapper) {
// barely-initialized dest -- need to alloc & copy
aDest.mU.mPtr = new ValueWrapper(*srcWrapper);
} else {
// both already fully-initialized -- just copy straight across
*destWrapper = *srcWrapper;
}
} else if (destWrapper) {
// fully-initialized dest, barely-initialized src -- clear dest
delete destWrapper;
aDest.mU.mPtr = destWrapper = nullptr;
} // else, both are barely-initialized -- nothing to do.
return NS_OK;
}
bool SMILCSSValueType::IsEqual(const SMILValue& aLeft,
const SMILValue& aRight) const {
MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types");
MOZ_ASSERT(aLeft.mType == this, "Unexpected SMIL value");
const ValueWrapper* leftWrapper = ExtractValueWrapper(aLeft);
const ValueWrapper* rightWrapper = ExtractValueWrapper(aRight);
if (leftWrapper) {
if (rightWrapper) {
// Both non-null
NS_WARNING_ASSERTION(leftWrapper != rightWrapper,
"Two SMILValues with matching ValueWrapper ptr");
return *leftWrapper == *rightWrapper;
}
// Left non-null, right null
return false;
}
if (rightWrapper) {
// Left null, right non-null
return false;
}
// Both null
return true;
}
static bool AddOrAccumulate(SMILValue& aDest, const SMILValue& aValueToAdd,
CompositeOperation aCompositeOp, uint64_t aCount) {
MOZ_ASSERT(aValueToAdd.mType == aDest.mType,
"Trying to add mismatching types");
MOZ_ASSERT(aValueToAdd.mType == &SMILCSSValueType::sSingleton,
"Unexpected SMIL value type");
MOZ_ASSERT(aCompositeOp == CompositeOperation::Add ||
aCompositeOp == CompositeOperation::Accumulate,
"Composite operation should be add or accumulate");
MOZ_ASSERT(aCompositeOp != CompositeOperation::Add || aCount == 1,
"Count should be 1 if composite operation is add");
ValueWrapper* destWrapper = ExtractValueWrapper(aDest);
const ValueWrapper* valueToAddWrapper = ExtractValueWrapper(aValueToAdd);
// If both of the values are empty just fail. This can happen in rare cases
// such as when the underlying animation produced an empty value.
//
// Technically, it doesn't matter what we return here since in either case it
// will produce the same result: an empty value.
if (!destWrapper && !valueToAddWrapper) {
return false;
}
nsCSSPropertyID property =
valueToAddWrapper ? valueToAddWrapper->mPropID : destWrapper->mPropID;
// Special case: font-size-adjust and stroke-dasharray are explicitly
// non-additive (even though StyleAnimationValue *could* support adding them)
if (property == eCSSProperty_font_size_adjust ||
property == eCSSProperty_stroke_dasharray) {
return false;
}
// Skip font shorthand since it includes font-size-adjust.
if (property == eCSSProperty_font) {
return false;
}
size_t len = valueToAddWrapper ? valueToAddWrapper->mServoValues.Length()
: destWrapper->mServoValues.Length();
MOZ_ASSERT(!valueToAddWrapper || !destWrapper ||
valueToAddWrapper->mServoValues.Length() ==
destWrapper->mServoValues.Length(),
"Both of values' length in the wrappers should be the same if "
"both of them exist");
for (size_t i = 0; i < len; i++) {
const RefPtr<StyleAnimationValue>* valueToAdd =
valueToAddWrapper ? &valueToAddWrapper->mServoValues[i] : nullptr;
const RefPtr<StyleAnimationValue>* destValue =
destWrapper ? &destWrapper->mServoValues[i] : nullptr;
RefPtr<StyleAnimationValue> zeroValueStorage;
if (!FinalizeServoAnimationValues(valueToAdd, destValue,
zeroValueStorage)) {
return false;
}
// FinalizeServoAnimationValues may have updated destValue so we should make
// sure the aDest and aDestWrapper outparams are up-to-date.
if (destWrapper) {
destWrapper->mServoValues[i] = *destValue;
} else {
// aDest may be a barely-initialized "zero" destination.
aDest.mU.mPtr = destWrapper = new ValueWrapper(property, *destValue);
destWrapper->mServoValues.SetLength(len);
}
RefPtr<StyleAnimationValue> result;
if (aCompositeOp == CompositeOperation::Add) {
result = Servo_AnimationValues_Add(*destValue, *valueToAdd).Consume();
} else {
result = Servo_AnimationValues_Accumulate(*destValue, *valueToAdd, aCount)
.Consume();
}
if (!result) {
return false;
}
destWrapper->mServoValues[i] = result;
}
return true;
}
nsresult SMILCSSValueType::SandwichAdd(SMILValue& aDest,
const SMILValue& aValueToAdd) const {
return AddOrAccumulate(aDest, aValueToAdd, CompositeOperation::Add, 1)
? NS_OK
: NS_ERROR_FAILURE;
}
nsresult SMILCSSValueType::Add(SMILValue& aDest, const SMILValue& aValueToAdd,
uint32_t aCount) const {
return AddOrAccumulate(aDest, aValueToAdd, CompositeOperation::Accumulate,
aCount)
? NS_OK
: NS_ERROR_FAILURE;
}
nsresult SMILCSSValueType::ComputeDistance(const SMILValue& aFrom,
const SMILValue& aTo,
double& aDistance) const {
MOZ_ASSERT(aFrom.mType == aTo.mType, "Trying to compare different types");
MOZ_ASSERT(aFrom.mType == this, "Unexpected source type");
const ValueWrapper* fromWrapper = ExtractValueWrapper(aFrom);
const ValueWrapper* toWrapper = ExtractValueWrapper(aTo);
MOZ_ASSERT(toWrapper, "expecting non-null endpoint");
size_t len = toWrapper->mServoValues.Length();
MOZ_ASSERT(!fromWrapper || fromWrapper->mServoValues.Length() == len,
"From and to values length should be the same if "
"The start value exists");
double squareDistance = 0;
for (size_t i = 0; i < len; i++) {
const RefPtr<StyleAnimationValue>* fromValue =
fromWrapper ? &fromWrapper->mServoValues[i] : nullptr;
const RefPtr<StyleAnimationValue>* toValue = &toWrapper->mServoValues[i];
RefPtr<StyleAnimationValue> zeroValueStorage;
if (!FinalizeServoAnimationValues(fromValue, toValue, zeroValueStorage)) {
return NS_ERROR_FAILURE;
}
double distance =
Servo_AnimationValues_ComputeDistance(*fromValue, *toValue);
if (distance < 0.0) {
return NS_ERROR_FAILURE;
}
if (len == 1) {
aDistance = distance;
return NS_OK;
}
squareDistance += distance * distance;
}
aDistance = sqrt(squareDistance);
return NS_OK;
}
nsresult SMILCSSValueType::Interpolate(const SMILValue& aStartVal,
const SMILValue& aEndVal,
double aUnitDistance,
SMILValue& aResult) const {
MOZ_ASSERT(aStartVal.mType == aEndVal.mType,
"Trying to interpolate different types");
MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation");
MOZ_ASSERT(aResult.mType == this, "Unexpected result type");
MOZ_ASSERT(aUnitDistance >= 0.0 && aUnitDistance <= 1.0,
"unit distance value out of bounds");
MOZ_ASSERT(!aResult.mU.mPtr, "expecting barely-initialized outparam");
const ValueWrapper* startWrapper = ExtractValueWrapper(aStartVal);
const ValueWrapper* endWrapper = ExtractValueWrapper(aEndVal);
MOZ_ASSERT(endWrapper, "expecting non-null endpoint");
// For discretely-animated properties Servo_AnimationValues_Interpolate will
// perform the discrete animation (i.e. 50% flip) and return a success result.
// However, SMIL has its own special discrete animation behavior that it uses
// when keyTimes are specified, but we won't run that unless that this method
// returns a failure to indicate that the property cannot be smoothly
// interpolated, i.e. that we need to use a discrete calcMode.
//
// For shorthands, Servo_Property_IsDiscreteAnimatable will always return
// false. That's fine since most shorthands (like 'font' and
// 'text-decoration') include non-discrete components. If authors want to
// treat all components as discrete then they should use calcMode="discrete".
if (Servo_Property_IsDiscreteAnimatable(endWrapper->mPropID)) {
return NS_ERROR_FAILURE;
}
ServoAnimationValues results;
size_t len = endWrapper->mServoValues.Length();
results.SetCapacity(len);
MOZ_ASSERT(!startWrapper || startWrapper->mServoValues.Length() == len,
"Start and end values length should be the same if "
"the start value exists");
for (size_t i = 0; i < len; i++) {
const RefPtr<StyleAnimationValue>* startValue =
startWrapper ? &startWrapper->mServoValues[i] : nullptr;
const RefPtr<StyleAnimationValue>* endValue = &endWrapper->mServoValues[i];
RefPtr<StyleAnimationValue> zeroValueStorage;
if (!FinalizeServoAnimationValues(startValue, endValue, zeroValueStorage)) {
return NS_ERROR_FAILURE;
}
RefPtr<StyleAnimationValue> result =
Servo_AnimationValues_Interpolate(*startValue, *endValue, aUnitDistance)
.Consume();
if (!result) {
return NS_ERROR_FAILURE;
}
results.AppendElement(result);
}
aResult.mU.mPtr = new ValueWrapper(endWrapper->mPropID, std::move(results));
return NS_OK;
}
static ServoAnimationValues ValueFromStringHelper(
nsCSSPropertyID aPropID, Element* aTargetElement,
nsPresContext* aPresContext, const ComputedStyle* aComputedStyle,
const nsAString& aString) {
ServoAnimationValues result;
Document* doc = aTargetElement->GetComposedDoc();
if (!doc) {
return result;
}
// Parse property
ServoCSSParser::ParsingEnvironment env =
ServoCSSParser::GetParsingEnvironment(doc);
RefPtr<StyleLockedDeclarationBlock> servoDeclarationBlock =
ServoCSSParser::ParseProperty(
aPropID, NS_ConvertUTF16toUTF8(aString), env,
StyleParsingMode::ALLOW_UNITLESS_LENGTH |
StyleParsingMode::ALLOW_ALL_NUMERIC_VALUES);
if (!servoDeclarationBlock) {
return result;
}
// Compute value
aPresContext->StyleSet()->GetAnimationValues(
servoDeclarationBlock, aTargetElement, aComputedStyle, result);
return result;
}
// static
void SMILCSSValueType::ValueFromString(nsCSSPropertyID aPropID,
Element* aTargetElement,
const nsAString& aString,
SMILValue& aValue,
bool* aIsContextSensitive) {
MOZ_ASSERT(aValue.IsNull(), "Outparam should be null-typed");
nsPresContext* presContext =
nsContentUtils::GetContextForContent(aTargetElement);
if (!presContext) {
NS_WARNING("Not parsing animation value; unable to get PresContext");
return;
}
Document* doc = aTargetElement->GetComposedDoc();
if (doc && !nsStyleUtil::CSPAllowsInlineStyle(nullptr, doc, nullptr, 0, 1,
aString, nullptr)) {
return;
}
RefPtr<const ComputedStyle> computedStyle =
nsComputedDOMStyle::GetComputedStyle(aTargetElement);
if (!computedStyle) {
return;
}
ServoAnimationValues parsedValues = ValueFromStringHelper(
aPropID, aTargetElement, presContext, computedStyle, aString);
if (aIsContextSensitive) {
// FIXME: Bug 1358955 - detect context-sensitive values and set this value
// appropriately.
*aIsContextSensitive = false;
}
if (!parsedValues.IsEmpty()) {
sSingleton.Init(aValue);
aValue.mU.mPtr = new ValueWrapper(aPropID, std::move(parsedValues));
}
}
// static
SMILValue SMILCSSValueType::ValueFromAnimationValue(
nsCSSPropertyID aPropID, Element* aTargetElement,
const AnimationValue& aValue) {
SMILValue result;
Document* doc = aTargetElement->GetComposedDoc();
// We'd like to avoid serializing |aValue| if possible, and since the
// string passed to CSPAllowsInlineStyle is only used for reporting violations
// and an intermediate CSS value is not likely to be particularly useful
// in that case, we just use a generic placeholder string instead.
static const nsLiteralString kPlaceholderText = u"[SVG animation of CSS]"_ns;
if (doc && !nsStyleUtil::CSPAllowsInlineStyle(nullptr, doc, nullptr, 0, 1,
kPlaceholderText, nullptr)) {
return result;
}
sSingleton.Init(result);
result.mU.mPtr = new ValueWrapper(aPropID, aValue);
return result;
}
// static
bool SMILCSSValueType::SetPropertyValues(nsCSSPropertyID aPropertyId,
const SMILValue& aValue,
DeclarationBlock& aDecl) {
MOZ_ASSERT(aValue.mType == &SMILCSSValueType::sSingleton,
"Unexpected SMIL value type");
const ValueWrapper* wrapper = ExtractValueWrapper(aValue);
if (!wrapper) {
return Servo_DeclarationBlock_RemovePropertyById(aDecl.Raw(), aPropertyId,
{});
}
bool changed = false;
for (const auto& value : wrapper->mServoValues) {
changed |= Servo_DeclarationBlock_SetPropertyToAnimationValue(aDecl.Raw(),
value, {});
}
return changed;
}
// static
nsCSSPropertyID SMILCSSValueType::PropertyFromValue(const SMILValue& aValue) {
if (aValue.mType != &SMILCSSValueType::sSingleton) {
return eCSSProperty_UNKNOWN;
}
const ValueWrapper* wrapper = ExtractValueWrapper(aValue);
if (!wrapper) {
return eCSSProperty_UNKNOWN;
}
return wrapper->mPropID;
}
// static
void SMILCSSValueType::FinalizeValue(SMILValue& aValue,
const SMILValue& aValueToMatch) {
MOZ_ASSERT(aValue.mType == aValueToMatch.mType, "Incompatible SMIL types");
MOZ_ASSERT(aValue.mType == &SMILCSSValueType::sSingleton,
"Unexpected SMIL value type");
ValueWrapper* valueWrapper = ExtractValueWrapper(aValue);
// If |aValue| already has a value, there's nothing to do here.
if (valueWrapper) {
return;
}
const ValueWrapper* valueToMatchWrapper = ExtractValueWrapper(aValueToMatch);
if (!valueToMatchWrapper) {
MOZ_ASSERT_UNREACHABLE("Value to match is empty");
return;
}
ServoAnimationValues zeroValues;
zeroValues.SetCapacity(valueToMatchWrapper->mServoValues.Length());
for (const auto& valueToMatch : valueToMatchWrapper->mServoValues) {
RefPtr<StyleAnimationValue> zeroValue =
Servo_AnimationValues_GetZeroValue(valueToMatch).Consume();
if (!zeroValue) {
return;
}
zeroValues.AppendElement(std::move(zeroValue));
}
aValue.mU.mPtr =
new ValueWrapper(valueToMatchWrapper->mPropID, std::move(zeroValues));
}
} // namespace mozilla