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

/* rules in a CSS stylesheet other than style rules (e.g., @import rules) */

#include "nsCSSRules.h"
#include "nsCSSFontFaceRule.h"

#include "mozilla/Attributes.h"

#include "nsCSSValue.h"
#include "mozilla/StyleSheetInlines.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/css/ImportRule.h"
#include "mozilla/css/NameSpaceRule.h"

#include "nsString.h"
#include "nsAtom.h"

#include "nsCSSProps.h"

#include "nsCOMPtr.h"
#include "nsMediaList.h"
#include "mozilla/dom/CSSRuleList.h"
#include "nsIDocument.h"
#include "nsPresContext.h"

#include "nsContentUtils.h"
#include "nsError.h"
#include "nsStyleUtil.h"
#include "mozilla/DeclarationBlockInlines.h"
#include "nsCSSParser.h"
#include "nsDOMClassInfoID.h"
#include "mozilla/dom/CSSStyleDeclarationBinding.h"
#include "mozilla/dom/CSSFontFeatureValuesRuleBinding.h"
#include "StyleRule.h"
#include "nsFont.h"
#include "nsIURI.h"

using namespace mozilla;
using namespace mozilla::dom;

// base class for all rule types in a CSS style sheet

namespace mozilla {
namespace css {

// -------------------------------------------
// ImportRule
//

ImportRule::ImportRule(nsMediaList* aMedia, const nsString& aURLSpec,
                       uint32_t aLineNumber, uint32_t aColumnNumber)
  : CSSImportRule(aLineNumber, aColumnNumber)
  , mURLSpec(aURLSpec)
  , mMedia(aMedia)
{
  MOZ_ASSERT(aMedia);
  // XXXbz This is really silly.... the mMedia here will be replaced
  // with itself if we manage to load a sheet.  Which should really
  // never fail nowadays, in sane cases.
}

ImportRule::ImportRule(const ImportRule& aCopy)
  : CSSImportRule(aCopy),
    mURLSpec(aCopy.mURLSpec)
{
  // Whether or not an @import rule has a null sheet is a permanent
  // property of that @import rule, since it is null only if the target
  // sheet failed security checks.
  if (aCopy.mChildSheet) {
    RefPtr<StyleSheet> sheet =
      aCopy.mChildSheet->Clone(nullptr, this, nullptr, nullptr);
    SetSheet(static_cast<CSSStyleSheet*>(sheet.get()));
    // SetSheet sets mMedia appropriately
  } else {
    // We better just copy mMedia from aCopy, since we have nowhere else to get
    // one.
    mMedia = aCopy.mMedia;
  }
}

ImportRule::~ImportRule()
{
  if (mChildSheet) {
    mChildSheet->SetOwnerRule(nullptr);
  }
}

NS_IMPL_ADDREF_INHERITED(ImportRule, CSSImportRule)
NS_IMPL_RELEASE_INHERITED(ImportRule, CSSImportRule)

// QueryInterface implementation for ImportRule
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ImportRule)
NS_INTERFACE_MAP_END_INHERITING(CSSImportRule)

NS_IMPL_CYCLE_COLLECTION_CLASS(ImportRule)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ImportRule, CSSImportRule)
  if (tmp->mChildSheet) {
    tmp->mChildSheet->SetOwnerRule(nullptr);
    tmp->mChildSheet = nullptr;
  }
  tmp->mMedia = nullptr;
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ImportRule, CSSImportRule)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMedia)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildSheet)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

#ifdef DEBUG
/* virtual */ void
ImportRule::List(FILE* out, int32_t aIndent) const
{
  nsAutoCString str;
  // Indent
  for (int32_t indent = aIndent; --indent >= 0; ) {
    str.AppendLiteral("  ");
  }

  str.AppendLiteral("@import \"");
  AppendUTF16toUTF8(mURLSpec, str);
  str.AppendLiteral("\" ");

  nsAutoString mediaText;
  mMedia->GetText(mediaText);
  AppendUTF16toUTF8(mediaText, str);
  str.AppendLiteral("\n");
  fprintf_stderr(out, "%s", str.get());
}
#endif

/* virtual */ already_AddRefed<Rule>
ImportRule::Clone() const
{
  RefPtr<Rule> clone = new ImportRule(*this);
  return clone.forget();
}

void
ImportRule::SetSheet(CSSStyleSheet* aSheet)
{
  NS_PRECONDITION(aSheet, "null arg");

  // set the new sheet
  mChildSheet = aSheet;
  aSheet->SetOwnerRule(this);

  // set our medialist to be the same as the sheet's medialist
  mMedia = static_cast<nsMediaList*>(mChildSheet->Media());
}

void
ImportRule::GetCssTextImpl(nsAString& aCssText) const
{
  aCssText.AssignLiteral("@import url(");
  nsStyleUtil::AppendEscapedCSSString(mURLSpec, aCssText);
  aCssText.Append(')');
  if (mMedia) {
    nsAutoString mediaText;
    mMedia->GetText(mediaText);
    if (!mediaText.IsEmpty()) {
      aCssText.Append(' ');
      aCssText.Append(mediaText);
    }
  }
  aCssText.Append(';');
}

MediaList*
ImportRule::GetMedia() const
{
  return mMedia;
}

StyleSheet*
ImportRule::GetStyleSheet() const
{
  return mChildSheet;
}

void
ImportRule::GetHref(nsAString& aHref) const
{
  aHref = mURLSpec;
}

/* virtual */ size_t
ImportRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
{
  return aMallocSizeOf(this);

  // Measurement of the following members may be added later if DMD finds it is
  // worthwhile:
  // - mURLSpec
  //
  // The following members are not measured:
  // - mMedia, because it is measured via CSSStyleSheet::mMedia
  // - mChildSheet, because it is measured via CSSStyleSheetInner::mSheets
}

