/* -*- 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_ServoStyleSet_h
#define mozilla_ServoStyleSet_h

#include "mozilla/EffectCompositor.h"
#include "mozilla/EnumeratedArray.h"
#include "mozilla/EventStates.h"
#include "mozilla/PostTraversalTask.h"
#include "mozilla/ServoBindingTypes.h"
#include "mozilla/ServoElementSnapshot.h"
#include "mozilla/ServoUtils.h"
#include "mozilla/StyleSheetInlines.h"
#include "mozilla/SheetType.h"
#include "mozilla/UniquePtr.h"
#include "MainThreadUtils.h"
#include "nsCSSPseudoElements.h"
#include "nsCSSAnonBoxes.h"
#include "nsChangeHint.h"
#include "nsIAtom.h"
#include "nsTArray.h"

namespace mozilla {
namespace dom {
class Element;
} // namespace dom
class CSSStyleSheet;
class ServoRestyleManager;
class ServoStyleSheet;
struct Keyframe;
class ServoElementSnapshotTable;
} // namespace mozilla
class nsCSSCounterStyleRule;
class nsIContent;
class nsIDocument;
class nsStyleContext;
class nsPresContext;
struct nsTimingFunction;
struct RawServoRuleNode;
struct TreeMatchContext;

namespace mozilla {

/**
 * A few flags used to track which kind of stylist state we may need to
 * update.
 */
enum class StylistState : uint8_t {
  /** The stylist is not dirty, we should do nothing */
  NotDirty = 0,

  /** The style sheets have changed, so we need to update the style data. */
  StyleSheetsDirty = 1 << 0,

  /**
   * All style data is dirty and both style sheet data and default computed
   * values need to be recomputed.
   */
  FullyDirty = 1 << 1,
};

MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(StylistState)

/**
 * The set of style sheets that apply to a document, backed by a Servo
 * Stylist.  A ServoStyleSet contains ServoStyleSheets.
 */
class ServoStyleSet
{
  friend class ServoRestyleManager;
  typedef ServoElementSnapshotTable SnapshotTable;

public:
  class AutoAllowStaleStyles
  {
  public:
    explicit AutoAllowStaleStyles(ServoStyleSet* aStyleSet)
      : mStyleSet(aStyleSet)
    {
      if (mStyleSet) {
        MOZ_ASSERT(!mStyleSet->mAllowResolveStaleStyles);
        mStyleSet->mAllowResolveStaleStyles = true;
      }
    }

    ~AutoAllowStaleStyles()
    {
      if (mStyleSet) {
        mStyleSet->mAllowResolveStaleStyles = false;
      }
    }

  private:
    ServoStyleSet* mStyleSet;
  };

  static bool IsInServoTraversal()
  {
    // The callers of this function are generally main-thread-only _except_
    // for potentially running during the Servo traversal, in which case they may
    // take special paths that avoid writing to caches and the like. In order
    // to allow those callers to branch efficiently without checking TLS, we
    // maintain this static boolean. However, the danger is that those callers
    // are generally unprepared to deal with non-Servo-but-also-non-main-thread
    // callers, and are likely to take the main-thread codepath if this function
    // returns false. So we assert against other non-main-thread callers here.
    MOZ_ASSERT(sInServoTraversal || NS_IsMainThread());
    return sInServoTraversal;
  }

  static ServoStyleSet* Current()
  {
    return sInServoTraversal;
  }

  ServoStyleSet();
  ~ServoStyleSet();

  void Init(nsPresContext* aPresContext);
  void BeginShutdown();
  void Shutdown();

  void RecordStyleSheetChange(mozilla::ServoStyleSheet*, StyleSheet::ChangeType);

  void RecordShadowStyleChange(mozilla::dom::ShadowRoot* aShadowRoot) {
    // FIXME(emilio): When we properly support shadow dom we'll need to do
    // better.
    ForceAllStyleDirty();
  }

  bool StyleSheetsHaveChanged() const
  {
    return StylistNeedsUpdate();
  }

  bool MediumFeaturesChanged() const;

  void InvalidateStyleForCSSRuleChanges();

  size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
  const RawServoStyleSet* RawSet() const {
    return mRawSet.get();
  }

