michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* representation of a value for a SMIL-animated CSS property */ michael@0: michael@0: #include "nsSMILCSSValueType.h" michael@0: #include "nsString.h" michael@0: #include "nsStyleAnimation.h" michael@0: #include "nsSMILParserUtils.h" michael@0: #include "nsSMILValue.h" michael@0: #include "nsCSSValue.h" michael@0: #include "nsColor.h" michael@0: #include "nsPresContext.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "nsDebug.h" michael@0: #include "nsStyleUtil.h" michael@0: #include "nsIDocument.h" michael@0: michael@0: using namespace mozilla::dom; michael@0: michael@0: /*static*/ nsSMILCSSValueType nsSMILCSSValueType::sSingleton; michael@0: michael@0: struct ValueWrapper { michael@0: ValueWrapper(nsCSSProperty aPropID, const nsStyleAnimation::Value& aValue) : michael@0: mPropID(aPropID), mCSSValue(aValue) {} michael@0: michael@0: nsCSSProperty mPropID; michael@0: nsStyleAnimation::Value mCSSValue; michael@0: }; michael@0: michael@0: // Helper Methods michael@0: // -------------- michael@0: static const nsStyleAnimation::Value* michael@0: GetZeroValueForUnit(nsStyleAnimation::Unit aUnit) michael@0: { michael@0: static const nsStyleAnimation::Value michael@0: sZeroCoord(0, nsStyleAnimation::Value::CoordConstructor); michael@0: static const nsStyleAnimation::Value michael@0: sZeroPercent(0.0f, nsStyleAnimation::Value::PercentConstructor); michael@0: static const nsStyleAnimation::Value michael@0: sZeroFloat(0.0f, nsStyleAnimation::Value::FloatConstructor); michael@0: static const nsStyleAnimation::Value michael@0: sZeroColor(NS_RGB(0,0,0), nsStyleAnimation::Value::ColorConstructor); michael@0: michael@0: NS_ABORT_IF_FALSE(aUnit != nsStyleAnimation::eUnit_Null, michael@0: "Need non-null unit for a zero value"); michael@0: switch (aUnit) { michael@0: case nsStyleAnimation::eUnit_Coord: michael@0: return &sZeroCoord; michael@0: case nsStyleAnimation::eUnit_Percent: michael@0: return &sZeroPercent; michael@0: case nsStyleAnimation::eUnit_Float: michael@0: return &sZeroFloat; michael@0: case nsStyleAnimation::eUnit_Color: michael@0: return &sZeroColor; michael@0: default: michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: // This method requires at least one of its arguments to be non-null. michael@0: // michael@0: // If one argument is null, this method updates it to point to "zero" michael@0: // for the other argument's Unit (if applicable; otherwise, we return false). michael@0: // michael@0: // If neither argument is null, this method generally does nothing, though it michael@0: // may apply a workaround for the special case where a 0 length-value is mixed michael@0: // with a eUnit_Float value. (See comment below.) michael@0: // michael@0: // Returns true on success, or false. michael@0: static const bool michael@0: FinalizeStyleAnimationValues(const nsStyleAnimation::Value*& aValue1, michael@0: const nsStyleAnimation::Value*& aValue2) michael@0: { michael@0: NS_ABORT_IF_FALSE(aValue1 || aValue2, michael@0: "expecting at least one non-null value"); michael@0: michael@0: // Are we missing either val? (If so, it's an implied 0 in other val's units) michael@0: if (!aValue1) { michael@0: aValue1 = GetZeroValueForUnit(aValue2->GetUnit()); michael@0: return !!aValue1; // Fail if we have no zero value for this unit. michael@0: } michael@0: if (!aValue2) { michael@0: aValue2 = GetZeroValueForUnit(aValue1->GetUnit()); michael@0: return !!aValue2; // Fail if we have no zero value for this unit. michael@0: } michael@0: michael@0: // Ok, both values were specified. michael@0: // Need to handle a special-case, though: unitless nonzero length (parsed as michael@0: // eUnit_Float) mixed with unitless 0 length (parsed as eUnit_Coord). These michael@0: // won't interoperate in nsStyleAnimation, since their Units don't match. michael@0: // In this case, we replace the eUnit_Coord 0 value with eUnit_Float 0 value. michael@0: const nsStyleAnimation::Value& zeroCoord = michael@0: *GetZeroValueForUnit(nsStyleAnimation::eUnit_Coord); michael@0: if (*aValue1 == zeroCoord && michael@0: aValue2->GetUnit() == nsStyleAnimation::eUnit_Float) { michael@0: aValue1 = GetZeroValueForUnit(nsStyleAnimation::eUnit_Float); michael@0: } else if (*aValue2 == zeroCoord && michael@0: aValue1->GetUnit() == nsStyleAnimation::eUnit_Float) { michael@0: aValue2 = GetZeroValueForUnit(nsStyleAnimation::eUnit_Float); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: InvertSign(nsStyleAnimation::Value& aValue) michael@0: { michael@0: switch (aValue.GetUnit()) { michael@0: case nsStyleAnimation::eUnit_Coord: michael@0: aValue.SetCoordValue(-aValue.GetCoordValue()); michael@0: break; michael@0: case nsStyleAnimation::eUnit_Percent: michael@0: aValue.SetPercentValue(-aValue.GetPercentValue()); michael@0: break; michael@0: case nsStyleAnimation::eUnit_Float: michael@0: aValue.SetFloatValue(-aValue.GetFloatValue()); michael@0: break; michael@0: default: michael@0: NS_NOTREACHED("Calling InvertSign with an unsupported unit"); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: static ValueWrapper* michael@0: ExtractValueWrapper(nsSMILValue& aValue) michael@0: { michael@0: return static_cast(aValue.mU.mPtr); michael@0: } michael@0: michael@0: static const ValueWrapper* michael@0: ExtractValueWrapper(const nsSMILValue& aValue) michael@0: { michael@0: return static_cast(aValue.mU.mPtr); michael@0: } michael@0: michael@0: // Class methods michael@0: // ------------- michael@0: void michael@0: nsSMILCSSValueType::Init(nsSMILValue& aValue) const michael@0: { michael@0: NS_ABORT_IF_FALSE(aValue.IsNull(), "Unexpected SMIL value type"); michael@0: michael@0: aValue.mU.mPtr = nullptr; michael@0: aValue.mType = this; michael@0: } michael@0: michael@0: void michael@0: nsSMILCSSValueType::Destroy(nsSMILValue& aValue) const michael@0: { michael@0: NS_ABORT_IF_FALSE(aValue.mType == this, "Unexpected SMIL value type"); michael@0: delete static_cast(aValue.mU.mPtr); michael@0: aValue.mType = nsSMILNullType::Singleton(); michael@0: } michael@0: michael@0: nsresult michael@0: nsSMILCSSValueType::Assign(nsSMILValue& aDest, const nsSMILValue& aSrc) const michael@0: { michael@0: NS_ABORT_IF_FALSE(aDest.mType == aSrc.mType, "Incompatible SMIL types"); michael@0: NS_ABORT_IF_FALSE(aDest.mType == this, "Unexpected SMIL value type"); michael@0: const ValueWrapper* srcWrapper = ExtractValueWrapper(aSrc); michael@0: ValueWrapper* destWrapper = ExtractValueWrapper(aDest); michael@0: michael@0: if (srcWrapper) { michael@0: if (!destWrapper) { michael@0: // barely-initialized dest -- need to alloc & copy michael@0: aDest.mU.mPtr = new ValueWrapper(*srcWrapper); michael@0: } else { michael@0: // both already fully-initialized -- just copy straight across michael@0: *destWrapper = *srcWrapper; michael@0: } michael@0: } else if (destWrapper) { michael@0: // fully-initialized dest, barely-initialized src -- clear dest michael@0: delete destWrapper; michael@0: aDest.mU.mPtr = destWrapper = nullptr; michael@0: } // else, both are barely-initialized -- nothing to do. michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsSMILCSSValueType::IsEqual(const nsSMILValue& aLeft, michael@0: const nsSMILValue& aRight) const michael@0: { michael@0: NS_ABORT_IF_FALSE(aLeft.mType == aRight.mType, "Incompatible SMIL types"); michael@0: NS_ABORT_IF_FALSE(aLeft.mType == this, "Unexpected SMIL value"); michael@0: const ValueWrapper* leftWrapper = ExtractValueWrapper(aLeft); michael@0: const ValueWrapper* rightWrapper = ExtractValueWrapper(aRight); michael@0: michael@0: if (leftWrapper) { michael@0: if (rightWrapper) { michael@0: // Both non-null michael@0: NS_WARN_IF_FALSE(leftWrapper != rightWrapper, michael@0: "Two nsSMILValues with matching ValueWrapper ptr"); michael@0: return (leftWrapper->mPropID == rightWrapper->mPropID && michael@0: leftWrapper->mCSSValue == rightWrapper->mCSSValue); michael@0: } michael@0: // Left non-null, right null michael@0: return false; michael@0: } michael@0: if (rightWrapper) { michael@0: // Left null, right non-null michael@0: return false; michael@0: } michael@0: // Both null michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: nsSMILCSSValueType::Add(nsSMILValue& aDest, const nsSMILValue& aValueToAdd, michael@0: uint32_t aCount) const michael@0: { michael@0: NS_ABORT_IF_FALSE(aValueToAdd.mType == aDest.mType, michael@0: "Trying to add invalid types"); michael@0: NS_ABORT_IF_FALSE(aValueToAdd.mType == this, "Unexpected source type"); michael@0: michael@0: ValueWrapper* destWrapper = ExtractValueWrapper(aDest); michael@0: const ValueWrapper* valueToAddWrapper = ExtractValueWrapper(aValueToAdd); michael@0: NS_ABORT_IF_FALSE(destWrapper || valueToAddWrapper, michael@0: "need at least one fully-initialized value"); michael@0: michael@0: nsCSSProperty property = (valueToAddWrapper ? valueToAddWrapper->mPropID : michael@0: destWrapper->mPropID); michael@0: // Special case: font-size-adjust and stroke-dasharray are explicitly michael@0: // non-additive (even though nsStyleAnimation *could* support adding them) michael@0: if (property == eCSSProperty_font_size_adjust || michael@0: property == eCSSProperty_stroke_dasharray) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: const nsStyleAnimation::Value* valueToAdd = valueToAddWrapper ? michael@0: &valueToAddWrapper->mCSSValue : nullptr; michael@0: const nsStyleAnimation::Value* destValue = destWrapper ? michael@0: &destWrapper->mCSSValue : nullptr; michael@0: if (!FinalizeStyleAnimationValues(valueToAdd, destValue)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: // Did FinalizeStyleAnimationValues change destValue? michael@0: // If so, update outparam to use the new value. michael@0: if (destWrapper && &destWrapper->mCSSValue != destValue) { michael@0: destWrapper->mCSSValue = *destValue; michael@0: } michael@0: michael@0: // Handle barely-initialized "zero" destination. michael@0: if (!destWrapper) { michael@0: aDest.mU.mPtr = destWrapper = michael@0: new ValueWrapper(property, *destValue); michael@0: } michael@0: michael@0: return nsStyleAnimation::Add(property, michael@0: destWrapper->mCSSValue, *valueToAdd, aCount) ? michael@0: NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsresult michael@0: nsSMILCSSValueType::ComputeDistance(const nsSMILValue& aFrom, michael@0: const nsSMILValue& aTo, michael@0: double& aDistance) const michael@0: { michael@0: NS_ABORT_IF_FALSE(aFrom.mType == aTo.mType, michael@0: "Trying to compare different types"); michael@0: NS_ABORT_IF_FALSE(aFrom.mType == this, "Unexpected source type"); michael@0: michael@0: const ValueWrapper* fromWrapper = ExtractValueWrapper(aFrom); michael@0: const ValueWrapper* toWrapper = ExtractValueWrapper(aTo); michael@0: NS_ABORT_IF_FALSE(toWrapper, "expecting non-null endpoint"); michael@0: michael@0: const nsStyleAnimation::Value* fromCSSValue = fromWrapper ? michael@0: &fromWrapper->mCSSValue : nullptr; michael@0: const nsStyleAnimation::Value* toCSSValue = &toWrapper->mCSSValue; michael@0: if (!FinalizeStyleAnimationValues(fromCSSValue, toCSSValue)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return nsStyleAnimation::ComputeDistance(toWrapper->mPropID, michael@0: *fromCSSValue, *toCSSValue, michael@0: aDistance) ? michael@0: NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsresult michael@0: nsSMILCSSValueType::Interpolate(const nsSMILValue& aStartVal, michael@0: const nsSMILValue& aEndVal, michael@0: double aUnitDistance, michael@0: nsSMILValue& aResult) const michael@0: { michael@0: NS_ABORT_IF_FALSE(aStartVal.mType == aEndVal.mType, michael@0: "Trying to interpolate different types"); michael@0: NS_ABORT_IF_FALSE(aStartVal.mType == this, michael@0: "Unexpected types for interpolation"); michael@0: NS_ABORT_IF_FALSE(aResult.mType == this, "Unexpected result type"); michael@0: NS_ABORT_IF_FALSE(aUnitDistance >= 0.0 && aUnitDistance <= 1.0, michael@0: "unit distance value out of bounds"); michael@0: NS_ABORT_IF_FALSE(!aResult.mU.mPtr, "expecting barely-initialized outparam"); michael@0: michael@0: const ValueWrapper* startWrapper = ExtractValueWrapper(aStartVal); michael@0: const ValueWrapper* endWrapper = ExtractValueWrapper(aEndVal); michael@0: NS_ABORT_IF_FALSE(endWrapper, "expecting non-null endpoint"); michael@0: michael@0: const nsStyleAnimation::Value* startCSSValue = startWrapper ? michael@0: &startWrapper->mCSSValue : nullptr; michael@0: const nsStyleAnimation::Value* endCSSValue = &endWrapper->mCSSValue; michael@0: if (!FinalizeStyleAnimationValues(startCSSValue, endCSSValue)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsStyleAnimation::Value resultValue; michael@0: if (nsStyleAnimation::Interpolate(endWrapper->mPropID, michael@0: *startCSSValue, *endCSSValue, michael@0: aUnitDistance, resultValue)) { michael@0: aResult.mU.mPtr = new ValueWrapper(endWrapper->mPropID, resultValue); michael@0: return NS_OK; michael@0: } michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Helper function to extract presContext michael@0: static nsPresContext* michael@0: GetPresContextForElement(Element* aElem) michael@0: { michael@0: nsIDocument* doc = aElem->GetCurrentDoc(); michael@0: if (!doc) { michael@0: // This can happen if we process certain types of restyles mid-sample michael@0: // and remove anonymous animated content from the document as a result. michael@0: // See bug 534975. michael@0: return nullptr; michael@0: } michael@0: nsIPresShell* shell = doc->GetShell(); michael@0: return shell ? shell->GetPresContext() : nullptr; michael@0: } michael@0: michael@0: // Helper function to parse a string into a nsStyleAnimation::Value michael@0: static bool michael@0: ValueFromStringHelper(nsCSSProperty aPropID, michael@0: Element* aTargetElement, michael@0: nsPresContext* aPresContext, michael@0: const nsAString& aString, michael@0: nsStyleAnimation::Value& aStyleAnimValue, michael@0: bool* aIsContextSensitive) michael@0: { michael@0: // If value is negative, we'll strip off the "-" so the CSS parser won't michael@0: // barf, and then manually make the parsed value negative. michael@0: // (This is a partial solution to let us accept some otherwise out-of-bounds michael@0: // CSS values. Bug 501188 will provide a more complete fix.) michael@0: bool isNegative = false; michael@0: uint32_t subStringBegin = 0; michael@0: michael@0: // NOTE: We need to opt-out 'stroke-dasharray' from the negative-number michael@0: // check. Its values might look negative (e.g. by starting with "-1"), but michael@0: // they're more complicated than our simple negation logic here can handle. michael@0: if (aPropID != eCSSProperty_stroke_dasharray) { michael@0: int32_t absValuePos = nsSMILParserUtils::CheckForNegativeNumber(aString); michael@0: if (absValuePos > 0) { michael@0: isNegative = true; michael@0: subStringBegin = (uint32_t)absValuePos; // Start parsing after '-' sign michael@0: } michael@0: } michael@0: nsDependentSubstring subString(aString, subStringBegin); michael@0: if (!nsStyleAnimation::ComputeValue(aPropID, aTargetElement, subString, michael@0: true, aStyleAnimValue, michael@0: aIsContextSensitive)) { michael@0: return false; michael@0: } michael@0: if (isNegative) { michael@0: InvertSign(aStyleAnimValue); michael@0: } michael@0: michael@0: if (aPropID == eCSSProperty_font_size) { michael@0: // Divide out text-zoom, since SVG is supposed to ignore it michael@0: NS_ABORT_IF_FALSE(aStyleAnimValue.GetUnit() == michael@0: nsStyleAnimation::eUnit_Coord, michael@0: "'font-size' value with unexpected style unit"); michael@0: aStyleAnimValue.SetCoordValue(aStyleAnimValue.GetCoordValue() / michael@0: aPresContext->TextZoom()); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsSMILCSSValueType::ValueFromString(nsCSSProperty aPropID, michael@0: Element* aTargetElement, michael@0: const nsAString& aString, michael@0: nsSMILValue& aValue, michael@0: bool* aIsContextSensitive) michael@0: { michael@0: NS_ABORT_IF_FALSE(aValue.IsNull(), "Outparam should be null-typed"); michael@0: nsPresContext* presContext = GetPresContextForElement(aTargetElement); michael@0: if (!presContext) { michael@0: NS_WARNING("Not parsing animation value; unable to get PresContext"); michael@0: return; michael@0: } michael@0: michael@0: nsIDocument* doc = aTargetElement->GetCurrentDoc(); michael@0: if (doc && !nsStyleUtil::CSPAllowsInlineStyle(nullptr, michael@0: doc->NodePrincipal(), michael@0: doc->GetDocumentURI(), michael@0: 0, aString, nullptr)) { michael@0: return; michael@0: } michael@0: michael@0: nsStyleAnimation::Value parsedValue; michael@0: if (ValueFromStringHelper(aPropID, aTargetElement, presContext, michael@0: aString, parsedValue, aIsContextSensitive)) { michael@0: sSingleton.Init(aValue); michael@0: aValue.mU.mPtr = new ValueWrapper(aPropID, parsedValue); michael@0: } michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: nsSMILCSSValueType::ValueToString(const nsSMILValue& aValue, michael@0: nsAString& aString) michael@0: { michael@0: NS_ABORT_IF_FALSE(aValue.mType == &nsSMILCSSValueType::sSingleton, michael@0: "Unexpected SMIL value type"); michael@0: const ValueWrapper* wrapper = ExtractValueWrapper(aValue); michael@0: return !wrapper || michael@0: nsStyleAnimation::UncomputeValue(wrapper->mPropID, michael@0: wrapper->mCSSValue, aString); michael@0: }