// -------------------------------------------
// nsICSSMediaRule
//
MediaRule::MediaRule(uint32_t aLineNumber, uint32_t aColumnNumber)
  : CSSMediaRule(aLineNumber, aColumnNumber)
{
}

MediaRule::MediaRule(const MediaRule& aCopy)
  : CSSMediaRule(aCopy)
{
  if (aCopy.mMedia) {
    mMedia = aCopy.mMedia->Clone().downcast<nsMediaList>();
    // XXXldb This doesn't really make sense.
    mMedia->SetStyleSheet(aCopy.GetStyleSheet());
  }
}

MediaRule::~MediaRule()
{
  if (mMedia) {
    mMedia->SetStyleSheet(nullptr);
  }
}

NS_IMPL_ADDREF_INHERITED(MediaRule, CSSMediaRule)
NS_IMPL_RELEASE_INHERITED(MediaRule, CSSMediaRule)

// QueryInterface implementation for MediaRule
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaRule)
NS_INTERFACE_MAP_END_INHERITING(CSSMediaRule)

NS_IMPL_CYCLE_COLLECTION_CLASS(MediaRule)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaRule, CSSMediaRule)
  if (tmp->mMedia) {
    tmp->mMedia->SetStyleSheet(nullptr);
    tmp->mMedia = nullptr;
  }
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaRule, CSSMediaRule)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMedia)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

/* virtual */ void
MediaRule::SetStyleSheet(StyleSheet* aSheet)
{
  if (mMedia) {
    // Set to null so it knows it's leaving one sheet and joining another.
    mMedia->SetStyleSheet(nullptr);
    if (aSheet) {
      mMedia->SetStyleSheet(aSheet->AsGecko());
    }
  }

  GroupRule::SetStyleSheet(aSheet);
}

#ifdef DEBUG
/* virtual */ void
MediaRule::List(FILE* out, int32_t aIndent) const
{
  nsAutoCString indentStr;
  for (int32_t indent = aIndent; --indent >= 0; ) {
    indentStr.AppendLiteral("  ");
  }

  nsAutoCString str(indentStr);
  str.AppendLiteral("@media ");

  if (mMedia) {
    nsAutoString mediaText;
    mMedia->GetText(mediaText);
    AppendUTF16toUTF8(mediaText, str);
  }

  str.AppendLiteral(" {\n");
  fprintf_stderr(out, "%s", str.get());

  GroupRule::List(out, aIndent);

  fprintf_stderr(out, "%s}\n", indentStr.get());
}
#endif

/* virtual */ already_AddRefed<Rule>
MediaRule::Clone() const
{
  RefPtr<Rule> clone = new MediaRule(*this);
  return clone.forget();
}

nsresult
MediaRule::SetMedia(nsMediaList* aMedia)
{
  mMedia = aMedia;
  if (aMedia)
    mMedia->SetStyleSheet(GetStyleSheet());
  return NS_OK;
}

MediaList*
MediaRule::Media()
{
  // In practice, if we end up being parsed at all, we have non-null mMedia.  So
  // it's OK to claim we don't return null here.
  return mMedia;
}

void
MediaRule::GetCssTextImpl(nsAString& aCssText) const
{
  aCssText.AssignLiteral("@media ");
  AppendConditionText(aCssText);
  GroupRule::AppendRulesToCssText(aCssText);
}

void
MediaRule::GetConditionText(nsAString& aConditionText)
{
  aConditionText.Truncate(0);
  AppendConditionText(aConditionText);
}

void
MediaRule::SetConditionText(const nsAString& aConditionText,
                            ErrorResult& aRv)
{
  if (!mMedia) {
    mMedia = new nsMediaList();
    mMedia->SetStyleSheet(GetStyleSheet());
  }

  mMedia->SetMediaText(aConditionText);
}

// GroupRule interface
/* virtual */ bool
MediaRule::UseForPresentation(nsPresContext* aPresContext,
                              nsMediaQueryResultCacheKey& aKey)
{
  if (mMedia) {
    MOZ_ASSERT(aPresContext);
    return mMedia->Matches(aPresContext, &aKey);
  }
  return true;
}

/* virtual */ size_t
MediaRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
{
  size_t n = aMallocSizeOf(this);
  n += GroupRule::SizeOfExcludingThis(aMallocSizeOf);

  // Measurement of the following members may be added later if DMD finds it is
  // worthwhile:
  // - mMedia

  return n;
}

void
MediaRule::AppendConditionText(nsAString& aOutput) const
{
  if (mMedia) {
    nsAutoString mediaText;
    mMedia->GetText(mediaText);
    aOutput.Append(mediaText);
  }
}

DocumentRule::DocumentRule(uint32_t aLineNumber, uint32_t aColumnNumber)
  : dom::CSSMozDocumentRule(aLineNumber, aColumnNumber)
{
}

DocumentRule::DocumentRule(const DocumentRule& aCopy)
  : dom::CSSMozDocumentRule(aCopy)
  , mURLs(new URL(*aCopy.mURLs))
{
}

DocumentRule::~DocumentRule()
{
}

NS_IMPL_ADDREF_INHERITED(DocumentRule, ConditionRule)
NS_IMPL_RELEASE_INHERITED(DocumentRule, ConditionRule)

// QueryInterface implementation for DocumentRule
NS_INTERFACE_MAP_BEGIN(DocumentRule)
NS_INTERFACE_MAP_END_INHERITING(dom::CSSMozDocumentRule)