  bool GetAuthorStyleDisabled() const;
  nsresult SetAuthorStyleDisabled(bool aStyleDisabled);

  void BeginUpdate();
  nsresult EndUpdate();

  already_AddRefed<nsStyleContext>
  ResolveStyleFor(dom::Element* aElement,
                  nsStyleContext* aParentContext,
                  LazyComputeBehavior aMayCompute);

  // Get a style context for a text node (which no rules will match).
  //
  // The returned style context will have nsCSSAnonBoxes::mozText as its pseudo.
  //
  // (Perhaps mozText should go away and we shouldn't even create style
  // contexts for such content nodes, when text-combine-upright is not
  // present.  However, not doing any rule matching for them is a first step.)
  already_AddRefed<nsStyleContext>
  ResolveStyleForText(nsIContent* aTextNode,
                      nsStyleContext* aParentContext);

  // Get a style context for a first-letter continuation (which no rules will
  // match).
  //
  // The returned style context will have
  // nsCSSAnonBoxes::firstLetterContinuation as its pseudo.
  //
  // (Perhaps nsCSSAnonBoxes::firstLetterContinuation should go away and we
  // shouldn't even create style contexts for such frames.  However, not doing
  // any rule matching for them is a first step.  And right now we do use this
  // style context for some things)
  already_AddRefed<nsStyleContext>
  ResolveStyleForFirstLetterContinuation(nsStyleContext* aParentContext);

  // Get a style context for a placeholder frame (which no rules will match).
  //
  // The returned style context will have nsCSSAnonBoxes::oofPlaceholder as
  // its pseudo.
  //
  // (Perhaps nsCSSAnonBoxes::oofPaceholder should go away and we shouldn't even
  // create style contexts for placeholders.  However, not doing any rule
  // matching for them is a first step.)
  already_AddRefed<nsStyleContext>
  ResolveStyleForPlaceholder();

  // Get a style context for a pseudo-element.  aParentElement must be
  // non-null.  aPseudoID is the CSSPseudoElementType for the
  // pseudo-element.  aPseudoElement must be non-null if the pseudo-element
  // type is one that allows user action pseudo-classes after it or allows
  // style attributes; otherwise, it is ignored.
  already_AddRefed<nsStyleContext>
  ResolvePseudoElementStyle(dom::Element* aOriginatingElement,
                            CSSPseudoElementType aType,
                            nsStyleContext* aParentContext,
                            dom::Element* aPseudoElement);

  // Resolves style for a (possibly-pseudo) Element without assuming that the
  // style has been resolved, and without worrying about setting the style
  // context up to live in the style context tree (a null parent is used).
  // |aPeudoTag| and |aPseudoType| must match.
  already_AddRefed<nsStyleContext>
  ResolveTransientStyle(dom::Element* aElement,
                        nsIAtom* aPseudoTag,
                        CSSPseudoElementType aPseudoType,
                        StyleRuleInclusion aRules =
                          StyleRuleInclusion::All);

  // Similar to ResolveTransientStyle() but returns ServoComputedValues.
  // Unlike ResolveServoStyle() this function calls PreTraverseSync().
  already_AddRefed<ServoComputedValues>
  ResolveTransientServoStyle(dom::Element* aElement,
                             CSSPseudoElementType aPseudoTag,
                             StyleRuleInclusion aRules =
                               StyleRuleInclusion::All);

  // Get a style context for an anonymous box.  aPseudoTag is the pseudo-tag to
  // use and must be non-null.  It must be an anon box, and must be one that
  // inherits style from the given aParentContext.
  already_AddRefed<nsStyleContext>
  ResolveInheritingAnonymousBoxStyle(nsIAtom* aPseudoTag,
                                     nsStyleContext* aParentContext);

  // Get a style context for an anonymous box that does not inherit style from
  // anything.  aPseudoTag is the pseudo-tag to use and must be non-null.  It
  // must be an anon box, and must be a non-inheriting one.
  already_AddRefed<nsStyleContext>
  ResolveNonInheritingAnonymousBoxStyle(nsIAtom* aPseudoTag);

