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

/* the interface (to internal code) for retrieving computed style data */

#include "nsStyleContext.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Maybe.h"

#include "nsCSSAnonBoxes.h"
#include "nsCSSPseudoElements.h"
#include "nsFontMetrics.h"
#include "nsStyleConsts.h"
#include "nsStyleStruct.h"
#include "nsStyleStructInlines.h"
#include "nsString.h"
#include "nsPresContext.h"
#include "nsIStyleRule.h"

#include "nsCOMPtr.h"
#include "nsStyleSet.h"
#include "nsIPresShell.h"

#include "nsRuleNode.h"
#include "GeckoProfiler.h"
#include "nsIDocument.h"
#include "nsPrintfCString.h"
#include "RubyUtils.h"
#include "mozilla/Preferences.h"
#include "mozilla/ArenaObjectID.h"
#include "mozilla/StyleSetHandle.h"
#include "mozilla/StyleSetHandleInlines.h"
#include "mozilla/GeckoStyleContext.h"
#include "mozilla/ServoStyleContext.h"
#include "nsStyleContextInlines.h"

#include "mozilla/ReflowInput.h"
#include "nsLayoutUtils.h"
#include "nsCoord.h"

// Ensure the binding function declarations in nsStyleContext.h matches
// those in ServoBindings.h.
#include "mozilla/ServoBindings.h"

using namespace mozilla;

//----------------------------------------------------------------------

#ifdef DEBUG

// Check that the style struct IDs are in the same order as they are
// in nsStyleStructList.h, since when we set up the IDs, we include
// the inherited and reset structs spearately from nsStyleStructList.h
enum DebugStyleStruct {
#define STYLE_STRUCT(name, checkdata_cb) eDebugStyleStruct_##name,
#include "nsStyleStructList.h"
#undef STYLE_STRUCT
};