#ifdef DEBUG
/* virtual */ void
DocumentRule::List(FILE* out, int32_t aIndent) const
{
  nsAutoCString indentStr;
  for (int32_t indent = aIndent; --indent >= 0; ) {
    indentStr.AppendLiteral("  ");
  }

  nsAutoCString str;
  str.AppendLiteral("@-moz-document ");
  for (URL *url = mURLs; url; url = url->next) {
    switch (url->func) {
      case URLMatchingFunction::eURL:
        str.AppendLiteral("url(\"");
        break;
      case URLMatchingFunction::eURLPrefix:
        str.AppendLiteral("url-prefix(\"");
        break;
      case URLMatchingFunction::eDomain:
        str.AppendLiteral("domain(\"");
        break;
      case URLMatchingFunction::eRegExp:
        str.AppendLiteral("regexp(\"");
        break;
    }
    nsAutoCString escapedURL(url->url);
    escapedURL.ReplaceSubstring("\"", "\\\""); // escape quotes
    str.Append(escapedURL);
    str.AppendLiteral("\"), ");
  }
  str.Cut(str.Length() - 2, 1); // remove last ,
  fprintf_stderr(out, "%s%s {\n", indentStr.get(), str.get());

  GroupRule::List(out, aIndent);

  fprintf_stderr(out, "%s}\n", indentStr.get());
}
#endif

/* virtual */ already_AddRefed<Rule>
DocumentRule::Clone() const
{
  RefPtr<Rule> clone = new DocumentRule(*this);
  return clone.forget();
}

void
DocumentRule::GetCssTextImpl(nsAString& aCssText) const
{
  aCssText.AssignLiteral("@-moz-document ");
  AppendConditionText(aCssText);
  GroupRule::AppendRulesToCssText(aCssText);
}

void
DocumentRule::GetConditionText(nsAString& aConditionText)
{
  aConditionText.Truncate(0);
  AppendConditionText(aConditionText);
}

void
DocumentRule::SetConditionText(const nsAString& aConditionText,
                               ErrorResult& aRv)
{
  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
}

// GroupRule interface
/* virtual */ bool
DocumentRule::UseForPresentation(nsPresContext* aPresContext,
                                 nsMediaQueryResultCacheKey& aKey)
{
  return UseForPresentation(aPresContext);
}

bool
DocumentRule::UseForPresentation(nsPresContext* aPresContext)
{
  nsIDocument *doc = aPresContext->Document();
  nsIURI *docURI = doc->GetDocumentURI();
  nsAutoCString docURISpec;
  if (docURI) {
    // If GetSpec fails (due to OOM) just skip these URI-specific CSS rules.
    nsresult rv = docURI->GetSpec(docURISpec);
    NS_ENSURE_SUCCESS(rv, false);
  }

  for (URL *url = mURLs; url; url = url->next) {
    if (Match(doc, docURI, docURISpec, url->url, url->func)) {
      return true;
    }
  }

  return false;
}

DocumentRule::URL::~URL()
{
  NS_CSS_DELETE_LIST_MEMBER(DocumentRule::URL, this, next);
}

/* virtual */ size_t
DocumentRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
{
  size_t n = aMallocSizeOf(this);
  n += GroupRule::SizeOfExcludingThis(aMallocSizeOf);

  // Measurement of the following members may be added later if DMD finds it is
  // worthwhile:
  // - mURLs

  return n;
}

void
DocumentRule::AppendConditionText(nsAString& aCssText) const
{
  for (URL *url = mURLs; url; url = url->next) {
    switch (url->func) {
      case URLMatchingFunction::eURL:
        aCssText.AppendLiteral("url(");
        break;
      case URLMatchingFunction::eURLPrefix:
        aCssText.AppendLiteral("url-prefix(");
        break;
      case URLMatchingFunction::eDomain:
        aCssText.AppendLiteral("domain(");
        break;
      case URLMatchingFunction::eRegExp:
        aCssText.AppendLiteral("regexp(");
        break;
    }
    nsStyleUtil::AppendEscapedCSSString(NS_ConvertUTF8toUTF16(url->url),
                                        aCssText);
    aCssText.AppendLiteral("), ");
  }
  aCssText.Truncate(aCssText.Length() - 2); // remove last ", "
}

// -------------------------------------------
// NameSpaceRule
//

NameSpaceRule::NameSpaceRule(nsAtom* aPrefix, const nsString& aURLSpec,
                             uint32_t aLineNumber, uint32_t aColumnNumber)
  : CSSNamespaceRule(aLineNumber, aColumnNumber),
    mPrefix(aPrefix),
    mURLSpec(aURLSpec)
{
}

NameSpaceRule::NameSpaceRule(const NameSpaceRule& aCopy)
  : CSSNamespaceRule(aCopy),
    mPrefix(aCopy.mPrefix),
    mURLSpec(aCopy.mURLSpec)
{
}

NameSpaceRule::~NameSpaceRule()
{
}

NS_IMPL_ADDREF_INHERITED(NameSpaceRule, CSSNamespaceRule)
NS_IMPL_RELEASE_INHERITED(NameSpaceRule, CSSNamespaceRule)

// QueryInterface implementation for NameSpaceRule
// If this ever gets its own cycle-collection bits, reevaluate our IsCCLeaf
// implementation.
NS_INTERFACE_MAP_BEGIN(NameSpaceRule)
  if (aIID.Equals(NS_GET_IID(css::NameSpaceRule))) {
    *aInstancePtr = this;
    NS_ADDREF_THIS();
    return NS_OK;
  }
  else
NS_INTERFACE_MAP_END_INHERITING(CSSNamespaceRule)