  // manage the set of style sheets in the style set
  nsresult AppendStyleSheet(SheetType aType, ServoStyleSheet* aSheet);
  nsresult PrependStyleSheet(SheetType aType, ServoStyleSheet* aSheet);
  nsresult RemoveStyleSheet(SheetType aType, ServoStyleSheet* aSheet);
  nsresult ReplaceSheets(SheetType aType,
                         const nsTArray<RefPtr<ServoStyleSheet>>& aNewSheets);
  nsresult InsertStyleSheetBefore(SheetType aType,
                                  ServoStyleSheet* aNewSheet,
                                  ServoStyleSheet* aReferenceSheet);

  int32_t SheetCount(SheetType aType) const;
  ServoStyleSheet* StyleSheetAt(SheetType aType, int32_t aIndex) const;

  nsresult RemoveDocStyleSheet(ServoStyleSheet* aSheet);
  nsresult AddDocStyleSheet(ServoStyleSheet* aSheet, nsIDocument* aDocument);

  // check whether there is ::before/::after style for an element
  already_AddRefed<nsStyleContext>
  ProbePseudoElementStyle(dom::Element* aOriginatingElement,
                          mozilla::CSSPseudoElementType aType,
                          nsStyleContext* aParentContext,
                          dom::Element* aPseudoElement = nullptr);

  // Test if style is dependent on content state
  nsRestyleHint HasStateDependentStyle(dom::Element* aElement,
                                       EventStates aStateMask);
  nsRestyleHint HasStateDependentStyle(
    dom::Element* aElement, mozilla::CSSPseudoElementType aPseudoType,
    dom::Element* aPseudoElement, EventStates aStateMask);

  /**
   * Performs a Servo traversal to compute style for all dirty nodes in the
   * document.
   *
   * This will traverse all of the document's style roots (that is, its document
   * element, and the roots of the document-level native anonymous content).
   *
   * |aRestyleBehavior| should be `Normal` or `ForCSSRuleChanges`.
   * We need to specify |ForCSSRuleChanges| to try to update all CSS animations
   * when we call this function due to CSS rule changes since @keyframes rules
   * may have changed.
   *
   * Returns true if a post-traversal is required.
   */
  bool StyleDocument(TraversalRestyleBehavior aRestyleBehavior);

  /**
   * Performs a Servo animation-only traversal to compute style for all nodes
   * with the animation-only dirty bit in the document.
   *
   * This will traverse all of the document's style roots (that is, its document
   * element, and the roots of the document-level native anonymous content).
   */
  bool StyleDocumentForAnimationOnly();

  /**
   * Eagerly styles a subtree of unstyled nodes that was just appended to the
   * tree. This is used in situations where we need the style immediately and
   * cannot wait for a future batch restyle.
   */
  void StyleNewSubtree(dom::Element* aRoot);

  /**
   * Like the above, but skips the root node, and only styles unstyled children.
   * When potentially appending multiple children, it's preferable to call
   * StyleNewChildren on the node rather than making multiple calls to
   * StyleNewSubtree on each child, since it allows for more parallelism.
   */
  void StyleNewChildren(dom::Element* aParent);

  /**
   * Like StyleNewSubtree, but in response to a request to reconstruct frames
   * for the given subtree, and so works on elements that already have
   * styles.  This will leave the subtree in a state just like after an initial
   * styling, i.e. with new styles, no change hints, and with the dirty
   * descendants bits cleared.  No comparison of old and new styles is done,
   * so no change hints will be processed.
   */
  void StyleSubtreeForReconstruct(dom::Element* aRoot);

  /**
   * Records that the contents of style sheets have changed since the last
   * restyle.  Calling this will ensure that the Stylist rebuilds its
   * selector maps.
   */
  void ForceAllStyleDirty();

  /**
   * Helper for correctly calling RebuildStylist without paying the cost of an
   * extra function call in the common no-rebuild-needed case.
   */
  void UpdateStylistIfNeeded()
  {
    if (StylistNeedsUpdate()) {
      UpdateStylist();
    }
  }

#ifdef DEBUG
  void AssertTreeIsClean();
#else
  void AssertTreeIsClean() {}
#endif