#define STYLE_STRUCT(name, checkdata_cb) \
  static_assert(static_cast<int>(eDebugStyleStruct_##name) == \
                  static_cast<int>(eStyleStruct_##name), \
                "Style struct IDs are not declared in order?");
#include "nsStyleStructList.h"
#undef STYLE_STRUCT

const uint32_t nsStyleContext::sDependencyTable[] = {
#define STYLE_STRUCT(name, checkdata_cb)
#define STYLE_STRUCT_DEP(dep) NS_STYLE_INHERIT_BIT(dep) |
#define STYLE_STRUCT_END() 0,
#include "nsStyleStructList.h"
#undef STYLE_STRUCT
#undef STYLE_STRUCT_DEP
#undef STYLE_STRUCT_END
};

#endif

nsStyleContext::nsStyleContext(nsAtom* aPseudoTag,
                               CSSPseudoElementType aPseudoType)
  : mPseudoTag(aPseudoTag)
  , mBits(((uint64_t)aPseudoType) << NS_STYLE_CONTEXT_TYPE_SHIFT)
{
  // This check has to be done "backward", because if it were written the
  // more natural way it wouldn't fail even when it needed to.
  static_assert((UINT64_MAX >> NS_STYLE_CONTEXT_TYPE_SHIFT) >=
                 static_cast<CSSPseudoElementTypeBase>(
                   CSSPseudoElementType::MAX),
                "pseudo element bits no longer fit in a uint64_t");

#define eStyleStruct_LastItem (nsStyleStructID_Length - 1)
  static_assert(NS_STYLE_INHERIT_MASK & NS_STYLE_INHERIT_BIT(LastItem),
                "NS_STYLE_INHERIT_MASK must be bigger, and other bits shifted");
#undef eStyleStruct_LastItem

#ifdef DEBUG
  static_assert(MOZ_ARRAY_LENGTH(nsStyleContext::sDependencyTable)
                  == nsStyleStructID_Length,
                "Number of items in dependency table doesn't match IDs");
#endif
}

// TODO(stylo-everywhere): Remove aSamePointerStructs.
nsChangeHint
nsStyleContext::CalcStyleDifference(nsStyleContext* aNewContext,
                                    uint32_t* aEqualStructs,
                                    uint32_t* aSamePointerStructs,
                                    bool aIgnoreVariables)
{
  AUTO_PROFILER_LABEL("nsStyleContext::CalcStyleDifference", CSS);

  static_assert(nsStyleStructID_Length <= 32,
                "aEqualStructs is not big enough");

  MOZ_ASSERT(!aIgnoreVariables || IsServo(),
             "aIgnoreVariables must be false for Gecko contexts");

  *aEqualStructs = 0;
  *aSamePointerStructs = 0;

  nsChangeHint hint = nsChangeHint(0);
  NS_ENSURE_TRUE(aNewContext, hint);
  // We must always ensure that we populate the structs on the new style
  // context that are filled in on the old context, so that if we get
  // two style changes in succession, the second of which causes a real
  // style change, the PeekStyleData doesn't return null (implying that
  // nobody ever looked at that struct's data).  In other words, we
  // can't skip later structs if we get a big change up front, because
  // we could later get a small change in one of those structs that we
  // don't want to miss.

  DebugOnly<uint32_t> structsFound = 0;

  if (IsGecko()) {
    // CalcStyleDifference is always called on the main thread for Gecko
    // style contexts.  This assertion helps the heap write static analysis.
    MOZ_ASSERT(NS_IsMainThread());

    // FIXME(heycam): We should just do the comparison in
    // nsStyleVariables::CalcDifference, returning NeutralChange if there are
    // any Variables differences.
    const nsStyleVariables* thisVariables = PeekStyleVariables();
    if (thisVariables) {
      structsFound |= NS_STYLE_INHERIT_BIT(Variables);
      const nsStyleVariables* otherVariables = aNewContext->StyleVariables();
      if (thisVariables == otherVariables) {
        *aSamePointerStructs |= NS_STYLE_INHERIT_BIT(Variables);
        *aEqualStructs |= NS_STYLE_INHERIT_BIT(Variables);
      } else if (thisVariables->mVariables == otherVariables->mVariables) {
        *aEqualStructs |= NS_STYLE_INHERIT_BIT(Variables);
      }
    } else {
      *aEqualStructs |= NS_STYLE_INHERIT_BIT(Variables);
    }
  } else {
    if (aIgnoreVariables ||
        Servo_ComputedValues_EqualCustomProperties(
          AsServo()->ComputedData(),
          aNewContext->ComputedData())) {
      *aEqualStructs |= NS_STYLE_INHERIT_BIT(Variables);
    }
  }

  DebugOnly<int> styleStructCount = 1;  // count Variables already

  // Servo's optimization to stop the cascade when there are no style changes
  // that children need to be recascade for relies on comparing all of the
  // structs, not just those that are returned from PeekStyleData, although
  // if PeekStyleData does return null we could avoid to accumulate any change
  // hints for those structs.
  //
  // FIXME(emilio): Reintroduce that optimization either for all kind of structs
  // after bug 1368290 with a weak parent pointer from text, or just for reset
  // structs.
  //
  // For Gecko structs, we just defer to PeekStyleXXX.  But for Servo structs,
  // we need to use the aRelevantStructs bitfield passed in to determine
  // whether to return a struct or not, since this->mBits might not yet
  // be correct (due to not calling ResolveSameStructsAs on it yet).
#define PEEK(struct_)                                                         \
  (IsGecko()                                                                  \
   ? PeekStyle##struct_()                                                     \
   : AsServo()->ComputedData()->GetStyle##struct_())                          \

#define EXPAND(...) __VA_ARGS__
#define DO_STRUCT_DIFFERENCE_WITH_ARGS(struct_, extra_args_)                  \
  PR_BEGIN_MACRO                                                              \
    const nsStyle##struct_* this##struct_ = PEEK(struct_);                    \
    if (this##struct_) {                                                      \
      structsFound |= NS_STYLE_INHERIT_BIT(struct_);                          \
                                                                              \
      const nsStyle##struct_* other##struct_ =                                \
        aNewContext->ThreadsafeStyle##struct_();                              \
      if (this##struct_ == other##struct_) {                                  \
        /* The very same struct, so we know that there will be no */          \
        /* differences.                                           */          \
        *aEqualStructs |= NS_STYLE_INHERIT_BIT(struct_);                      \
      } else {                                                                \
        nsChangeHint difference =                                             \
          this##struct_->CalcDifference(*other##struct_ EXPAND extra_args_);  \
        hint |= difference;                                                   \
        if (!difference) {                                                    \
          *aEqualStructs |= NS_STYLE_INHERIT_BIT(struct_);                    \
        }                                                                     \
      }                                                                       \
    } else {                                                                  \
      *aEqualStructs |= NS_STYLE_INHERIT_BIT(struct_);                        \
    }                                                                         \
    styleStructCount++;                                                       \
  PR_END_MACRO
#define DO_STRUCT_DIFFERENCE(struct_) \
  DO_STRUCT_DIFFERENCE_WITH_ARGS(struct_, ())

  // FIXME: The order of these DO_STRUCT_DIFFERENCE calls is no longer
  // significant.  With a small amount of effort, we could replace them with a
  // #include "nsStyleStructList.h".
  DO_STRUCT_DIFFERENCE(Display);
  DO_STRUCT_DIFFERENCE(XUL);
  DO_STRUCT_DIFFERENCE(Column);
  DO_STRUCT_DIFFERENCE(Content);
  DO_STRUCT_DIFFERENCE(UserInterface);
  DO_STRUCT_DIFFERENCE(Visibility);
  DO_STRUCT_DIFFERENCE(Outline);
  DO_STRUCT_DIFFERENCE(TableBorder);
  DO_STRUCT_DIFFERENCE(Table);
  DO_STRUCT_DIFFERENCE(UIReset);
  DO_STRUCT_DIFFERENCE(Text);
  DO_STRUCT_DIFFERENCE_WITH_ARGS(List, (, PEEK(Display)));
  DO_STRUCT_DIFFERENCE(SVGReset);
  DO_STRUCT_DIFFERENCE(SVG);
  DO_STRUCT_DIFFERENCE_WITH_ARGS(Position, (, PEEK(Visibility)));
  DO_STRUCT_DIFFERENCE(Font);
  DO_STRUCT_DIFFERENCE(Margin);
  DO_STRUCT_DIFFERENCE(Padding);
  DO_STRUCT_DIFFERENCE(Border);
  DO_STRUCT_DIFFERENCE(TextReset);
  DO_STRUCT_DIFFERENCE(Effects);
  DO_STRUCT_DIFFERENCE(Background);
  DO_STRUCT_DIFFERENCE(Color);

#undef DO_STRUCT_DIFFERENCE
#undef DO_STRUCT_DIFFERENCE_WITH_ARGS
#undef EXPAND

  MOZ_ASSERT(styleStructCount == nsStyleStructID_Length,
             "missing a call to DO_STRUCT_DIFFERENCE");

#ifdef DEBUG
  #define STYLE_STRUCT_LIST_IGNORE_VARIABLES
  #define STYLE_STRUCT(name_, callback_)                                      \
    MOZ_ASSERT(!!(structsFound & NS_STYLE_INHERIT_BIT(name_)) ==              \
               (PEEK(name_) != nullptr),                                      \
               "PeekStyleData results must not change in the middle of "      \
               "difference calculation.");
  #include "nsStyleStructList.h"
  #undef STYLE_STRUCT
  #undef STYLE_STRUCT_LIST_IGNORE_VARIABLES
#endif

  // We check for struct pointer equality here rather than as part of the
  // DO_STRUCT_DIFFERENCE calls, since those calls can result in structs
  // we previously examined and found to be null on this style context
  // getting computed by later DO_STRUCT_DIFFERENCE calls (which can
  // happen when the nsRuleNode::ComputeXXXData method looks up another
  // struct.)  This is important for callers in RestyleManager that
  // need to know the equality or not of the final set of cached struct
  // pointers.
  *aSamePointerStructs = 0;

#define STYLE_STRUCT_LIST_IGNORE_VARIABLES
#define STYLE_STRUCT(name_, callback_)                                        \
  {                                                                           \
    const nsStyle##name_* data = PEEK(name_);                                 \
    if (!data || data == aNewContext->ThreadsafeStyle##name_()) {             \
      *aSamePointerStructs |= NS_STYLE_INHERIT_BIT(name_);                    \
    }                                                                         \
  }
#include "nsStyleStructList.h"
#undef STYLE_STRUCT_LIST_IGNORE_VARIABLES
#undef STYLE_STRUCT

  // Note that we do not check whether this->RelevantLinkVisited() !=
  // aNewContext->RelevantLinkVisited(); we don't need to since
  // nsCSSFrameConstructor::DoContentStateChanged always adds
  // nsChangeHint_RepaintFrame for NS_EVENT_STATE_VISITED changes (and
  // needs to, since HasStateDependentStyle probably doesn't work right
  // for NS_EVENT_STATE_VISITED).  Hopefully this doesn't actually
  // expose whether links are visited to performance tests since all
  // link coloring happens asynchronously at a time when it's hard for
  // the page to measure.
  // However, we do need to compute the larger of the changes that can
  // happen depending on whether the link is visited or unvisited, since
  // doing only the one that's currently appropriate would expose which
  // links are in history to easy performance measurement.  Therefore,
  // here, we add nsChangeHint_RepaintFrame hints (the maximum for
  // things that can depend on :visited) for the properties on which we
  // call GetVisitedDependentColor.
  nsStyleContext* thisVis = GetStyleIfVisited();
  nsStyleContext* otherVis = aNewContext->GetStyleIfVisited();
  if (!thisVis != !otherVis) {
    // One style context has a style-if-visited and the other doesn't.
    // Presume a difference.
#define STYLE_STRUCT(name_, fields_)                                \
    *aSamePointerStructs &= ~NS_STYLE_INHERIT_BIT(name_);           \
    *aEqualStructs &= ~NS_STYLE_INHERIT_BIT(name_);
#include "nsCSSVisitedDependentPropList.h"
#undef STYLE_STRUCT
    hint |= nsChangeHint_RepaintFrame;
  } else if (thisVis) {
    // Both style contexts have a style-if-visited.
    bool change = false;

    // NB: Calling Peek on |this|, not |thisVis|, since callers may look
    // at a struct on |this| without looking at the same struct on
    // |thisVis| (including this function if we skip one of these checks
    // due to change being true already or due to the old style context
    // not having a style-if-visited), but not the other way around.
#define STYLE_FIELD(name_) thisVisStruct->name_ != otherVisStruct->name_
#define STYLE_STRUCT(name_, fields_)                                    \
    if (PEEK(name_)) {                                                  \
      const nsStyle##name_* thisVisStruct =                             \
        thisVis->ThreadsafeStyle##name_();                              \
      const nsStyle##name_* otherVisStruct =                            \
        otherVis->ThreadsafeStyle##name_();                             \
      if (MOZ_FOR_EACH_SEPARATED(STYLE_FIELD, (||), (), fields_)) {     \
        *aSamePointerStructs &= ~NS_STYLE_INHERIT_BIT(name_);           \
        *aEqualStructs &= ~NS_STYLE_INHERIT_BIT(name_);                 \
        change = true;                                                  \
      }                                                                 \
    }
#include "nsCSSVisitedDependentPropList.h"
#undef STYLE_STRUCT
#undef STYLE_FIELD

    if (change) {
      hint |= nsChangeHint_RepaintFrame;
    }
  }

  if (hint & nsChangeHint_UpdateContainingBlock) {
    // If a struct returned nsChangeHint_UpdateContainingBlock, that
    // means that one property's influence on whether we're a containing
    // block for abs-pos or fixed-pos elements has changed.  However, we
    // only need to return the hint if the overall computation of
    // whether we establish a containing block has changed.

    // This depends on data in nsStyleDisplay, nsStyleEffects and
    // nsStyleSVGReset, so we do it here.

    // Note that it's perhaps good for this test to be last because it
    // doesn't use Peek* functions to get the structs on the old
    // context.  But this isn't a big concern because these struct
    // getters should be called during frame construction anyway.
    if (ThreadsafeStyleDisplay()->IsAbsPosContainingBlockForAppropriateFrame(this) ==
        aNewContext->ThreadsafeStyleDisplay()->
          IsAbsPosContainingBlockForAppropriateFrame(aNewContext) &&
        ThreadsafeStyleDisplay()->IsFixedPosContainingBlockForAppropriateFrame(this) ==
        aNewContext->ThreadsafeStyleDisplay()->
          IsFixedPosContainingBlockForAppropriateFrame(aNewContext)) {
      // While some styles that cause the frame to be a containing block
      // has changed, the overall result hasn't.
      hint &= ~nsChangeHint_UpdateContainingBlock;
    }
  }

  MOZ_ASSERT(NS_IsHintSubset(hint, nsChangeHint_AllHints),
             "Added a new hint without bumping AllHints?");
  return hint & ~nsChangeHint_NeutralChange;
}

namespace mozilla {

void
GeckoStyleContext::EnsureSameStructsCached(nsStyleContext* aOldContext)
{
  // NOTE(emilio): We could do better here for stylo, where we only call
  // Style##name_() because we need to run FinishStyle, but otherwise this
  // is only a bitwise or.
  //
  // We could reduce the FFI traffic we do only doing it for structs that have
  // non-trivial FinishStyle.

#define STYLE_STRUCT(name_, checkdata_cb_)                                    \
  if (aOldContext->PeekStyle##name_()) {                                      \
    Style##name_();                                                           \
  }
#include "nsStyleStructList.h"
#undef STYLE_STRUCT
}

} // namespace mozilla

#ifdef DEBUG
void nsStyleContext::List(FILE* out, int32_t aIndent, bool aListDescendants)
{
  nsAutoCString str;
  // Indent
  int32_t ix;
  for (ix = aIndent; --ix >= 0; ) {
    str.AppendLiteral("  ");
  }
  str.Append(nsPrintfCString("%p(%d) parent=%p ",
                             (void*)this, IsGecko() ? AsGecko()->mRefCnt : 0,
                             IsGecko() ? AsGecko()->GetParent() : nullptr));
  if (mPseudoTag) {
    nsAutoString  buffer;
    mPseudoTag->ToString(buffer);
    AppendUTF16toUTF8(buffer, str);
    str.Append(' ');
  }

  if (IsServo()) {
    fprintf_stderr(out, "%s{ServoComputedData}\n", str.get());
  } else if (nsRuleNode* ruleNode = AsGecko()->RuleNode()) {
    fprintf_stderr(out, "%s{\n", str.get());
    str.Truncate();
    while (ruleNode) {
      nsIStyleRule *styleRule = ruleNode->GetRule();
      if (styleRule) {
        styleRule->List(out, aIndent + 1);
      }
      ruleNode = ruleNode->GetParent();
    }
    for (ix = aIndent; --ix >= 0; ) {
      str.AppendLiteral("  ");
    }
    fprintf_stderr(out, "%s}\n", str.get());
  }
  else {
    fprintf_stderr(out, "%s{}\n", str.get());
  }

  if (aListDescendants) {
    if (GeckoStyleContext* gecko = GetAsGecko()) {
      gecko->ListDescendants(out, aIndent);
    }
  }
}
#endif

already_AddRefed<GeckoStyleContext>
NS_NewStyleContext(GeckoStyleContext* aParentContext,
                   nsAtom* aPseudoTag,
                   CSSPseudoElementType aPseudoType,
                   nsRuleNode* aRuleNode,
                   bool aSkipParentDisplayBasedStyleFixup)
{
  RefPtr<nsRuleNode> node = aRuleNode;
  RefPtr<GeckoStyleContext> context =
    new (aRuleNode->PresContext())
    GeckoStyleContext(aParentContext, aPseudoTag, aPseudoType, node.forget(),
                   aSkipParentDisplayBasedStyleFixup);
  return context.forget();
}

nsIPresShell*
nsStyleContext::Arena()
{
  return PresContext()->PresShell();
}

template<typename Func>
static nscolor
GetVisitedDependentColorInternal(nsStyleContext* aSc, Func aColorFunc)
{
  nscolor colors[2];
  colors[0] = aColorFunc(aSc);
  if (nsStyleContext* visitedStyle = aSc->GetStyleIfVisited()) {
    colors[1] = aColorFunc(visitedStyle);
    return nsStyleContext::
      CombineVisitedColors(colors, aSc->RelevantLinkVisited());
  }
  return colors[0];
}

static nscolor
ExtractColor(nsStyleContext* aContext, const nscolor& aColor)
{
  return aColor;
}

static nscolor
ExtractColor(nsStyleContext* aContext, const StyleComplexColor& aColor)
{
  return aContext->StyleColor()->CalcComplexColor(aColor);
}

static nscolor
ExtractColor(nsStyleContext* aContext, const nsStyleSVGPaint& aPaintServer)
{
  return aPaintServer.Type() == eStyleSVGPaintType_Color
    ? aPaintServer.GetColor() : NS_RGBA(0, 0, 0, 0);
}

#define STYLE_FIELD(struct_, field_) aField == &struct_::field_ ||
#define STYLE_STRUCT(name_, fields_)                                          \
  template<> nscolor                                                          \
  nsStyleContext::GetVisitedDependentColor(                                   \
    decltype(nsStyle##name_::MOZ_ARG_1 fields_) nsStyle##name_::* aField)     \
  {                                                                           \
    MOZ_ASSERT(MOZ_FOR_EACH(STYLE_FIELD, (nsStyle##name_,), fields_) false,   \
               "Getting visited-dependent color for a field in nsStyle"#name_ \
               " which is not listed in nsCSSVisitedDependentPropList.h");    \
    return GetVisitedDependentColorInternal(this,                             \
                                            [aField](nsStyleContext* sc) {    \
      return ExtractColor(sc, sc->Style##name_()->*aField);                   \
    });                                                                       \
  }
#include "nsCSSVisitedDependentPropList.h"
#undef STYLE_STRUCT
#undef STYLE_FIELD

struct ColorIndexSet {
  uint8_t colorIndex, alphaIndex;
};

static const ColorIndexSet gVisitedIndices[2] = { { 0, 0 }, { 1, 0 } };

/* static */ nscolor
nsStyleContext::CombineVisitedColors(nscolor *aColors, bool aLinkIsVisited)
{
  if (NS_GET_A(aColors[1]) == 0) {
    // If the style-if-visited is transparent, then just use the
    // unvisited style rather than using the (meaningless) color
    // components of the visited style along with a potentially
    // non-transparent alpha value.
    aLinkIsVisited = false;
  }

  // NOTE: We want this code to have as little timing dependence as
  // possible on whether this->RelevantLinkVisited() is true.
  const ColorIndexSet &set =
    gVisitedIndices[aLinkIsVisited ? 1 : 0];

  nscolor colorColor = aColors[set.colorIndex];
  nscolor alphaColor = aColors[set.alphaIndex];
  return NS_RGBA(NS_GET_R(colorColor), NS_GET_G(colorColor),
                 NS_GET_B(colorColor), NS_GET_A(alphaColor));
}

#ifdef DEBUG
/* static */ const char*
nsStyleContext::StructName(nsStyleStructID aSID)
{
  switch (aSID) {
#define STYLE_STRUCT(name_, checkdata_cb)                                     \
    case eStyleStruct_##name_:                                                \
      return #name_;
#include "nsStyleStructList.h"
#undef STYLE_STRUCT
    default:
      return "Unknown";
  }
}

/* static */ bool
nsStyleContext::LookupStruct(const nsACString& aName, nsStyleStructID& aResult)
{
  if (false)
    ;
#define STYLE_STRUCT(name_, checkdata_cb_)                                    \
  else if (aName.EqualsLiteral(#name_))                                       \
    aResult = eStyleStruct_##name_;
#include "nsStyleStructList.h"
#undef STYLE_STRUCT
  else
    return false;
  return true;
}

void
nsStyleContext::FrameAddRef()
{
  if (auto gecko = GetAsGecko()) {
    gecko->FrameAddRef();
  }
}

void
nsStyleContext::FrameRelease()
{
  if (auto gecko = GetAsGecko()) {
    gecko->FrameRelease();
  }
}
#endif