#ifdef DEBUG
/* virtual */ void
NameSpaceRule::List(FILE* out, int32_t aIndent) const
{
  nsAutoCString str;
  for (int32_t indent = aIndent; --indent >= 0; ) {
    str.AppendLiteral("  ");
  }

  nsAutoString  buffer;

  str.AppendLiteral("@namespace ");

  if (mPrefix) {
    mPrefix->ToString(buffer);
    AppendUTF16toUTF8(buffer, str);
    str.Append(' ');
  }

  str.AppendLiteral("url(\"");
  AppendUTF16toUTF8(mURLSpec, str);
  str.AppendLiteral("\")\n");
  fprintf_stderr(out, "%s", str.get());
}
#endif

/* virtual */ already_AddRefed<Rule>
NameSpaceRule::Clone() const
{
  RefPtr<Rule> clone = new NameSpaceRule(*this);
  return clone.forget();
}

void
NameSpaceRule::GetCssTextImpl(nsAString& aCssText) const
{
  aCssText.AssignLiteral("@namespace ");
  if (mPrefix) {
    aCssText.Append(nsDependentAtomString(mPrefix) + NS_LITERAL_STRING(" "));
  }
  aCssText.AppendLiteral("url(");
  nsStyleUtil::AppendEscapedCSSString(mURLSpec, aCssText);
  aCssText.AppendLiteral(");");
}

/* virtual */ size_t
NameSpaceRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
{
  return aMallocSizeOf(this);

  // Measurement of the following members may be added later if DMD finds it is
  // worthwhile:
  // - mPrefix
  // - mURLSpec
}

} // namespace css
} // namespace mozilla

// -----------------------------------
// nsCSSFontFeatureValuesRule
//

/* virtual */ already_AddRefed<css::Rule>
nsCSSFontFeatureValuesRule::Clone() const
{
  RefPtr<css::Rule> clone = new nsCSSFontFeatureValuesRule(*this);
  return clone.forget();
}

static void
FeatureValuesToString(
  const nsTArray<gfxFontFeatureValueSet::FeatureValues>& aFeatureValues,
  nsAString& aOutStr)
{
  uint32_t i, n;

  // append values
  n = aFeatureValues.Length();
  for (i = 0; i < n; i++) {
    const gfxFontFeatureValueSet::FeatureValues& fv = aFeatureValues[i];

    // @alternate
    aOutStr.AppendLiteral("  @");
    nsAutoString functAlt;
    nsStyleUtil::GetFunctionalAlternatesName(fv.alternate, functAlt);
    aOutStr.Append(functAlt);
    aOutStr.AppendLiteral(" {");

    // for each ident-values tuple
    uint32_t j, numValues = fv.valuelist.Length();
    for (j = 0; j < numValues; j++) {
      aOutStr.Append(' ');
      const gfxFontFeatureValueSet::ValueList& vlist = fv.valuelist[j];
      nsStyleUtil::AppendEscapedCSSIdent(vlist.name, aOutStr);
      aOutStr.Append(':');

      uint32_t k, numSelectors = vlist.featureSelectors.Length();
      for (k = 0; k < numSelectors; k++) {
        aOutStr.Append(' ');
        aOutStr.AppendInt(vlist.featureSelectors[k]);
      }

      aOutStr.Append(';');
    }
    aOutStr.AppendLiteral(" }\n");
  }
}

static void
FontFeatureValuesRuleToString(
  mozilla::SharedFontList* aFamilyList,
  const nsTArray<gfxFontFeatureValueSet::FeatureValues>& aFeatureValues,
  nsAString& aOutStr)
{
  aOutStr.AssignLiteral("@font-feature-values ");
  nsAutoString familyListStr, valueTextStr;
  nsStyleUtil::AppendEscapedCSSFontFamilyList(aFamilyList, familyListStr);
  aOutStr.Append(familyListStr);
  aOutStr.AppendLiteral(" {\n");
  FeatureValuesToString(aFeatureValues, valueTextStr);
  aOutStr.Append(valueTextStr);
  aOutStr.Append('}');
}

#ifdef DEBUG
void
nsCSSFontFeatureValuesRule::List(FILE* out, int32_t aIndent) const
{
  nsAutoString text;
  FontFeatureValuesRuleToString(mFamilyList, mFeatureValues, text);
  NS_ConvertUTF16toUTF8 utf8(text);

  // replace newlines with newlines plus indent spaces
  char* indent = new char[(aIndent + 1) * 2];
  int32_t i;
  for (i = 1; i < (aIndent + 1) * 2 - 1; i++) {
    indent[i] = 0x20;
  }
  indent[0] = 0xa;
  indent[aIndent * 2 + 1] = 0;
  utf8.ReplaceSubstring("\n", indent);
  delete [] indent;

  nsAutoCString indentStr;
  for (i = aIndent; --i >= 0; ) {
    indentStr.AppendLiteral("  ");
  }
  fprintf_stderr(out, "%s%s\n", indentStr.get(), utf8.get());
}
#endif

void
nsCSSFontFeatureValuesRule::GetFontFamily(nsAString& aFamilyListStr)
{
  nsStyleUtil::AppendEscapedCSSFontFamilyList(mFamilyList, aFamilyListStr);
}

void
nsCSSFontFeatureValuesRule::GetValueText(nsAString& aValueText)
{
  FeatureValuesToString(mFeatureValues, aValueText);
}

void
nsCSSFontFeatureValuesRule::SetFontFamily(const nsAString& aFontFamily,
                                          ErrorResult& aRv)
{
  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
}

void
nsCSSFontFeatureValuesRule::SetValueText(const nsAString& aValueText,
                                         ErrorResult& aRv)
{
  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
}

void
nsCSSFontFeatureValuesRule::GetCssTextImpl(nsAString& aCssText) const
{
  FontFeatureValuesRuleToString(mFamilyList, mFeatureValues, aCssText);
}

struct MakeFamilyArray {
  explicit MakeFamilyArray(nsTArray<nsString>& aFamilyArray)
    : familyArray(aFamilyArray), hasGeneric(false)
  {}