  /**
   * Clears the style data, both style sheet data and cached non-inheriting
   * style contexts, and marks the stylist as needing an unconditional full
   * rebuild, including a device reset.
   */
  void ClearDataAndMarkDeviceDirty();

  /**
   * Notifies the Servo stylesheet that the document's compatibility mode has changed.
   */
  void CompatibilityModeChanged();

  /**
   * Resolve style for the given element, and return it as a
   * ServoComputedValues, not an nsStyleContext.
   */
  already_AddRefed<ServoComputedValues> ResolveServoStyle(dom::Element* aElement);

  bool GetKeyframesForName(const nsString& aName,
                           const nsTimingFunction& aTimingFunction,
                           const ServoComputedValues* aComputedValues,
                           nsTArray<Keyframe>& aKeyframes);

  nsTArray<ComputedKeyframeValues>
  GetComputedKeyframeValuesFor(const nsTArray<Keyframe>& aKeyframes,
                               dom::Element* aElement,
                               ServoComputedValuesBorrowed aComputedValues);

  void
  GetAnimationValues(RawServoDeclarationBlock* aDeclarations,
                     dom::Element* aElement,
                     ServoComputedValuesBorrowed aComputedValues,
                     nsTArray<RefPtr<RawServoAnimationValue>>& aAnimationValues);

  bool AppendFontFaceRules(nsTArray<nsFontFaceRuleContainer>& aArray);

  nsCSSCounterStyleRule* CounterStyleRuleForName(nsIAtom* aName);

  already_AddRefed<ServoComputedValues>
  GetBaseComputedValuesForElement(dom::Element* aElement,
                                  CSSPseudoElementType aPseudoType);

  /**
   * Resolve style for a given declaration block with/without the parent style.
   * If the parent style is not specified, the document default computed values
   * is used.
   */
  already_AddRefed<ServoComputedValues>
  ResolveForDeclarations(ServoComputedValuesBorrowedOrNull aParentOrNull,
                         RawServoDeclarationBlockBorrowed aDeclarations);

  already_AddRefed<RawServoAnimationValue>
  ComputeAnimationValue(dom::Element* aElement,
                        RawServoDeclarationBlock* aDeclaration,
                        ServoComputedValuesBorrowed aComputedValues);

  void AppendTask(PostTraversalTask aTask)
  {
    MOZ_ASSERT(IsInServoTraversal());

    // We currently only use PostTraversalTasks while the Servo font metrics
    // mutex is locked.  If we need to use them in other situations during
    // a traversal, we should assert that we've taken appropriate
    // synchronization measures.
    AssertIsMainThreadOrServoFontMetricsLocked();

    mPostTraversalTasks.AppendElement(aTask);
  }

  // Returns true if a restyle of the document is needed due to cloning
  // sheet inners.
  bool EnsureUniqueInnerOnCSSSheets();

  // Called by StyleSheet::EnsureUniqueInner to let us know it cloned
  // its inner.
  void SetNeedsRestyleAfterEnsureUniqueInner() {
    mNeedsRestyleAfterEnsureUniqueInner = true;
  }

private:
  // On construction, sets sInServoTraversal to the given ServoStyleSet.
  // On destruction, clears sInServoTraversal and calls RunPostTraversalTasks.
  class MOZ_STACK_CLASS AutoSetInServoTraversal
  {
  public:
    explicit AutoSetInServoTraversal(ServoStyleSet* aSet)
      : mSet(aSet)
    {
      MOZ_ASSERT(!sInServoTraversal);
      MOZ_ASSERT(aSet);
      sInServoTraversal = aSet;
    }

    ~AutoSetInServoTraversal()
    {
      MOZ_ASSERT(sInServoTraversal);
      sInServoTraversal = nullptr;
      mSet->RunPostTraversalTasks();
    }

  private:
    ServoStyleSet* mSet;
  };

  already_AddRefed<nsStyleContext> GetContext(already_AddRefed<ServoComputedValues>,
                                              nsStyleContext* aParentContext,
                                              nsIAtom* aPseudoTag,
                                              CSSPseudoElementType aPseudoType,
                                              dom::Element* aElementForAnimation);

