/* -*- 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/. */

#ifndef mozilla_EffectCompositor_h
#define mozilla_EffectCompositor_h

#include "mozilla/EnumeratedArray.h"
#include "mozilla/Maybe.h"
#include "mozilla/OwningNonNull.h"
#include "mozilla/PseudoElementHashEntry.h"
#include "mozilla/RefPtr.h"
#include "nsCSSPropertyID.h"
#include "nsCycleCollectionParticipant.h"
#include "nsDataHashtable.h"
#include "nsIStyleRuleProcessor.h"
#include "nsTArray.h"

class nsCSSPropertyIDSet;
class nsIFrame;
class nsIStyleRule;
class nsPresContext;
class nsStyleContext;

namespace mozilla {

class EffectSet;
class RestyleTracker;
struct AnimationPerformanceWarning;
struct NonOwningAnimationTarget;

namespace dom {
class Animation;
class Element;
}

class EffectCompositor
{
public:
  explicit EffectCompositor(nsPresContext* aPresContext)
    : mPresContext(aPresContext)
  {
    for (size_t i = 0; i < kCascadeLevelCount; i++) {
      CascadeLevel cascadeLevel = CascadeLevel(i);
      mRuleProcessors[cascadeLevel] =
        new AnimationStyleRuleProcessor(this, cascadeLevel);
    }
  }

  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(EffectCompositor)
  NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(EffectCompositor)

  void Disconnect() {
    mPresContext = nullptr;
  }

  // Animations can be applied at two different levels in the CSS cascade:
  enum class CascadeLevel {
    // The animations sheet (CSS animations, script-generated animations,
    // and CSS transitions that are no longer tied to CSS markup)
    Animations,
    // The transitions sheet (CSS transitions that are tied to CSS markup)
    Transitions
  };
  // We don't define this as part of CascadeLevel as then we'd have to add
  // explicit checks for the Count enum value everywhere CascadeLevel is used.
  static const size_t kCascadeLevelCount =
    static_cast<size_t>(CascadeLevel::Transitions) + 1;

  // NOTE: This can return null after Disconnect().
  nsPresContext* PresContext() const { return mPresContext; }

  enum class RestyleType {
    // Animation style has changed but the compositor is applying the same
    // change so we might be able to defer updating the main thread until it
    // becomes necessary.
    Throttled,
    // Animation style has changed and needs to be updated on the main thread.
    Standard,
    // Animation style has changed and needs to be updated on the main thread
    // as well as forcing animations on layers to be updated.
    // This is needed in cases such as when an animation becomes paused or has
    // its playback rate changed. In such cases, although the computed style
    // and refresh driver time might not change, we still need to ensure the
    // corresponding animations on layers are updated to reflect the new
    // configuration of the animation.
    Layer
  };

  // Notifies the compositor that the animation rule for the specified
  // (pseudo-)element at the specified cascade level needs to be updated.
  // The specified steps taken to update the animation rule depend on
  // |aRestyleType| whose values are described above.
  void RequestRestyle(dom::Element* aElement,
                      CSSPseudoElementType aPseudoType,
                      RestyleType aRestyleType,
                      CascadeLevel aCascadeLevel);

  // Schedule an animation restyle. This is called automatically by
  // RequestRestyle when necessary. However, it is exposed here since we also
  // need to perform this step when triggering transitions *without* also
  // invalidating the animation style rule (which RequestRestyle would do).
  void PostRestyleForAnimation(dom::Element* aElement,
                               CSSPseudoElementType aPseudoType,
                               CascadeLevel aCascadeLevel);

  // Posts an animation restyle for any elements whose animation style rule
  // is out of date but for which an animation restyle has not yet been
  // posted because updates on the main thread are throttled.
  void PostRestyleForThrottledAnimations();