  static bool
  AddFamily(const nsString& aFamily, bool aGeneric, void* aData)
  {
    MakeFamilyArray *familyArr = reinterpret_cast<MakeFamilyArray*> (aData);
    if (!aGeneric && !aFamily.IsEmpty()) {
      familyArr->familyArray.AppendElement(aFamily);
    }
    if (aGeneric) {
      familyArr->hasGeneric = true;
    }
    return true;
  }

  nsTArray<nsString>& familyArray;
  bool hasGeneric;
};

void
nsCSSFontFeatureValuesRule::AddValueList(int32_t aVariantAlternate,
                     nsTArray<gfxFontFeatureValueSet::ValueList>& aValueList)
{
  uint32_t i, len = mFeatureValues.Length();
  bool foundAlternate = false;

  // add to an existing list for a given property value
  for (i = 0; i < len; i++) {
    gfxFontFeatureValueSet::FeatureValues& f = mFeatureValues.ElementAt(i);

    if (f.alternate == uint32_t(aVariantAlternate)) {
      f.valuelist.AppendElements(aValueList);
      foundAlternate = true;
      break;
    }
  }

  // create a new list for a given property value
  if (!foundAlternate) {
    gfxFontFeatureValueSet::FeatureValues &f = *mFeatureValues.AppendElement();
    f.alternate = aVariantAlternate;
    f.valuelist.AppendElements(aValueList);
  }
}

size_t
nsCSSFontFeatureValuesRule::SizeOfIncludingThis(
  MallocSizeOf aMallocSizeOf) const
{
  return aMallocSizeOf(this);
}

// -------------------------------------------
// nsCSSKeyframeStyleDeclaration
//

nsCSSKeyframeStyleDeclaration::nsCSSKeyframeStyleDeclaration(nsCSSKeyframeRule *aRule)
  : mRule(aRule)
{
}

nsCSSKeyframeStyleDeclaration::~nsCSSKeyframeStyleDeclaration()
{
  NS_ASSERTION(!mRule, "DropReference not called.");
}

NS_IMPL_CYCLE_COLLECTING_ADDREF(nsCSSKeyframeStyleDeclaration)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsCSSKeyframeStyleDeclaration)

NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsCSSKeyframeStyleDeclaration)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsCSSKeyframeStyleDeclaration)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_END_INHERITING(nsDOMCSSDeclaration)

DeclarationBlock*
nsCSSKeyframeStyleDeclaration::GetCSSDeclaration(Operation aOperation)
{
  if (mRule) {
    return mRule->Declaration();
  } else {
    return nullptr;
  }
}

void
nsCSSKeyframeStyleDeclaration::GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv,
                                                        nsIPrincipal* aSubjectPrincipal)
{
  GetCSSParsingEnvironmentForRule(mRule, aCSSParseEnv);
}

nsDOMCSSDeclaration::ServoCSSParsingEnvironment
nsCSSKeyframeStyleDeclaration::GetServoCSSParsingEnvironment(
  nsIPrincipal* aSubjectPrincipal) const
{
  MOZ_CRASH("GetURLData shouldn't be calling on a Gecko rule");
}

css::Rule*
nsCSSKeyframeStyleDeclaration::GetParentRule()
{
  return mRule;
}

nsresult
nsCSSKeyframeStyleDeclaration::SetCSSDeclaration(DeclarationBlock* aDecl)
{
  MOZ_ASSERT(aDecl, "must be non-null");
  mRule->ChangeDeclaration(aDecl->AsGecko());
  return NS_OK;
}

nsIDocument*
nsCSSKeyframeStyleDeclaration::DocToUpdate()
{
  return nullptr;
}

nsINode*
nsCSSKeyframeStyleDeclaration::GetParentObject()
{
  return mRule ? mRule->GetDocument() : nullptr;
}

DocGroup*
nsCSSKeyframeStyleDeclaration::GetDocGroup() const
{
  if (!mRule) {
    return nullptr;
  }

  nsIDocument* document = mRule->GetDocument();
  return document ? document->GetDocGroup() : nullptr;
}

// -------------------------------------------
// nsCSSKeyframeRule
//

nsCSSKeyframeRule::nsCSSKeyframeRule(const nsCSSKeyframeRule& aCopy)
  // copy everything except our reference count and mDOMDeclaration
  : dom::CSSKeyframeRule(aCopy)
  , mKeys(aCopy.mKeys)
  , mDeclaration(new css::Declaration(*aCopy.mDeclaration))
{
  mDeclaration->SetOwningRule(this);
}

nsCSSKeyframeRule::~nsCSSKeyframeRule()
{
  mDeclaration->SetOwningRule(nullptr);
  if (mDOMDeclaration) {
    mDOMDeclaration->DropReference();
  }
}

/* virtual */ already_AddRefed<css::Rule>
nsCSSKeyframeRule::Clone() const
{
  RefPtr<css::Rule> clone = new nsCSSKeyframeRule(*this);
  return clone.forget();
}

NS_IMPL_ADDREF_INHERITED(nsCSSKeyframeRule, dom::CSSKeyframeRule)
NS_IMPL_RELEASE_INHERITED(nsCSSKeyframeRule, dom::CSSKeyframeRule)

NS_IMPL_CYCLE_COLLECTION_CLASS(nsCSSKeyframeRule)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsCSSKeyframeRule,
                                                dom::CSSKeyframeRule)
  if (tmp->mDOMDeclaration) {
    tmp->mDOMDeclaration->DropReference();
    tmp->mDOMDeclaration = nullptr;
  }
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsCSSKeyframeRule,
                                                  dom::CSSKeyframeRule)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMDeclaration)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

bool
nsCSSKeyframeRule::IsCCLeaf() const
{
  // Let's not worry about figuring out whether we're a leaf or not.
  return false;
}

