/* 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 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 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 asMs = aValue.To("ms"_ns, IgnoreErrors())) { return true; } if (RefPtr 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