  // Called when the style context on the specified (pseudo-) element might
  // have changed so that any context-sensitive values stored within
  // animation effects (e.g. em-based endpoints used in keyframe effects)
  // can be re-resolved to computed values.
  void UpdateEffectProperties(nsStyleContext* aStyleContext,
                              dom::Element* aElement,
                              CSSPseudoElementType aPseudoType);

  // Updates the animation rule stored on the EffectSet for the
  // specified (pseudo-)element for cascade level |aLevel|.
  // If the animation rule is not marked as needing an update,
  // no work is done.
  // |aStyleContext| is used for UpdateCascadingResults.
  // |aStyleContext| can be nullptr if style context, which is associated with
  // the primary frame of the specified (pseudo-)element, is the current style
  // context.
  // If we are resolving a new style context, we shoud pass the newly created
  // style context, otherwise we may use an old style context, it will result
  // unexpected cascading results.
  void MaybeUpdateAnimationRule(dom::Element* aElement,
                                CSSPseudoElementType aPseudoType,
                                CascadeLevel aCascadeLevel,
                                nsStyleContext *aStyleContext);

  // We need to pass the newly resolved style context as |aStyleContext| when
  // we call this function during resolving style context because this function
  // calls UpdateCascadingResults with a style context if necessary, at the
  // time, we end up using the previous style context if we don't pass the new
  // style context.
  // When we are not resolving style context, |aStyleContext| can be nullptr, we
  // will use a style context associated with the primary frame of the specified
  // (pseudo-)element.
  nsIStyleRule* GetAnimationRule(dom::Element* aElement,
                                 CSSPseudoElementType aPseudoType,
                                 CascadeLevel aCascadeLevel,
                                 nsStyleContext* aStyleContext);

  bool HasPendingStyleUpdates() const;
  bool HasThrottledStyleUpdates() const;

  // Tell the restyle tracker about all the animated styles that have
  // pending updates so that it can update the animation rule for these
  // elements.
  void AddStyleUpdatesTo(RestyleTracker& aTracker);

  nsIStyleRuleProcessor* RuleProcessor(CascadeLevel aCascadeLevel) const
  {
    return mRuleProcessors[aCascadeLevel];
  }

  static bool HasAnimationsForCompositor(const nsIFrame* aFrame,
                                         nsCSSPropertyID aProperty);

  static nsTArray<RefPtr<dom::Animation>>
  GetAnimationsForCompositor(const nsIFrame* aFrame,
                             nsCSSPropertyID aProperty);

  static void ClearIsRunningOnCompositor(const nsIFrame* aFrame,
                                         nsCSSPropertyID aProperty);

  // Update animation cascade results for the specified (pseudo-)element
  // but only if we have marked the cascade as needing an update due a
  // the change in the set of effects or a change in one of the effects'
  // "in effect" state.
  // |aStyleContext| may be nullptr in which case we will use the
  // nsStyleContext of the primary frame of the specified (pseudo-)element.
  //
  // This method does NOT detect if other styles that apply above the
  // animation level of the cascade have changed.
  static void
  MaybeUpdateCascadeResults(dom::Element* aElement,
                            CSSPseudoElementType aPseudoType,
                            nsStyleContext* aStyleContext);

  // Update the mPropertiesWithImportantRules and
  // mPropertiesForAnimationsLevel members of the corresponding EffectSet.
  //
  // This can be expensive so we should only call it if styles that apply
  // above the animation level of the cascade might have changed. For all
  // other cases we should call MaybeUpdateCascadeResults.
  static void
  UpdateCascadeResults(dom::Element* aElement,
                       CSSPseudoElementType aPseudoType,
                       nsStyleContext* aStyleContext);

  // Helper to fetch the corresponding element and pseudo-type from a frame.
  //
  // For frames corresponding to pseudo-elements, the returned element is the
  // element on which we store the animations (i.e. the EffectSet and/or
  // AnimationCollection), *not* the generated content.
  //
  // Returns an empty result when a suitable element cannot be found including
  // when the frame represents a pseudo-element on which we do not support
  // animations.
  static Maybe<NonOwningAnimationTarget>
  GetAnimationElementAndPseudoForFrame(const nsIFrame* aFrame);