// QueryInterface implementation for nsCSSKeyframeRule
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsCSSKeyframeRule)
NS_INTERFACE_MAP_END_INHERITING(dom::CSSKeyframeRule)

#ifdef DEBUG
void
nsCSSKeyframeRule::List(FILE* out, int32_t aIndent) const
{
  nsAutoCString str;
  for (int32_t index = aIndent; --index >= 0; ) {
    str.AppendLiteral("  ");
  }

  nsAutoString tmp;
  DoGetKeyText(tmp);
  AppendUTF16toUTF8(tmp, str);
  str.AppendLiteral(" { ");
  mDeclaration->ToString(tmp);
  AppendUTF16toUTF8(tmp, str);
  str.AppendLiteral("}\n");
  fprintf_stderr(out, "%s", str.get());
}
#endif

void
nsCSSKeyframeRule::GetCssTextImpl(nsAString& aCssText) const
{
  DoGetKeyText(aCssText);
  aCssText.AppendLiteral(" { ");
  nsAutoString tmp;
  mDeclaration->ToString(tmp);
  aCssText.Append(tmp);
  aCssText.AppendLiteral(" }");
}

void
nsCSSKeyframeRule::GetKeyText(nsAString& aKeyText)
{
  DoGetKeyText(aKeyText);
}

void
nsCSSKeyframeRule::DoGetKeyText(nsAString& aKeyText) const
{
  aKeyText.Truncate();
  uint32_t i = 0, i_end = mKeys.Length();
  MOZ_ASSERT(i_end != 0, "must have some keys");
  for (;;) {
    aKeyText.AppendFloat(mKeys[i] * 100.0f);
    aKeyText.Append(char16_t('%'));
    if (++i == i_end) {
      break;
    }
    aKeyText.AppendLiteral(", ");
  }
}

void
nsCSSKeyframeRule::SetKeyText(const nsAString& aKeyText)
{
  nsCSSParser parser;

  InfallibleTArray<float> newSelectors;
  // FIXME: pass filename and line number
  if (!parser.ParseKeyframeSelectorString(aKeyText, nullptr, 0, newSelectors)) {
    // for now, we don't do anything if the parse fails
    return;
  }

  nsIDocument* doc = GetDocument();
  MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);

  newSelectors.SwapElements(mKeys);

  if (StyleSheet* sheet = GetStyleSheet()) {
    sheet->RuleChanged(this);
  }
}

nsICSSDeclaration*
nsCSSKeyframeRule::Style()
{
  if (!mDOMDeclaration) {
    mDOMDeclaration = new nsCSSKeyframeStyleDeclaration(this);
  }
  return mDOMDeclaration;
}

void
nsCSSKeyframeRule::ChangeDeclaration(css::Declaration* aDeclaration)
{
  // Our caller already did a BeginUpdate/EndUpdate, but with
  // UPDATE_CONTENT, and we need UPDATE_STYLE to trigger work in
  // PresShell::EndUpdate.
  nsIDocument* doc = GetDocument();
  MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);

  if (aDeclaration != mDeclaration) {
    mDeclaration->SetOwningRule(nullptr);
    mDeclaration = aDeclaration;
    mDeclaration->SetOwningRule(this);
  }

  if (StyleSheet* sheet = GetStyleSheet()) {
    sheet->RuleChanged(this);
  }
}

/* virtual */ size_t
nsCSSKeyframeRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
{
  return aMallocSizeOf(this);

  // Measurement of the following members may be added later if DMD finds it is
  // worthwhile:
  // - mKeys
  // - mDeclaration
  // - mDOMDeclaration
}

// -------------------------------------------
// nsCSSKeyframesRule
//

nsCSSKeyframesRule::nsCSSKeyframesRule(const nsCSSKeyframesRule& aCopy)
  // copy everything except our reference count.  GroupRule's copy
  // constructor also doesn't copy the lazily-constructed
  // mRuleCollection.
  : dom::CSSKeyframesRule(aCopy),
    mName(aCopy.mName)
{
}

nsCSSKeyframesRule::~nsCSSKeyframesRule()
{
}

/* virtual */ already_AddRefed<css::Rule>
nsCSSKeyframesRule::Clone() const
{
  RefPtr<css::Rule> clone = new nsCSSKeyframesRule(*this);
  return clone.forget();
}

#ifdef DEBUG
void
nsCSSKeyframesRule::List(FILE* out, int32_t aIndent) const
{
  nsAutoCString indentStr;
  for (int32_t indent = aIndent; --indent >= 0; ) {
    indentStr.AppendLiteral("  ");
  }

  fprintf_stderr(out, "%s@keyframes %s {\n",
                 indentStr.get(), nsAtomCString(mName).get());

  GroupRule::List(out, aIndent);

  fprintf_stderr(out, "%s}\n", indentStr.get());
}
#endif

void
nsCSSKeyframesRule::GetCssTextImpl(nsAString& aCssText) const
{
  aCssText.AssignLiteral("@keyframes ");
  aCssText.Append(nsDependentAtomString(mName));
  aCssText.AppendLiteral(" {\n");
  nsAutoString tmp;
  for (const Rule* rule : GeckoRules()) {
    static_cast<const nsCSSKeyframeRule*>(rule)->GetCssText(tmp);
    aCssText.Append(tmp);
    aCssText.Append('\n');
  }
  aCssText.Append('}');
}

void
nsCSSKeyframesRule::GetName(nsAString& aName) const
{
  mName->ToString(aName);
}

void
nsCSSKeyframesRule::SetName(const nsAString& aName)
{
  if (mName->Equals(aName)) {
    return;
  }

  nsIDocument* doc = GetDocument();
  MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);

  mName = NS_Atomize(aName);

  if (StyleSheet* sheet = GetStyleSheet()) {
    sheet->RuleChanged(this);
  }
}