  already_AddRefed<nsStyleContext> GetContext(nsIContent* aContent,
                                              nsStyleContext* aParentContext,
                                              nsIAtom* aPseudoTag,
                                              CSSPseudoElementType aPseudoType,
                                              LazyComputeBehavior aMayCompute);

  /**
   * Rebuild the style data. This will force a stylesheet flush, and also
   * recompute the default computed styles.
   */
  void RebuildData();

  /**
   * Gets the pending snapshots to handle from the restyle manager.
   */
  const SnapshotTable& Snapshots();

  /**
   * Resolve all ServoDeclarationBlocks attached to mapped
   * presentation attributes cached on the document.
   *
   * Call this before jumping into Servo's style system.
   */
  void ResolveMappedAttrDeclarationBlocks();

  /**
   * Perform all lazy operations required before traversing
   * a subtree.
   *
   * Returns whether a post-traversal is required.
   */
  bool PrepareAndTraverseSubtree(RawGeckoElementBorrowed aRoot,
                                 TraversalRootBehavior aRootBehavior,
                                 TraversalRestyleBehavior aRestyleBehavior);

  /**
   * Clear our cached mNonInheritingStyleContexts.
   *
   * We do this when we want to make sure those style contexts won't live too
   * long (e.g. when rebuilding all style data or when shutting down the style
   * set).
   */
  void ClearNonInheritingStyleContexts();

  /**
   * Perform processes that we should do before traversing.
   *
   * When aRoot is null, the entire document is pre-traversed.  Otherwise,
   * only the subtree rooted at aRoot is pre-traversed.
   */
  void PreTraverse(dom::Element* aRoot = nullptr,
                   EffectCompositor::AnimationRestyleType =
                     EffectCompositor::AnimationRestyleType::Throttled);
  // Subset of the pre-traverse steps that involve syncing up data
  void PreTraverseSync();

  /**
   * Note that the stylist needs a style flush due to style sheet changes.
   */
  void SetStylistStyleSheetsDirty()
  {
    mStylistState |= StylistState::StyleSheetsDirty;
  }

  bool StylistNeedsUpdate() const
  {
    return mStylistState != StylistState::NotDirty;
  }

  /**
   * Update the stylist as needed to ensure style data is up-to-date.
   *
   * This should only be called if StylistNeedsUpdate returns true.
   */
  void UpdateStylist();

  already_AddRefed<ServoComputedValues>
    ResolveStyleLazily(dom::Element* aElement,
                       CSSPseudoElementType aPseudoType,
                       StyleRuleInclusion aRules =
                         StyleRuleInclusion::All);

  void RunPostTraversalTasks();

  void PrependSheetOfType(SheetType aType,
                          ServoStyleSheet* aSheet);

  void AppendSheetOfType(SheetType aType,
                         ServoStyleSheet* aSheet);

  void InsertSheetOfType(SheetType aType,
                         ServoStyleSheet* aSheet,
                         ServoStyleSheet* aBeforeSheet);

  void RemoveSheetOfType(SheetType aType,
                         ServoStyleSheet* aSheet);

  nsPresContext* mPresContext;
  UniquePtr<RawServoStyleSet> mRawSet;
  EnumeratedArray<SheetType, SheetType::Count,
                  nsTArray<RefPtr<ServoStyleSheet>>> mSheets;
  bool mAllowResolveStaleStyles;
  bool mAuthorStyleDisabled;
  StylistState mStylistState;
  uint64_t mUserFontSetUpdateGeneration;

  bool mNeedsRestyleAfterEnsureUniqueInner;

  // Stores pointers to our cached style contexts for non-inheriting anonymous
  // boxes.
  EnumeratedArray<nsCSSAnonBoxes::NonInheriting,
                  nsCSSAnonBoxes::NonInheriting::_Count,
                  RefPtr<nsStyleContext>> mNonInheritingStyleContexts;

  // Tasks to perform after a traversal, back on the main thread.
  //
  // These are similar to Servo's SequentialTasks, except that they are
  // posted by C++ code running on style worker threads.
  nsTArray<PostTraversalTask> mPostTraversalTasks;

  static ServoStyleSet* sInServoTraversal;
};

} // namespace mozilla

#endif // mozilla_ServoStyleSet_h
