Extend CSSNumericValue.parse() to support simple numbers, percentages, angles, and times in addition to the already-supported length values. Also parse the whole input so strings with trailing tokens are rejected, and update WPT expectations for tests that now pass with the additional numeric value support. Differential Revision: https://phabricator.services.mozilla.com/D309251
274 lines
8.7 KiB
C++
274 lines
8.7 KiB
C++
/* 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 "AnimationUtils.h"
|
|
|
|
#include "mozilla/EffectSet.h"
|
|
#include "mozilla/ErrorResult.h"
|
|
#include "mozilla/StaticPrefs_layout.h"
|
|
#include "mozilla/dom/Animation.h"
|
|
#include "mozilla/dom/CSSNumericValueBinding.h"
|
|
#include "mozilla/dom/CSSUnitValue.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "mozilla/dom/KeyframeEffect.h"
|
|
#include "mozilla/dom/ScrollTimeline.h" // For PROGRESS_TIMELINE_DURATION_MILLISEC
|
|
#include "nsAtom.h"
|
|
#include "nsDebug.h"
|
|
#include "nsGlobalWindowInner.h"
|
|
#include "nsIContent.h"
|
|
#include "nsLayoutUtils.h"
|
|
#include "nsString.h"
|
|
#include "xpcpublic.h" // For xpc::NativeGlobal
|
|
|
|
using namespace mozilla::dom;
|
|
|
|
namespace mozilla {
|
|
|
|
/* static */
|
|
void AnimationUtils::LogAsyncAnimationFailure(nsCString& aMessage,
|
|
const nsIContent* aContent) {
|
|
if (aContent) {
|
|
aMessage.AppendLiteral(" [");
|
|
aMessage.Append(nsAtomCString(aContent->NodeInfo()->NameAtom()));
|
|
|
|
nsAtom* id = aContent->GetID();
|
|
if (id) {
|
|
aMessage.AppendLiteral(" with id '");
|
|
aMessage.Append(nsAtomCString(aContent->GetID()));
|
|
aMessage.Append('\'');
|
|
}
|
|
aMessage.Append(']');
|
|
}
|
|
aMessage.Append('\n');
|
|
printf_stderr("%s", aMessage.get());
|
|
}
|
|
|
|
/* static */
|
|
Document* AnimationUtils::GetCurrentRealmDocument(JSContext* aCx) {
|
|
nsGlobalWindowInner* win = xpc::CurrentWindowOrNull(aCx);
|
|
if (!win) {
|
|
return nullptr;
|
|
}
|
|
return win->GetDoc();
|
|
}
|
|
|
|
/* static */
|
|
Document* AnimationUtils::GetDocumentFromGlobal(JSObject* aGlobalObject) {
|
|
nsGlobalWindowInner* win = xpc::WindowOrNull(aGlobalObject);
|
|
if (!win) {
|
|
return nullptr;
|
|
}
|
|
return win->GetDoc();
|
|
}
|
|
|
|
/* static */
|
|
bool AnimationUtils::FrameHasAnimatedScale(const nsIFrame* aFrame) {
|
|
EffectSet* effectSet = EffectSet::GetForFrame(
|
|
aFrame, nsCSSPropertyIDSet::TransformLikeProperties());
|
|
if (!effectSet) {
|
|
return false;
|
|
}
|
|
|
|
for (const dom::KeyframeEffect* effect : *effectSet) {
|
|
if (effect->ContainsAnimatedScale(aFrame)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* static */
|
|
bool AnimationUtils::HasCurrentTransitions(
|
|
const Element* aElement, const PseudoStyleRequest& aPseudoRequest) {
|
|
MOZ_ASSERT(aElement);
|
|
|
|
EffectSet* effectSet = EffectSet::Get(aElement, aPseudoRequest);
|
|
if (!effectSet) {
|
|
return false;
|
|
}
|
|
|
|
for (const dom::KeyframeEffect* effect : *effectSet) {
|
|
// If |effect| is current, it must have an associated Animation
|
|
// so we don't need to null-check the result of GetAnimation().
|
|
if (effect->IsCurrent() && effect->GetAnimation()->AsCSSTransition()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*static*/
|
|
std::pair<const Element*, PseudoStyleRequest>
|
|
AnimationUtils::GetElementPseudoPair(const Element* aElementOrPseudo) {
|
|
MOZ_ASSERT(aElementOrPseudo);
|
|
|
|
if (aElementOrPseudo->IsGeneratedContentContainerForBefore()) {
|
|
return {aElementOrPseudo->GetParent()->AsElement(),
|
|
PseudoStyleRequest::Before()};
|
|
}
|
|
|
|
if (aElementOrPseudo->IsGeneratedContentContainerForAfter()) {
|
|
return {aElementOrPseudo->GetParent()->AsElement(),
|
|
PseudoStyleRequest::After()};
|
|
}
|
|
|
|
if (aElementOrPseudo->IsGeneratedContentContainerForMarker()) {
|
|
return {aElementOrPseudo->GetParent()->AsElement(),
|
|
PseudoStyleRequest::Marker()};
|
|
}
|
|
|
|
if (aElementOrPseudo->IsGeneratedContentContainerForBackdrop()) {
|
|
return {aElementOrPseudo->GetParent()->AsElement(),
|
|
PseudoStyleRequest::Backdrop()};
|
|
}
|
|
|
|
if (aElementOrPseudo->IsGeneratedContentContainerForCheckmark()) {
|
|
return {aElementOrPseudo->GetParent()->AsElement(),
|
|
PseudoStyleRequest(PseudoStyleType::Checkmark)};
|
|
}
|
|
|
|
if (aElementOrPseudo->IsGeneratedContentContainerForPickerIcon()) {
|
|
return {aElementOrPseudo->GetParent()->AsElement(),
|
|
PseudoStyleRequest(PseudoStyleType::PickerIcon)};
|
|
}
|
|
|
|
const PseudoStyleType type = aElementOrPseudo->GetPseudoElementType();
|
|
if (PseudoStyle::IsViewTransitionPseudoElement(type)) {
|
|
// Note: ::view-transition doesn't have a name, so we check if it has a name
|
|
// first.
|
|
nsAtom* name =
|
|
aElementOrPseudo->HasName()
|
|
? aElementOrPseudo->GetParsedAttr(nsGkAtoms::name)->GetAtomValue()
|
|
: nullptr;
|
|
return {aElementOrPseudo->GetOwnerDocument()->GetRootElement(),
|
|
PseudoStyleRequest(type, name)};
|
|
}
|
|
|
|
return {aElementOrPseudo, PseudoStyleRequest::NotPseudo()};
|
|
}
|
|
|
|
static bool IsPercentUnit(const CSSNumericValue& aValue) {
|
|
// TODO: this can use a bit more efficient check once it's available, see:
|
|
// https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-match
|
|
if (RefPtr<CSSUnitValue> asPercent =
|
|
aValue.To("percent"_ns, IgnoreErrors())) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// https://www.w3.org/TR/css-values-4/#time-value
|
|
static bool IsDurationUnit(const CSSNumericValue& aValue) {
|
|
// TODO: this can use a bit more efficient check once it's available, see:
|
|
// https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-match
|
|
if (RefPtr<CSSUnitValue> asMs = aValue.To("ms"_ns, IgnoreErrors())) {
|
|
return true;
|
|
}
|
|
if (RefPtr<CSSUnitValue> asSeconds = aValue.To("s"_ns, IgnoreErrors())) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// https://drafts.csswg.org/web-animations-2/#validate-a-cssnumberish-time
|
|
/* static */
|
|
bool AnimationUtils::ValidateCSSNumberishTime(const CSSNumberish& aValue,
|
|
bool aProgressBased,
|
|
ErrorResult& aRv) {
|
|
const bool isCSSNumericValue = aValue.IsCSSNumericValue();
|
|
|
|
// A CSSNumericValue was passed. This shouldn't be reachable from JS while
|
|
// typed-OM is disabled (the interface is pref-gated), but reject defensively.
|
|
if (isCSSNumericValue && !StaticPrefs::layout_css_typed_om_enabled()) {
|
|
aRv.ThrowTypeError("CSSNumericValue is not supported.");
|
|
return false;
|
|
}
|
|
|
|
if (aProgressBased) {
|
|
if (!isCSSNumericValue) {
|
|
aRv.ThrowTypeError(
|
|
"Setting time using absolute time values is not supported for "
|
|
"progress-based animations.");
|
|
return false;
|
|
}
|
|
|
|
CSSNumericValue& numeric = aValue.GetAsCSSNumericValue();
|
|
if (!IsPercentUnit(numeric)) {
|
|
aRv.ThrowTypeError(
|
|
"CSSNumericValue must be a percentage for progress-based "
|
|
"animations.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!aProgressBased && isCSSNumericValue) {
|
|
CSSNumericValue& numeric = aValue.GetAsCSSNumericValue();
|
|
if (!IsDurationUnit(numeric)) {
|
|
aRv.ThrowTypeError(
|
|
"CSSNumericValue must be a <time> for non-progress-based "
|
|
"animations.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* static */
|
|
void AnimationUtils::DoubleToCSSNumberish(double aMs, bool aProgressBased,
|
|
nsIGlobalObject* aGlobal,
|
|
OwningCSSNumberish& aRetVal) {
|
|
if (aProgressBased) {
|
|
const double progress =
|
|
aMs / static_cast<double>(PROGRESS_TIMELINE_DURATION_MILLISEC) * 100.0;
|
|
aRetVal.SetAsCSSNumericValue() = MakeCSSUnitValue(
|
|
aGlobal, StyleNumericType::Percent(), progress, "percent"_ns);
|
|
return;
|
|
}
|
|
aRetVal.SetAsDouble() = aMs;
|
|
}
|
|
|
|
/* static */
|
|
void AnimationUtils::DurationToCSSNumberish(
|
|
const Nullable<TimeDuration>& aTime, bool aProgressBased,
|
|
RTPCallerType aRTPCallerType, nsIGlobalObject* aGlobal,
|
|
Nullable<OwningCSSNumberish>& aRetVal) {
|
|
if (aTime.IsNull()) {
|
|
aRetVal.SetNull();
|
|
return;
|
|
}
|
|
const double ms = TimeDurationToDouble(aTime, aRTPCallerType).Value();
|
|
DoubleToCSSNumberish(ms, aProgressBased, aGlobal, aRetVal.SetValue());
|
|
}
|
|
|
|
/* static */
|
|
Nullable<TimeDuration> AnimationUtils::CSSNumberishToDuration(
|
|
const CSSNumberish& aValue, bool aProgressBased) {
|
|
if (aValue.IsDouble()) {
|
|
return Nullable<TimeDuration>(
|
|
TimeDuration::FromMilliseconds(aValue.GetAsDouble()));
|
|
}
|
|
|
|
CSSNumericValue& numeric = aValue.GetAsCSSNumericValue();
|
|
if (aProgressBased) {
|
|
RefPtr<CSSUnitValue> asPercent = numeric.To("percent"_ns, IgnoreErrors());
|
|
MOZ_ASSERT(asPercent, "caller should validate value");
|
|
const double ms = asPercent->Value() / 100.0 *
|
|
static_cast<double>(PROGRESS_TIMELINE_DURATION_MILLISEC);
|
|
return Nullable<TimeDuration>(TimeDuration::FromMilliseconds(ms));
|
|
}
|
|
|
|
if (RefPtr<CSSUnitValue> asMs = numeric.To("ms"_ns, IgnoreErrors())) {
|
|
return Nullable<TimeDuration>(
|
|
TimeDuration::FromMilliseconds(asMs->Value()));
|
|
}
|
|
RefPtr<CSSUnitValue> asSeconds = numeric.To("s"_ns, IgnoreErrors());
|
|
MOZ_ASSERT(asSeconds, "caller should validate value");
|
|
return Nullable<TimeDuration>(TimeDuration::FromSeconds(asSeconds->Value()));
|
|
}
|
|
|
|
} // namespace mozilla
|