void
nsCSSKeyframesRule::AppendRule(const nsAString& aRule)
{
  // The spec is confusing, and I think we should just append the rule,
  // which also turns out to match WebKit:
  // http://lists.w3.org/Archives/Public/www-style/2011Apr/0034.html
  nsCSSParser parser;

  // FIXME: pass filename and line number
  RefPtr<nsCSSKeyframeRule> rule =
    parser.ParseKeyframeRule(aRule, nullptr, 0);
  if (rule) {
    nsIDocument* doc = GetDocument();
    MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);

    AppendStyleRule(rule);

    if (StyleSheet* sheet = GetStyleSheet()) {
      sheet->RuleChanged(this);
    }
  }
}

static const uint32_t RULE_NOT_FOUND = uint32_t(-1);

uint32_t
nsCSSKeyframesRule::FindRuleIndexForKey(const nsAString& aKey)
{
  nsCSSParser parser;

  InfallibleTArray<float> keys;
  // FIXME: pass filename and line number
  if (parser.ParseKeyframeSelectorString(aKey, nullptr, 0, keys)) {
    IncrementalClearCOMRuleArray& rules = GeckoRules();
    // The spec isn't clear, but we'll match on the key list, which
    // mostly matches what WebKit does, except we'll do last-match
    // instead of first-match, and handling parsing differences better.
    // http://lists.w3.org/Archives/Public/www-style/2011Apr/0036.html
    // http://lists.w3.org/Archives/Public/www-style/2011Apr/0037.html
    for (uint32_t i = rules.Count(); i-- != 0; ) {
      if (static_cast<nsCSSKeyframeRule*>(rules[i])->GetKeys() == keys) {
        return i;
      }
    }
  }

  return RULE_NOT_FOUND;
}

void
nsCSSKeyframesRule::DeleteRule(const nsAString& aKey)
{
  uint32_t index = FindRuleIndexForKey(aKey);
  if (index != RULE_NOT_FOUND) {
    nsIDocument* doc = GetDocument();
    MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);

    DeleteStyleRuleAt(index);

    if (StyleSheet* sheet = GetStyleSheet()) {
      sheet->RuleChanged(this);
    }
  }
}

nsCSSKeyframeRule*
nsCSSKeyframesRule::FindRule(const nsAString& aKey)
{
  uint32_t index = FindRuleIndexForKey(aKey);
  if (index == RULE_NOT_FOUND) {
    return nullptr;
  }
  return static_cast<nsCSSKeyframeRule*>(GeckoRules()[index]);
}

/* virtual */ size_t
nsCSSKeyframesRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
{
  size_t n = aMallocSizeOf(this);
  n += GroupRule::SizeOfExcludingThis(aMallocSizeOf);

  // Measurement of the following members may be added later if DMD finds it is
  // worthwhile:
  // - mName

  return n;
}

// -------------------------------------------
// nsCSSPageStyleDeclaration
//

nsCSSPageStyleDeclaration::nsCSSPageStyleDeclaration(nsCSSPageRule* aRule)
  : mRule(aRule)
{
}

nsCSSPageStyleDeclaration::~nsCSSPageStyleDeclaration()
{
  NS_ASSERTION(!mRule, "DropReference not called.");
}

NS_IMPL_CYCLE_COLLECTING_ADDREF(nsCSSPageStyleDeclaration)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsCSSPageStyleDeclaration)

NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsCSSPageStyleDeclaration)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsCSSPageStyleDeclaration)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_END_INHERITING(nsDOMCSSDeclaration)

DeclarationBlock*
nsCSSPageStyleDeclaration::GetCSSDeclaration(Operation aOperation)
{
  if (mRule) {
    return mRule->Declaration();
  } else {
    return nullptr;
  }
}

void
nsCSSPageStyleDeclaration::GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv,
                                                    nsIPrincipal* aSubjectPrincipal)
{
  GetCSSParsingEnvironmentForRule(mRule, aCSSParseEnv);
}

nsDOMCSSDeclaration::ServoCSSParsingEnvironment
nsCSSPageStyleDeclaration::GetServoCSSParsingEnvironment(
  nsIPrincipal* aSubjectPrincipal) const
{
  MOZ_CRASH("GetURLData shouldn't be calling on a Gecko rule");
}

css::Rule*
nsCSSPageStyleDeclaration::GetParentRule()
{
  return mRule;
}

nsresult
nsCSSPageStyleDeclaration::SetCSSDeclaration(DeclarationBlock* aDecl)
{
  MOZ_ASSERT(aDecl, "must be non-null");
  mRule->ChangeDeclaration(aDecl->AsGecko());
  return NS_OK;
}

nsIDocument*
nsCSSPageStyleDeclaration::DocToUpdate()
{
  return nullptr;
}

nsINode*
nsCSSPageStyleDeclaration::GetParentObject()
{
  return mRule ? mRule->GetDocument() : nullptr;
}

DocGroup*
nsCSSPageStyleDeclaration::GetDocGroup() const
{
  if (!mRule) {
    return nullptr;
  }

  nsIDocument* document = mRule->GetDocument();
  return document ? document->GetDocGroup() : nullptr;
}

// -------------------------------------------
// nsCSSPageRule
//

nsCSSPageRule::nsCSSPageRule(const nsCSSPageRule& aCopy)
  // copy everything except our reference count and mDOMDeclaration
  : dom::CSSPageRule(aCopy)
  , mDeclaration(new css::Declaration(*aCopy.mDeclaration))
{
  mDeclaration->SetOwningRule(this);
}

nsCSSPageRule::~nsCSSPageRule()
{
  mDeclaration->SetOwningRule(nullptr);
  if (mDOMDeclaration) {
    mDOMDeclaration->DropReference();
  }
}