  // Associates a performance warning with effects on |aFrame| that animates
  // |aProperty|.
  static void SetPerformanceWarning(
    const nsIFrame* aFrame,
    nsCSSPropertyID aProperty,
    const AnimationPerformanceWarning& aWarning);

private:
  ~EffectCompositor() = default;

  // Rebuilds the animation rule corresponding to |aCascadeLevel| on the
  // EffectSet associated with the specified (pseudo-)element.
  static void ComposeAnimationRule(dom::Element* aElement,
                                   CSSPseudoElementType aPseudoType,
                                   CascadeLevel aCascadeLevel,
                                   TimeStamp aRefreshTime);

  static dom::Element* GetElementToRestyle(dom::Element* aElement,
                                           CSSPseudoElementType
                                             aPseudoType);

  // Get the properties in |aEffectSet| that we are able to animate on the
  // compositor but which are also specified at a higher level in the cascade
  // than the animations level in |aStyleContext|.
  static void
  GetOverriddenProperties(nsStyleContext* aStyleContext,
                          EffectSet& aEffectSet,
                          nsCSSPropertyIDSet& aPropertiesOverridden);

  static void
  UpdateCascadeResults(EffectSet& aEffectSet,
                       dom::Element* aElement,
                       CSSPseudoElementType aPseudoType,
                       nsStyleContext* aStyleContext);

  static nsPresContext* GetPresContext(dom::Element* aElement);

  nsPresContext* mPresContext;

  // Elements with a pending animation restyle. The associated bool value is
  // true if a pending animation restyle has also been dispatched. For
  // animations that can be throttled, we will add an entry to the hashtable to
  // indicate that the style rule on the element is out of date but without
  // posting a restyle to update it.
  EnumeratedArray<CascadeLevel, CascadeLevel(kCascadeLevelCount),
                  nsDataHashtable<PseudoElementHashEntry, bool>>
                    mElementsToRestyle;

  class AnimationStyleRuleProcessor final : public nsIStyleRuleProcessor
  {
  public:
    AnimationStyleRuleProcessor(EffectCompositor* aCompositor,
                                CascadeLevel aCascadeLevel)
      : mCompositor(aCompositor)
      , mCascadeLevel(aCascadeLevel)
    {
      MOZ_ASSERT(aCompositor);
    }

    NS_DECL_ISUPPORTS

    // nsIStyleRuleProcessor (parts)
    nsRestyleHint HasStateDependentStyle(
                        StateRuleProcessorData* aData) override;
    nsRestyleHint HasStateDependentStyle(
                        PseudoElementStateRuleProcessorData* aData) override;
    bool HasDocumentStateDependentStyle(StateRuleProcessorData* aData) override;
    nsRestyleHint HasAttributeDependentStyle(
                        AttributeRuleProcessorData* aData,
                        RestyleHintData& aRestyleHintDataResult) override;
    bool MediumFeaturesChanged(nsPresContext* aPresContext) override;
    void RulesMatching(ElementRuleProcessorData* aData) override;
    void RulesMatching(PseudoElementRuleProcessorData* aData) override;
    void RulesMatching(AnonBoxRuleProcessorData* aData) override;
#ifdef MOZ_XUL
    void RulesMatching(XULTreeRuleProcessorData* aData) override;
#endif
    size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf)
      const MOZ_MUST_OVERRIDE override;
    size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)
      const MOZ_MUST_OVERRIDE override;

  private:
    ~AnimationStyleRuleProcessor() = default;

    EffectCompositor* mCompositor;
    CascadeLevel      mCascadeLevel;
  };

  EnumeratedArray<CascadeLevel, CascadeLevel(kCascadeLevelCount),
                  OwningNonNull<AnimationStyleRuleProcessor>>
                    mRuleProcessors;
};

} // namespace mozilla

#endif // mozilla_EffectCompositor_h