/* virtual */ already_AddRefed<css::Rule>
nsCSSPageRule::Clone() const
{
  RefPtr<css::Rule> clone = new nsCSSPageRule(*this);
  return clone.forget();
}

NS_IMPL_ADDREF_INHERITED(nsCSSPageRule, dom::CSSPageRule)
NS_IMPL_RELEASE_INHERITED(nsCSSPageRule, dom::CSSPageRule)

NS_IMPL_CYCLE_COLLECTION_CLASS(nsCSSPageRule)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsCSSPageRule,
                                                dom::CSSPageRule)
  if (tmp->mDOMDeclaration) {
    tmp->mDOMDeclaration->DropReference();
    tmp->mDOMDeclaration = nullptr;
  }
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsCSSPageRule,
                                                  dom::CSSPageRule)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMDeclaration)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

bool
nsCSSPageRule::IsCCLeaf() const
{
  // Let's not worry about figuring out whether we're a leaf or not.
  return false;
}

// QueryInterface implementation for nsCSSPageRule
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsCSSPageRule)
NS_INTERFACE_MAP_END_INHERITING(dom::CSSPageRule)

#ifdef DEBUG
void
nsCSSPageRule::List(FILE* out, int32_t aIndent) const
{
  nsAutoCString str;
  for (int32_t indent = aIndent; --indent >= 0; ) {
    str.AppendLiteral("  ");
  }

  str.AppendLiteral("@page { ");
  nsAutoString tmp;
  mDeclaration->ToString(tmp);
  AppendUTF16toUTF8(tmp, str);
  str.AppendLiteral("}\n");
  fprintf_stderr(out, "%s", str.get());
}
#endif

void
nsCSSPageRule::GetCssTextImpl(nsAString& aCssText) const
{
  aCssText.AppendLiteral("@page { ");
  nsAutoString tmp;
  mDeclaration->ToString(tmp);
  aCssText.Append(tmp);
  aCssText.AppendLiteral(" }");
}

nsICSSDeclaration*
nsCSSPageRule::Style()
{
  if (!mDOMDeclaration) {
    mDOMDeclaration = new nsCSSPageStyleDeclaration(this);
  }
  return mDOMDeclaration;
}

void
nsCSSPageRule::ChangeDeclaration(css::Declaration* aDeclaration)
{
  if (aDeclaration != mDeclaration) {
    mDeclaration->SetOwningRule(nullptr);
    mDeclaration = aDeclaration;
    mDeclaration->SetOwningRule(this);
  }

  nsIDocument* doc = GetDocument();
  MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);
  if (StyleSheet* sheet = GetStyleSheet()) {
    sheet->RuleChanged(this);
  }
}

/* virtual */ size_t
nsCSSPageRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
{
  return aMallocSizeOf(this);
}

namespace mozilla {

CSSSupportsRule::CSSSupportsRule(bool aConditionMet,
                                 const nsString& aCondition,
                                 uint32_t aLineNumber, uint32_t aColumnNumber)
  : dom::CSSSupportsRule(aLineNumber, aColumnNumber)
  , mUseGroup(aConditionMet)
  , mCondition(aCondition)
{
}

CSSSupportsRule::~CSSSupportsRule()
{
}

CSSSupportsRule::CSSSupportsRule(const CSSSupportsRule& aCopy)
  : dom::CSSSupportsRule(aCopy),
    mUseGroup(aCopy.mUseGroup),
    mCondition(aCopy.mCondition)
{
}

#ifdef DEBUG
/* virtual */ void
CSSSupportsRule::List(FILE* out, int32_t aIndent) const
{
  nsAutoCString indentStr;
  for (int32_t indent = aIndent; --indent >= 0; ) {
    indentStr.AppendLiteral("  ");
  }

  fprintf_stderr(out, "%s@supports %s {\n",
                 indentStr.get(), NS_ConvertUTF16toUTF8(mCondition).get());

  css::GroupRule::List(out, aIndent);

  fprintf_stderr(out, "%s}\n", indentStr.get());
}
#endif

/* virtual */ already_AddRefed<mozilla::css::Rule>
CSSSupportsRule::Clone() const
{
  RefPtr<css::Rule> clone = new CSSSupportsRule(*this);
  return clone.forget();
}

/* virtual */ bool
CSSSupportsRule::UseForPresentation(nsPresContext* aPresContext,
                                   nsMediaQueryResultCacheKey& aKey)
{
  return mUseGroup;
}

NS_IMPL_ADDREF_INHERITED(mozilla::CSSSupportsRule, dom::CSSSupportsRule)
NS_IMPL_RELEASE_INHERITED(mozilla::CSSSupportsRule, dom::CSSSupportsRule)

// QueryInterface implementation for CSSSupportsRule
NS_INTERFACE_MAP_BEGIN(CSSSupportsRule)
NS_INTERFACE_MAP_END_INHERITING(dom::CSSSupportsRule)

void
CSSSupportsRule::GetCssTextImpl(nsAString& aCssText) const
{
  aCssText.AssignLiteral("@supports ");
  aCssText.Append(mCondition);
  css::GroupRule::AppendRulesToCssText(aCssText);
}

void
CSSSupportsRule::GetConditionText(nsAString& aConditionText)
{
  aConditionText.Assign(mCondition);
}

void
CSSSupportsRule::SetConditionText(const nsAString& aConditionText,
                                  ErrorResult& aRv)
{
  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
}

/* virtual */ size_t
CSSSupportsRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
{
  size_t n = aMallocSizeOf(this);
  n += css::GroupRule::SizeOfExcludingThis(aMallocSizeOf);
  n += mCondition.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
  return n;
}

} // namespace mozilla
