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

/* FFI functions for Servo to call into Gecko */

#include "mozilla/GeckoBindings.h"

#include "ChildIterator.h"
#include "ErrorReporter.h"
#include "gfxFontFeatures.h"
#include "gfxMathTable.h"
#include "gfxTextRun.h"
#include "imgLoader.h"
#include "nsAnimationManager.h"
#include "nsAttrValueInlines.h"
#include "nsCSSFrameConstructor.h"
#include "nsCSSProps.h"
#include "nsCSSPseudoElements.h"
#include "nsContentUtils.h"
#include "nsDOMTokenList.h"
#include "nsDeviceContext.h"
#include "nsLayoutUtils.h"
#include "nsIContentInlines.h"
#include "mozilla/dom/DocumentInlines.h"
#include "nsILoadContext.h"
#include "nsIFrame.h"
#include "nsIMozBrowserFrame.h"
#include "nsINode.h"
#include "nsIURI.h"
#include "nsFontMetrics.h"
#include "nsHTMLStyleSheet.h"
#include "nsMappedAttributes.h"
#include "nsNameSpaceManager.h"
#include "nsNetUtil.h"
#include "nsProxyRelease.h"
#include "nsString.h"
#include "nsStyleStruct.h"
#include "nsStyleUtil.h"
#include "nsTArray.h"
#include "nsTransitionManager.h"
#include "nsWindowSizes.h"

#include "mozilla/css/ImageLoader.h"
#include "mozilla/DeclarationBlock.h"
#include "mozilla/EffectCompositor.h"
#include "mozilla/EffectSet.h"
#include "mozilla/FontPropertyTypes.h"
#include "mozilla/Keyframe.h"
#include "mozilla/Mutex.h"
#include "mozilla/Preferences.h"
#include "mozilla/ServoElementSnapshot.h"
#include "mozilla/ShadowParts.h"
#include "mozilla/StaticPresData.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/RestyleManager.h"
#include "mozilla/SizeOfState.h"
#include "mozilla/StyleAnimationValue.h"
#include "mozilla/ServoBindings.h"
#include "mozilla/ServoTraversalStatistics.h"
#include "mozilla/Telemetry.h"
#include "mozilla/TimelineManager.h"
#include "mozilla/RWLock.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ElementInlines.h"
#include "mozilla/dom/HTMLImageElement.h"
#include "mozilla/dom/HTMLTableCellElement.h"
#include "mozilla/dom/HTMLBodyElement.h"
#include "mozilla/dom/HTMLSelectElement.h"
#include "mozilla/dom/HTMLSlotElement.h"
#include "mozilla/dom/MediaList.h"
#include "mozilla/dom/ReferrerInfo.h"
#include "mozilla/dom/SVGElement.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/URLExtraData.h"
#include "mozilla/dom/CSSMozDocumentRule.h"

#if defined(MOZ_MEMORY)
#  include "mozmemory.h"
#endif

using namespace mozilla;
using namespace mozilla::css;
using namespace mozilla::dom;

// Definitions of the global traversal stats.
bool ServoTraversalStatistics::sActive = false;
ServoTraversalStatistics ServoTraversalStatistics::sSingleton;

static StaticAutoPtr<RWLock> sServoFFILock;

static const LangGroupFontPrefs* ThreadSafeGetLangGroupFontPrefs(
    const Document& aDocument, nsAtom* aLanguage) {
  bool needsCache = false;
  {
    AutoReadLock guard(*sServoFFILock);
    if (auto* prefs = aDocument.GetFontPrefsForLang(aLanguage, &needsCache)) {
      return prefs;
    }
  }
  MOZ_ASSERT(needsCache);
  AutoWriteLock guard(*sServoFFILock);
  return aDocument.GetFontPrefsForLang(aLanguage);
}

static const nsFont& ThreadSafeGetDefaultVariableFont(const Document& aDocument,
                                                      nsAtom* aLanguage) {
  return ThreadSafeGetLangGroupFontPrefs(aDocument, aLanguage)
      ->mDefaultVariableFont;
}

/*
 * Does this child count as significant for selector matching?
 *
 * See nsStyleUtil::IsSignificantChild for details.
 */
bool Gecko_IsSignificantChild(const nsINode* aNode,
                              bool aWhitespaceIsSignificant) {
  return nsStyleUtil::ThreadSafeIsSignificantChild(aNode->AsContent(),
                                                   aWhitespaceIsSignificant);
}

const nsINode* Gecko_GetLastChild(const nsINode* aNode) {
  return aNode->GetLastChild();
}

const nsINode* Gecko_GetFlattenedTreeParentNode(const nsINode* aNode) {
  return aNode->GetFlattenedTreeParentNodeForStyle();
}

const Element* Gecko_GetBeforeOrAfterPseudo(const Element* aElement,
                                            bool aIsBefore) {
  MOZ_ASSERT(aElement);
  MOZ_ASSERT(aElement->HasProperties());

  return aIsBefore ? nsLayoutUtils::GetBeforePseudo(aElement)
                   : nsLayoutUtils::GetAfterPseudo(aElement);
}

const Element* Gecko_GetMarkerPseudo(const Element* aElement) {
  MOZ_ASSERT(aElement);
  MOZ_ASSERT(aElement->HasProperties());

  return nsLayoutUtils::GetMarkerPseudo(aElement);
}

nsTArray<nsIContent*>* Gecko_GetAnonymousContentForElement(
    const Element* aElement) {
  nsIAnonymousContentCreator* ac = do_QueryFrame(aElement->GetPrimaryFrame());
  if (!ac) {
    return nullptr;
  }

  auto* array = new nsTArray<nsIContent*>();
  nsContentUtils::AppendNativeAnonymousChildren(aElement, *array, 0);
  return array;
}

void Gecko_DestroyAnonymousContentList(nsTArray<nsIContent*>* aAnonContent) {
  MOZ_ASSERT(aAnonContent);
  delete aAnonContent;
}

const nsTArray<RefPtr<nsINode>>* Gecko_GetAssignedNodes(
    const Element* aElement) {
  MOZ_ASSERT(HTMLSlotElement::FromNode(aElement));
  return &static_cast<const HTMLSlotElement*>(aElement)->AssignedNodes();
}

void Gecko_GetQueryContainerSize(const Element* aElement, nscoord* aOutWidth,
                                 nscoord* aOutHeight) {
  MOZ_ASSERT(aElement);
  const nsIFrame* frame = aElement->GetPrimaryFrame();
  if (!frame) {
    return;
  }
  const auto containAxes = frame->GetContainSizeAxes();
  if (!containAxes.IsAny()) {
    return;
  }
  nsSize size = frame->GetContentRectRelativeToSelf().Size();
  bool isVertical = frame->GetWritingMode().IsVertical();
  if (isVertical ? containAxes.mBContained : containAxes.mIContained) {
    *aOutWidth = size.width;
  }
  if (isVertical ? containAxes.mIContained : containAxes.mBContained) {
    *aOutHeight = size.height;
  }
}

void Gecko_ComputedStyle_Init(ComputedStyle* aStyle,
                              const ServoComputedData* aValues,
                              PseudoStyleType aPseudoType) {
  new (KnownNotNull, aStyle)
      ComputedStyle(aPseudoType, ServoComputedDataForgotten(aValues));
}

ServoComputedData::ServoComputedData(const ServoComputedDataForgotten aValue) {
  PodAssign(this, aValue.mPtr);
}

MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ServoStyleStructsMallocEnclosingSizeOf)

void ServoComputedData::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const {
  // Note: GetStyleFoo() returns a pointer to an nsStyleFoo that sits within a
  // servo_arc::Arc, i.e. it is preceded by a word-sized refcount. So we need
  // to measure it with a function that can handle an interior pointer. We use
  // ServoStyleStructsEnclosingMallocSizeOf to clearly identify in DMD's
  // output the memory measured here.
#define STYLE_STRUCT(name_)                                       \
  static_assert(alignof(nsStyle##name_) <= sizeof(size_t),        \
                "alignment will break AddSizeOfExcludingThis()"); \
  const void* p##name_ = Style##name_();                          \
  if (!aSizes.mState.HaveSeenPtr(p##name_)) {                     \
    aSizes.mStyleSizes.NS_STYLE_SIZES_FIELD(name_) +=             \
        ServoStyleStructsMallocEnclosingSizeOf(p##name_);         \
  }
#include "nsStyleStructList.h"
#undef STYLE_STRUCT

  if (visited_style && !aSizes.mState.HaveSeenPtr(visited_style)) {
    visited_style->AddSizeOfIncludingThis(aSizes,
                                          &aSizes.mLayoutComputedValuesVisited);
  }

  // Measurement of the following members may be added later if DMD finds it is
  // worthwhile:
  // - custom_properties
  // - writing_mode
  // - rules
  // - font_computation_data
}

void Gecko_ComputedStyle_Destroy(ComputedStyle* aStyle) {
  aStyle->~ComputedStyle();
}

void Gecko_ConstructStyleChildrenIterator(const Element* aElement,
                                          StyleChildrenIterator* aIterator) {
  MOZ_ASSERT(aElement);
  MOZ_ASSERT(aIterator);
  new (aIterator) StyleChildrenIterator(aElement);
}

void Gecko_DestroyStyleChildrenIterator(StyleChildrenIterator* aIterator) {
  MOZ_ASSERT(aIterator);

  aIterator->~StyleChildrenIterator();
}

const nsINode* Gecko_GetNextStyleChild(StyleChildrenIterator* aIterator) {
  MOZ_ASSERT(aIterator);
  return aIterator->GetNextChild();
}

bool Gecko_VisitedStylesEnabled(const Document* aDoc) {
  MOZ_ASSERT(aDoc);
  MOZ_ASSERT(NS_IsMainThread());

  if (!StaticPrefs::layout_css_visited_links_enabled()) {
    return false;
  }

  if (aDoc->IsBeingUsedAsImage()) {
    return false;
  }

  nsILoadContext* loadContext = aDoc->GetLoadContext();
  if (loadContext && loadContext->UsePrivateBrowsing()) {
    return false;
  }

  return true;
}

ElementState::InternalType Gecko_ElementState(const Element* aElement) {
  return aElement->StyleState().GetInternalValue();
}

bool Gecko_IsRootElement(const Element* aElement) {
  return aElement->OwnerDoc()->GetRootElement() == aElement;
}

void Gecko_NoteDirtyElement(const Element* aElement) {
  MOZ_ASSERT(NS_IsMainThread());
  const_cast<Element*>(aElement)->NoteDirtyForServo();
}

void Gecko_NoteDirtySubtreeForInvalidation(const Element* aElement) {
  MOZ_ASSERT(NS_IsMainThread());
  const_cast<Element*>(aElement)->NoteDirtySubtreeForServo();
}

void Gecko_NoteAnimationOnlyDirtyElement(const Element* aElement) {
  MOZ_ASSERT(NS_IsMainThread());
  const_cast<Element*>(aElement)->NoteAnimationOnlyDirtyForServo();
}

bool Gecko_AnimationNameMayBeReferencedFromStyle(
    const nsPresContext* aPresContext, nsAtom* aName) {
  MOZ_ASSERT(aPresContext);
  return aPresContext->AnimationManager()->AnimationMayBeReferenced(aName);
}

float Gecko_GetScrollbarInlineSize(const nsPresContext* aPc) {
  MOZ_ASSERT(aPc);
  AutoWriteLock guard(*sServoFFILock);  // We read some look&feel values.
  auto overlay = aPc->UseOverlayScrollbars() ? nsITheme::Overlay::Yes
                                             : nsITheme::Overlay::No;
  LayoutDeviceIntCoord size =
      aPc->Theme()->GetScrollbarSize(aPc, StyleScrollbarWidth::Auto, overlay);
  return aPc->DevPixelsToFloatCSSPixels(size);
}

PseudoStyleType Gecko_GetImplementedPseudo(const Element* aElement) {
  return aElement->GetPseudoElementType();
}

uint32_t Gecko_CalcStyleDifference(const ComputedStyle* aOldStyle,
                                   const ComputedStyle* aNewStyle,
                                   bool* aAnyStyleStructChanged,
                                   bool* aOnlyResetStructsChanged) {
  MOZ_ASSERT(aOldStyle);
  MOZ_ASSERT(aNewStyle);

  uint32_t equalStructs;
  nsChangeHint result =
      aOldStyle->CalcStyleDifference(*aNewStyle, &equalStructs);

  *aAnyStyleStructChanged =
      equalStructs != StyleStructConstants::kAllStructsMask;

  const auto kInheritedStructsMask =
      StyleStructConstants::kInheritedStructsMask;
  *aOnlyResetStructsChanged =
      (equalStructs & kInheritedStructsMask) == kInheritedStructsMask;

  return result;
}

nscoord Gecko_CalcLineHeight(const StyleLineHeight* aLh,
                             const nsPresContext* aPc, bool aVertical,
                             const nsStyleFont* aAgainstFont,
                             const mozilla::dom::Element* aElement) {
  // Normal line-height depends on font metrics.
  AutoWriteLock guard(*sServoFFILock);
  return ReflowInput::CalcLineHeight(*aLh, *aAgainstFont,
                                     const_cast<nsPresContext*>(aPc), aVertical,
                                     aElement, NS_UNCONSTRAINEDSIZE, 1.0f);
}

const ServoElementSnapshot* Gecko_GetElementSnapshot(
    const ServoElementSnapshotTable* aTable, const Element* aElement) {
  MOZ_ASSERT(aTable);
  MOZ_ASSERT(aElement);

  return aTable->Get(const_cast<Element*>(aElement));
}

bool Gecko_HaveSeenPtr(SeenPtrs* aTable, const void* aPtr) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(aTable);
  // Empty Rust allocations are indicated by small values up to the alignment
  // of the relevant type. We shouldn't see anything like that here.
  MOZ_ASSERT(uintptr_t(aPtr) > 16);

  return aTable->HaveSeenPtr(aPtr);
}

const StyleLockedDeclarationBlock* Gecko_GetStyleAttrDeclarationBlock(
    const Element* aElement) {
  DeclarationBlock* decl = aElement->GetInlineStyleDeclaration();
  if (!decl) {
    return nullptr;
  }
  return decl->Raw();
}

void Gecko_UnsetDirtyStyleAttr(const Element* aElement) {
  DeclarationBlock* decl = aElement->GetInlineStyleDeclaration();
  if (!decl) {
    return;
  }
  decl->UnsetDirty();
}

const StyleLockedDeclarationBlock*
Gecko_GetHTMLPresentationAttrDeclarationBlock(const Element* aElement) {
  const nsMappedAttributes* attrs = aElement->GetMappedAttributes();
  if (!attrs) {
    if (auto* svg = SVGElement::FromNodeOrNull(aElement)) {
      if (const auto* decl = svg->GetContentDeclarationBlock()) {
        return decl->Raw();
      }
    }
    return nullptr;
  }

  return attrs->GetServoStyle();
}

const StyleLockedDeclarationBlock* Gecko_GetExtraContentStyleDeclarations(
    const Element* aElement) {
  if (const auto* cell = HTMLTableCellElement::FromNode(aElement)) {
    if (nsMappedAttributes* attrs =
            cell->GetMappedAttributesInheritedFromTable()) {
      return attrs->GetServoStyle();
    }
  } else if (const auto* img = HTMLImageElement::FromNode(aElement)) {
    if (const auto* attrs = img->GetMappedAttributesFromSource()) {
      return attrs->GetServoStyle();
    }
  }
  return nullptr;
}

const StyleLockedDeclarationBlock* Gecko_GetUnvisitedLinkAttrDeclarationBlock(
    const Element* aElement) {
  nsHTMLStyleSheet* sheet = aElement->OwnerDoc()->GetAttributeStyleSheet();
  if (!sheet) {
    return nullptr;
  }

  return sheet->GetServoUnvisitedLinkDecl();
}

StyleSheet* Gecko_StyleSheet_Clone(const StyleSheet* aSheet,
                                   const StyleSheet* aNewParentSheet) {
  MOZ_ASSERT(aSheet);
  MOZ_ASSERT(aSheet->GetParentSheet(), "Should only be used for @import");
  MOZ_ASSERT(aNewParentSheet, "Wat");

  RefPtr<StyleSheet> newSheet = aSheet->Clone(nullptr, nullptr);

  // NOTE(emilio): This code runs in the StylesheetInner constructor, which
  // means that the inner pointer of `aNewParentSheet` still points to the old
  // one.
  //
  // So we _don't_ update neither the parent pointer of the stylesheet, nor the
  // child list (yet). This is fixed up in that same constructor.
  return static_cast<StyleSheet*>(newSheet.forget().take());
}

void Gecko_StyleSheet_AddRef(const StyleSheet* aSheet) {
  MOZ_ASSERT(NS_IsMainThread());
  const_cast<StyleSheet*>(aSheet)->AddRef();
}

void Gecko_StyleSheet_Release(const StyleSheet* aSheet) {
  MOZ_ASSERT(NS_IsMainThread());
  const_cast<StyleSheet*>(aSheet)->Release();
}

const StyleLockedDeclarationBlock* Gecko_GetVisitedLinkAttrDeclarationBlock(
    const Element* aElement) {
  nsHTMLStyleSheet* sheet = aElement->OwnerDoc()->GetAttributeStyleSheet();
  if (!sheet) {
    return nullptr;
  }

  return sheet->GetServoVisitedLinkDecl();
}

const StyleLockedDeclarationBlock* Gecko_GetActiveLinkAttrDeclarationBlock(
    const Element* aElement) {
  nsHTMLStyleSheet* sheet = aElement->OwnerDoc()->GetAttributeStyleSheet();
  if (!sheet) {
    return nullptr;
  }

  return sheet->GetServoActiveLinkDecl();
}

bool Gecko_GetAnimationRule(const Element* aElement,
                            EffectCompositor::CascadeLevel aCascadeLevel,
                            StyleAnimationValueMap* aAnimationValues) {
  MOZ_ASSERT(aElement);

  Document* doc = aElement->GetComposedDoc();
  if (!doc) {
    return false;
  }
  nsPresContext* presContext = doc->GetPresContext();
  if (!presContext) {
    return false;
  }

  const auto [element, pseudoType] =
      AnimationUtils::GetElementPseudoPair(aElement);
  return presContext->EffectCompositor()->GetServoAnimationRule(
      element, pseudoType, aCascadeLevel, aAnimationValues);
}

bool Gecko_StyleAnimationsEquals(const nsStyleAutoArray<StyleAnimation>* aA,
                                 const nsStyleAutoArray<StyleAnimation>* aB) {
  return *aA == *aB;
}

bool Gecko_StyleScrollTimelinesEquals(
    const nsStyleAutoArray<StyleScrollTimeline>* aA,
    const nsStyleAutoArray<StyleScrollTimeline>* aB) {
  return *aA == *aB;
}

bool Gecko_StyleViewTimelinesEquals(
    const nsStyleAutoArray<StyleViewTimeline>* aA,
    const nsStyleAutoArray<StyleViewTimeline>* aB) {
  return *aA == *aB;
}

void Gecko_CopyAnimationNames(nsStyleAutoArray<StyleAnimation>* aDest,
                              const nsStyleAutoArray<StyleAnimation>* aSrc) {
  size_t srcLength = aSrc->Length();
  aDest->EnsureLengthAtLeast(srcLength);

  for (size_t index = 0; index < srcLength; index++) {
    (*aDest)[index].SetName((*aSrc)[index].GetName());
  }
}

void Gecko_SetAnimationName(StyleAnimation* aStyleAnimation, nsAtom* aAtom) {
  MOZ_ASSERT(aStyleAnimation);
  aStyleAnimation->SetName(already_AddRefed<nsAtom>(aAtom));
}

void Gecko_UpdateAnimations(const Element* aElement,
                            const ComputedStyle* aOldComputedData,
                            const ComputedStyle* aComputedData,
                            UpdateAnimationsTasks aTasks) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(aElement);

  if (!aElement->IsInComposedDoc()) {
    return;
  }

  nsPresContext* presContext = nsContentUtils::GetContextForContent(aElement);
  if (!presContext || !presContext->IsDynamic()) {
    return;
  }

  nsAutoAnimationMutationBatch mb(aElement->OwnerDoc());

  const auto [element, pseudoType] =
      AnimationUtils::GetElementPseudoPair(aElement);

  // Handle scroll/view timelines first because CSS animations may refer to the
  // timeline defined by itself.
  if (aTasks & UpdateAnimationsTasks::ScrollTimelines) {
    presContext->TimelineManager()->UpdateTimelines(
        const_cast<Element*>(element), pseudoType, aComputedData,
        TimelineManager::ProgressTimelineType::Scroll);
  }

  if (aTasks & UpdateAnimationsTasks::ViewTimelines) {
    presContext->TimelineManager()->UpdateTimelines(
        const_cast<Element*>(element), pseudoType, aComputedData,
        TimelineManager::ProgressTimelineType::View);
  }

  if (aTasks & UpdateAnimationsTasks::CSSAnimations) {
    presContext->AnimationManager()->UpdateAnimations(
        const_cast<Element*>(element), pseudoType, aComputedData);
  }

  // aComputedData might be nullptr if the target element is now in a
  // display:none subtree. We still call Gecko_UpdateAnimations in this case
  // because we need to stop CSS animations in the display:none subtree.
  // However, we don't need to update transitions since they are stopped by
  // RestyleManager::AnimationsWithDestroyedFrame so we just return early
  // here.
  if (!aComputedData) {
    return;
  }

  if (aTasks & UpdateAnimationsTasks::CSSTransitions) {
    MOZ_ASSERT(aOldComputedData);
    presContext->TransitionManager()->UpdateTransitions(
        const_cast<Element*>(element), pseudoType, *aOldComputedData,
        *aComputedData);
  }

  if (aTasks & UpdateAnimationsTasks::EffectProperties) {
    presContext->EffectCompositor()->UpdateEffectProperties(
        aComputedData, const_cast<Element*>(element), pseudoType);
  }

  if (aTasks & UpdateAnimationsTasks::CascadeResults) {
    EffectSet* effectSet = EffectSet::Get(element, pseudoType);
    // CSS animations/transitions might have been destroyed as part of the above
    // steps so before updating cascade results, we check if there are still any
    // animations to update.
    if (effectSet) {
      // We call UpdateCascadeResults directly (intead of
      // MaybeUpdateCascadeResults) since we know for sure that the cascade has
      // changed, but we were unable to call MarkCascadeUpdated when we noticed
      // it since we avoid mutating state as part of the Servo parallel
      // traversal.
      presContext->EffectCompositor()->UpdateCascadeResults(
          *effectSet, const_cast<Element*>(element), pseudoType);
    }
  }

  if (aTasks & UpdateAnimationsTasks::DisplayChangedFromNone) {
    presContext->EffectCompositor()->RequestRestyle(
        const_cast<Element*>(element), pseudoType,
        EffectCompositor::RestyleType::Standard,
        EffectCompositor::CascadeLevel::Animations);
  }
}

size_t Gecko_GetAnimationEffectCount(const Element* aElementOrPseudo) {
  const auto [element, pseudoType] =
      AnimationUtils::GetElementPseudoPair(aElementOrPseudo);

  EffectSet* effectSet = EffectSet::Get(element, pseudoType);
  return effectSet ? effectSet->Count() : 0;
}

bool Gecko_ElementHasAnimations(const Element* aElement) {
  const auto [element, pseudoType] =
      AnimationUtils::GetElementPseudoPair(aElement);
  return !!EffectSet::Get(element, pseudoType);
}

bool Gecko_ElementHasCSSAnimations(const Element* aElement) {
  const auto [element, pseudoType] =
      AnimationUtils::GetElementPseudoPair(aElement);
  auto* collection =
      nsAnimationManager::CSSAnimationCollection::Get(element, pseudoType);
  return collection && !collection->mAnimations.IsEmpty();
}

bool Gecko_ElementHasCSSTransitions(const Element* aElement) {
  const auto [element, pseudoType] =
      AnimationUtils::GetElementPseudoPair(aElement);
  auto* collection =
      nsTransitionManager::CSSTransitionCollection::Get(element, pseudoType);
  return collection && !collection->mAnimations.IsEmpty();
}

size_t Gecko_ElementTransitions_Length(const Element* aElement) {
  const auto [element, pseudoType] =
      AnimationUtils::GetElementPseudoPair(aElement);
  auto* collection =
      nsTransitionManager::CSSTransitionCollection::Get(element, pseudoType);
  return collection ? collection->mAnimations.Length() : 0;
}

static CSSTransition* GetCurrentTransitionAt(const Element* aElement,
                                             size_t aIndex) {
  const auto [element, pseudoType] =
      AnimationUtils::GetElementPseudoPair(aElement);
  auto* collection =
      nsTransitionManager::CSSTransitionCollection ::Get(element, pseudoType);
  if (!collection) {
    return nullptr;
  }
  return collection->mAnimations.SafeElementAt(aIndex);
}

nsCSSPropertyID Gecko_ElementTransitions_PropertyAt(const Element* aElement,
                                                    size_t aIndex) {
  CSSTransition* transition = GetCurrentTransitionAt(aElement, aIndex);
  return transition ? transition->TransitionProperty()
                    : nsCSSPropertyID::eCSSProperty_UNKNOWN;
}

const StyleAnimationValue* Gecko_ElementTransitions_EndValueAt(
    const Element* aElement, size_t aIndex) {
  CSSTransition* transition = GetCurrentTransitionAt(aElement, aIndex);
  return transition ? transition->ToValue().mServo.get() : nullptr;
}

double Gecko_GetProgressFromComputedTiming(const ComputedTiming* aTiming) {
  return aTiming->mProgress.Value();
}

double Gecko_GetPositionInSegment(const AnimationPropertySegment* aSegment,
                                  double aProgress, bool aBeforeFlag) {
  MOZ_ASSERT(aSegment->mFromKey < aSegment->mToKey,
             "The segment from key should be less than to key");

  double positionInSegment = (aProgress - aSegment->mFromKey) /
                             // To avoid floating precision inaccuracies, make
                             // sure we calculate both the numerator and
                             // denominator using double precision.
                             (double(aSegment->mToKey) - aSegment->mFromKey);

  return StyleComputedTimingFunction::GetPortion(
      aSegment->mTimingFunction, positionInSegment, aBeforeFlag);
}

const StyleAnimationValue* Gecko_AnimationGetBaseStyle(
    const RawServoAnimationValueTable* aBaseStyles, nsCSSPropertyID aProperty) {
  auto base = reinterpret_cast<
      const nsRefPtrHashtable<nsUint32HashKey, StyleAnimationValue>*>(
      aBaseStyles);
  return base->GetWeak(aProperty);
}

void Gecko_FillAllImageLayers(nsStyleImageLayers* aLayers, uint32_t aMaxLen) {
  aLayers->FillAllLayers(aMaxLen);
}

bool Gecko_IsDocumentBody(const Element* aElement) {
  Document* doc = aElement->GetUncomposedDoc();
  return doc && doc->GetBodyElement() == aElement;
}

nscolor Gecko_ComputeSystemColor(StyleSystemColor aColor, const Document* aDoc,
                                 const StyleColorScheme* aStyle) {
  auto colorScheme = LookAndFeel::ColorSchemeForStyle(*aDoc, aStyle->bits);

  const auto& colors = PreferenceSheet::PrefsFor(*aDoc).ColorsFor(colorScheme);
  switch (aColor) {
    case StyleSystemColor::Canvastext:
      return colors.mDefault;
    case StyleSystemColor::Canvas:
      return colors.mDefaultBackground;
    case StyleSystemColor::Linktext:
      return colors.mLink;
    case StyleSystemColor::Activetext:
      return colors.mActiveLink;
    case StyleSystemColor::Visitedtext:
      return colors.mVisitedLink;
    default:
      break;
  }

  auto useStandins = LookAndFeel::ShouldUseStandins(*aDoc, aColor);

  AutoWriteLock guard(*sServoFFILock);
  return LookAndFeel::Color(aColor, colorScheme, useStandins);
}

int32_t Gecko_GetLookAndFeelInt(int32_t aId) {
  auto intId = static_cast<LookAndFeel::IntID>(aId);
  AutoWriteLock guard(*sServoFFILock);
  return LookAndFeel::GetInt(intId);
}

float Gecko_GetLookAndFeelFloat(int32_t aId) {
  auto id = static_cast<LookAndFeel::FloatID>(aId);
  AutoWriteLock guard(*sServoFFILock);
  return LookAndFeel::GetFloat(id);
}

bool Gecko_MatchLang(const Element* aElement, nsAtom* aOverrideLang,
                     bool aHasOverrideLang, const char16_t* aValue) {
  MOZ_ASSERT(!(aOverrideLang && !aHasOverrideLang),
             "aHasOverrideLang should only be set when aOverrideLang is null");
  MOZ_ASSERT(aValue, "null lang parameter");
  if (!aValue || !*aValue) {
    return false;
  }

  // We have to determine the language of the current element.  Since
  // this is currently no property and since the language is inherited
  // from the parent we have to be prepared to look at all parent
  // nodes.  The language itself is encoded in the LANG attribute.
  if (auto* language = aHasOverrideLang ? aOverrideLang : aElement->GetLang()) {
    return nsStyleUtil::LangTagCompare(nsAtomCString(language),
                                       NS_ConvertUTF16toUTF8(aValue));
  }

  // Try to get the language from the HTTP header or if this
  // is missing as well from the preferences.
  // The content language can be a comma-separated list of
  // language codes.
  nsAutoString language;
  aElement->OwnerDoc()->GetContentLanguage(language);

  NS_ConvertUTF16toUTF8 langString(aValue);
  language.StripWhitespace();
  for (auto const& lang : language.Split(char16_t(','))) {
    if (nsStyleUtil::LangTagCompare(NS_ConvertUTF16toUTF8(lang), langString)) {
      return true;
    }
  }
  return false;
}

nsAtom* Gecko_GetXMLLangValue(const Element* aElement) {
  const nsAttrValue* attr =
      aElement->GetParsedAttr(nsGkAtoms::lang, kNameSpaceID_XML);

  if (!attr) {
    return nullptr;
  }

  MOZ_ASSERT(attr->Type() == nsAttrValue::eAtom);

  RefPtr<nsAtom> atom = attr->GetAtomValue();
  return atom.forget().take();
}

const PreferenceSheet::Prefs* Gecko_GetPrefSheetPrefs(const Document* aDoc) {
  return &PreferenceSheet::PrefsFor(*aDoc);
}

bool Gecko_IsTableBorderNonzero(const Element* aElement) {
  if (!aElement->IsHTMLElement(nsGkAtoms::table)) {
    return false;
  }
  const nsAttrValue* val = aElement->GetParsedAttr(nsGkAtoms::border);
  return val &&
         (val->Type() != nsAttrValue::eInteger || val->GetIntegerValue() != 0);
}

bool Gecko_IsBrowserFrame(const Element* aElement) {
  nsIMozBrowserFrame* browserFrame =
      const_cast<Element*>(aElement)->GetAsMozBrowserFrame();
  return browserFrame && browserFrame->GetReallyIsBrowser();
}

bool Gecko_IsSelectListBox(const Element* aElement) {
  const auto* select = HTMLSelectElement::FromNode(aElement);
  return select && !select->IsCombobox();
}

template <typename Implementor>
static nsAtom* LangValue(Implementor* aElement) {
  // TODO(emilio): Deduplicate a bit with nsIContent::GetLang().
  const nsAttrValue* attr =
      aElement->GetParsedAttr(nsGkAtoms::lang, kNameSpaceID_XML);
  if (!attr && aElement->SupportsLangAttr()) {
    attr = aElement->GetParsedAttr(nsGkAtoms::lang);
  }

  if (!attr) {
    return nullptr;
  }

  MOZ_ASSERT(attr->Type() == nsAttrValue::eAtom);
  RefPtr<nsAtom> atom = attr->GetAtomValue();
  return atom.forget().take();
}

template <typename Implementor, typename MatchFn>
static bool DoMatch(Implementor* aElement, nsAtom* aNS, nsAtom* aName,
                    MatchFn aMatch) {
  if (MOZ_LIKELY(aNS)) {
    int32_t ns = aNS == nsGkAtoms::_empty
                     ? kNameSpaceID_None
                     : nsNameSpaceManager::GetInstance()->GetNameSpaceID(
                           aNS, aElement->IsInChromeDocument());

    MOZ_ASSERT(ns == nsNameSpaceManager::GetInstance()->GetNameSpaceID(
                         aNS, aElement->IsInChromeDocument()));
    NS_ENSURE_TRUE(ns != kNameSpaceID_Unknown, false);
    const nsAttrValue* value = aElement->GetParsedAttr(aName, ns);
    return value && aMatch(value);
  }

  // No namespace means any namespace - we have to check them all. :-(
  BorrowedAttrInfo attrInfo;
  for (uint32_t i = 0; (attrInfo = aElement->GetAttrInfoAt(i)); ++i) {
    if (attrInfo.mName->LocalName() != aName) {
      continue;
    }
    if (aMatch(attrInfo.mValue)) {
      return true;
    }
  }
  return false;
}

template <typename Implementor>
static bool HasAttr(Implementor* aElement, nsAtom* aNS, nsAtom* aName) {
  auto match = [](const nsAttrValue* aValue) { return true; };
  return DoMatch(aElement, aNS, aName, match);
}

template <typename Implementor>
static bool AttrEquals(Implementor* aElement, nsAtom* aNS, nsAtom* aName,
                       nsAtom* aStr, bool aIgnoreCase) {
  auto match = [aStr, aIgnoreCase](const nsAttrValue* aValue) {
    return aValue->Equals(aStr, aIgnoreCase ? eIgnoreCase : eCaseMatters);
  };
  return DoMatch(aElement, aNS, aName, match);
}

#define WITH_COMPARATOR(ignore_case_, c_, expr_)                  \
  auto c_ = ignore_case_ ? nsASCIICaseInsensitiveStringComparator \
                         : nsTDefaultStringComparator<char16_t>;  \
  return expr_;

template <typename Implementor>
static bool AttrDashEquals(Implementor* aElement, nsAtom* aNS, nsAtom* aName,
                           nsAtom* aStr, bool aIgnoreCase) {
  auto match = [aStr, aIgnoreCase](const nsAttrValue* aValue) {
    nsAutoString str;
    aValue->ToString(str);
    WITH_COMPARATOR(
        aIgnoreCase, c,
        nsStyleUtil::DashMatchCompare(str, nsDependentAtomString(aStr), c))
  };
  return DoMatch(aElement, aNS, aName, match);
}

template <typename Implementor>
static bool AttrIncludes(Implementor* aElement, nsAtom* aNS, nsAtom* aName,
                         nsAtom* aStr, bool aIgnoreCase) {
  auto match = [aStr, aIgnoreCase](const nsAttrValue* aValue) {
    nsAutoString str;
    aValue->ToString(str);
    WITH_COMPARATOR(
        aIgnoreCase, c,
        nsStyleUtil::ValueIncludes(str, nsDependentAtomString(aStr), c))
  };
  return DoMatch(aElement, aNS, aName, match);
}

template <typename Implementor>
static bool AttrHasSubstring(Implementor* aElement, nsAtom* aNS, nsAtom* aName,
                             nsAtom* aStr, bool aIgnoreCase) {
  auto match = [aStr, aIgnoreCase](const nsAttrValue* aValue) {
    return aValue->HasSubstring(nsDependentAtomString(aStr),
                                aIgnoreCase ? eIgnoreCase : eCaseMatters);
  };
  return DoMatch(aElement, aNS, aName, match);
}

template <typename Implementor>
static bool AttrHasPrefix(Implementor* aElement, nsAtom* aNS, nsAtom* aName,
                          nsAtom* aStr, bool aIgnoreCase) {
  auto match = [aStr, aIgnoreCase](const nsAttrValue* aValue) {
    return aValue->HasPrefix(nsDependentAtomString(aStr),
                             aIgnoreCase ? eIgnoreCase : eCaseMatters);
  };
  return DoMatch(aElement, aNS, aName, match);
}

template <typename Implementor>
static bool AttrHasSuffix(Implementor* aElement, nsAtom* aNS, nsAtom* aName,
                          nsAtom* aStr, bool aIgnoreCase) {
  auto match = [aStr, aIgnoreCase](const nsAttrValue* aValue) {
    return aValue->HasSuffix(nsDependentAtomString(aStr),
                             aIgnoreCase ? eIgnoreCase : eCaseMatters);
  };
  return DoMatch(aElement, aNS, aName, match);
}

#define SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS(prefix_, implementor_)      \
  nsAtom* prefix_##LangValue(implementor_ aElement) {                          \
    return LangValue(aElement);                                                \
  }                                                                            \
  bool prefix_##HasAttr(implementor_ aElement, nsAtom* aNS, nsAtom* aName) {   \
    return HasAttr(aElement, aNS, aName);                                      \
  }                                                                            \
  bool prefix_##AttrEquals(implementor_ aElement, nsAtom* aNS, nsAtom* aName,  \
                           nsAtom* aStr, bool aIgnoreCase) {                   \
    return AttrEquals(aElement, aNS, aName, aStr, aIgnoreCase);                \
  }                                                                            \
  bool prefix_##AttrDashEquals(implementor_ aElement, nsAtom* aNS,             \
                               nsAtom* aName, nsAtom* aStr,                    \
                               bool aIgnoreCase) {                             \
    return AttrDashEquals(aElement, aNS, aName, aStr, aIgnoreCase);            \
  }                                                                            \
  bool prefix_##AttrIncludes(implementor_ aElement, nsAtom* aNS,               \
                             nsAtom* aName, nsAtom* aStr, bool aIgnoreCase) {  \
    return AttrIncludes(aElement, aNS, aName, aStr, aIgnoreCase);              \
  }                                                                            \
  bool prefix_##AttrHasSubstring(implementor_ aElement, nsAtom* aNS,           \
                                 nsAtom* aName, nsAtom* aStr,                  \
                                 bool aIgnoreCase) {                           \
    return AttrHasSubstring(aElement, aNS, aName, aStr, aIgnoreCase);          \
  }                                                                            \
  bool prefix_##AttrHasPrefix(implementor_ aElement, nsAtom* aNS,              \
                              nsAtom* aName, nsAtom* aStr, bool aIgnoreCase) { \
    return AttrHasPrefix(aElement, aNS, aName, aStr, aIgnoreCase);             \
  }                                                                            \
  bool prefix_##AttrHasSuffix(implementor_ aElement, nsAtom* aNS,              \
                              nsAtom* aName, nsAtom* aStr, bool aIgnoreCase) { \
    return AttrHasSuffix(aElement, aNS, aName, aStr, aIgnoreCase);             \
  }

SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_, const Element*)
SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_Snapshot,
                                           const ServoElementSnapshot*)

#undef SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS

nsAtom* Gecko_Atomize(const char* aString, uint32_t aLength) {
  return NS_Atomize(nsDependentCSubstring(aString, aLength)).take();
}

nsAtom* Gecko_Atomize16(const nsAString* aString) {
  return NS_Atomize(*aString).take();
}

void Gecko_AddRefAtom(nsAtom* aAtom) { NS_ADDREF(aAtom); }

void Gecko_ReleaseAtom(nsAtom* aAtom) { NS_RELEASE(aAtom); }

void Gecko_nsFont_InitSystem(nsFont* aDest, StyleSystemFont aFontId,
                             const nsStyleFont* aFont,
                             const Document* aDocument) {
  const nsFont& defaultVariableFont =
      ThreadSafeGetDefaultVariableFont(*aDocument, aFont->mLanguage);

  // We have passed uninitialized memory to this function,
  // initialize it. We can't simply return an nsFont because then
  // we need to know its size beforehand. Servo cannot initialize nsFont
  // itself, so this will do.
  new (aDest) nsFont(defaultVariableFont);

  AutoWriteLock guard(*sServoFFILock);
  nsLayoutUtils::ComputeSystemFont(aDest, aFontId, defaultVariableFont,
                                   aDocument);
}

void Gecko_nsFont_Destroy(nsFont* aDest) { aDest->~nsFont(); }

StyleGenericFontFamily Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage(
    const Document* aDoc, nsAtom* aLanguage) {
  return ThreadSafeGetLangGroupFontPrefs(*aDoc, aLanguage)->GetDefaultGeneric();
}

Length Gecko_GetBaseSize(const Document* aDoc, nsAtom* aLang,
                         StyleGenericFontFamily aGeneric) {
  return ThreadSafeGetLangGroupFontPrefs(*aDoc, aLang)
      ->GetDefaultFont(aGeneric)
      ->size;
}

gfxFontFeatureValueSet* Gecko_ConstructFontFeatureValueSet() {
  return new gfxFontFeatureValueSet();
}

nsTArray<uint32_t>* Gecko_AppendFeatureValueHashEntry(
    gfxFontFeatureValueSet* aFontFeatureValues, nsAtom* aFamily,
    uint32_t aAlternate, nsAtom* aName) {
  MOZ_ASSERT(NS_IsMainThread());
  return aFontFeatureValues->AppendFeatureValueHashEntry(nsAtomCString(aFamily),
                                                         aName, aAlternate);
}

gfx::FontPaletteValueSet* Gecko_ConstructFontPaletteValueSet() {
  return new gfx::FontPaletteValueSet();
}

gfx::FontPaletteValueSet::PaletteValues* Gecko_AppendPaletteValueHashEntry(
    gfx::FontPaletteValueSet* aPaletteValueSet, nsAtom* aFamily,
    nsAtom* aName) {
  MOZ_ASSERT(NS_IsMainThread());
  return aPaletteValueSet->Insert(aName, nsAtomCString(aFamily));
}

void Gecko_SetFontPaletteBase(gfx::FontPaletteValueSet::PaletteValues* aValues,
                              int32_t aBasePaletteIndex) {
  aValues->mBasePalette = aBasePaletteIndex;
}

void Gecko_SetFontPaletteOverride(
    gfx::FontPaletteValueSet::PaletteValues* aValues, int32_t aIndex,
    StyleAbsoluteColor* aColor) {
  if (aIndex < 0) {
    return;
  }
  aValues->mOverrides.AppendElement(gfx::FontPaletteValueSet::OverrideColor{
      uint32_t(aIndex), gfx::sRGBColor::FromABGR(aColor->ToColor())});
}

void Gecko_CounterStyle_ToPtr(const StyleCounterStyle* aStyle,
                              CounterStylePtr* aPtr) {
  *aPtr = CounterStylePtr::FromStyle(*aStyle);
}

void Gecko_SetCounterStyleToNone(CounterStylePtr* aPtr) {
  *aPtr = nsGkAtoms::none;
}

void Gecko_SetCounterStyleToString(CounterStylePtr* aPtr,
                                   const nsACString* aSymbol) {
  *aPtr = new AnonymousCounterStyle(NS_ConvertUTF8toUTF16(*aSymbol));
}

void Gecko_CopyCounterStyle(CounterStylePtr* aDst,
                            const CounterStylePtr* aSrc) {
  *aDst = *aSrc;
}

nsAtom* Gecko_CounterStyle_GetName(const CounterStylePtr* aPtr) {
  return aPtr->IsAtom() ? aPtr->AsAtom() : nullptr;
}

const AnonymousCounterStyle* Gecko_CounterStyle_GetAnonymous(
    const CounterStylePtr* aPtr) {
  return aPtr->AsAnonymous();
}

void Gecko_EnsureTArrayCapacity(void* aArray, size_t aCapacity,
                                size_t aElemSize) {
  auto base =
      reinterpret_cast<nsTArray_base<nsTArrayInfallibleAllocator,
                                     nsTArray_RelocateUsingMemutils>*>(aArray);

  base->EnsureCapacity<nsTArrayInfallibleAllocator>(aCapacity, aElemSize);
}

void Gecko_ClearPODTArray(void* aArray, size_t aElementSize,
                          size_t aElementAlign) {
  auto base =
      reinterpret_cast<nsTArray_base<nsTArrayInfallibleAllocator,
                                     nsTArray_RelocateUsingMemutils>*>(aArray);

  base->template ShiftData<nsTArrayInfallibleAllocator>(
      0, base->Length(), 0, aElementSize, aElementAlign);
}

void Gecko_ResizeTArrayForStrings(nsTArray<nsString>* aArray,
                                  uint32_t aLength) {
  aArray->SetLength(aLength);
}

void Gecko_ResizeAtomArray(nsTArray<RefPtr<nsAtom>>* aArray, uint32_t aLength) {
  aArray->SetLength(aLength);
}

void Gecko_EnsureImageLayersLength(nsStyleImageLayers* aLayers, size_t aLen,
                                   nsStyleImageLayers::LayerType aLayerType) {
  size_t oldLength = aLayers->mLayers.Length();

  aLayers->mLayers.EnsureLengthAtLeast(aLen);

  for (size_t i = oldLength; i < aLen; ++i) {
    aLayers->mLayers[i].Initialize(aLayerType);
  }
}

template <typename StyleType>
static void EnsureStyleAutoArrayLength(StyleType* aArray, size_t aLen) {
  size_t oldLength = aArray->Length();

  aArray->EnsureLengthAtLeast(aLen);

  for (size_t i = oldLength; i < aLen; ++i) {
    (*aArray)[i].SetInitialValues();
  }
}

void Gecko_EnsureStyleAnimationArrayLength(void* aArray, size_t aLen) {
  auto* base = static_cast<nsStyleAutoArray<StyleAnimation>*>(aArray);
  EnsureStyleAutoArrayLength(base, aLen);
}

void Gecko_EnsureStyleTransitionArrayLength(void* aArray, size_t aLen) {
  auto* base = reinterpret_cast<nsStyleAutoArray<StyleTransition>*>(aArray);
  EnsureStyleAutoArrayLength(base, aLen);
}

void Gecko_EnsureStyleScrollTimelineArrayLength(void* aArray, size_t aLen) {
  auto* base = static_cast<nsStyleAutoArray<StyleScrollTimeline>*>(aArray);
  EnsureStyleAutoArrayLength(base, aLen);
}

void Gecko_EnsureStyleViewTimelineArrayLength(void* aArray, size_t aLen) {
  auto* base = static_cast<nsStyleAutoArray<StyleViewTimeline>*>(aArray);
  EnsureStyleAutoArrayLength(base, aLen);
}

enum class KeyframeSearchDirection {
  Forwards,
  Backwards,
};

enum class KeyframeInsertPosition {
  Prepend,
  LastForOffset,
};

static Keyframe* GetOrCreateKeyframe(
    nsTArray<Keyframe>* aKeyframes, float aOffset,
    const StyleComputedTimingFunction* aTimingFunction,
    const CompositeOperationOrAuto aComposition,
    KeyframeSearchDirection aSearchDirection,
    KeyframeInsertPosition aInsertPosition) {
  MOZ_ASSERT(aKeyframes, "The keyframe array should be valid");
  MOZ_ASSERT(aTimingFunction, "The timing function should be valid");
  MOZ_ASSERT(aOffset >= 0. && aOffset <= 1.,
             "The offset should be in the range of [0.0, 1.0]");

  size_t keyframeIndex;
  switch (aSearchDirection) {
    case KeyframeSearchDirection::Forwards:
      if (nsAnimationManager::FindMatchingKeyframe(
              *aKeyframes, aOffset, *aTimingFunction, aComposition,
              keyframeIndex)) {
        return &(*aKeyframes)[keyframeIndex];
      }
      break;
    case KeyframeSearchDirection::Backwards:
      if (nsAnimationManager::FindMatchingKeyframe(
              Reversed(*aKeyframes), aOffset, *aTimingFunction, aComposition,
              keyframeIndex)) {
        return &(*aKeyframes)[aKeyframes->Length() - 1 - keyframeIndex];
      }
      keyframeIndex = aKeyframes->Length() - 1;
      break;
  }

  Keyframe* keyframe = aKeyframes->InsertElementAt(
      aInsertPosition == KeyframeInsertPosition::Prepend ? 0 : keyframeIndex);
  keyframe->mOffset.emplace(aOffset);
  if (!aTimingFunction->IsLinearKeyword()) {
    keyframe->mTimingFunction.emplace(*aTimingFunction);
  }
  keyframe->mComposite = aComposition;

  return keyframe;
}

Keyframe* Gecko_GetOrCreateKeyframeAtStart(
    nsTArray<Keyframe>* aKeyframes, float aOffset,
    const StyleComputedTimingFunction* aTimingFunction,
    const CompositeOperationOrAuto aComposition) {
  MOZ_ASSERT(aKeyframes->IsEmpty() ||
                 aKeyframes->ElementAt(0).mOffset.value() >= aOffset,
             "The offset should be less than or equal to the first keyframe's "
             "offset if there are exisiting keyframes");

  return GetOrCreateKeyframe(aKeyframes, aOffset, aTimingFunction, aComposition,
                             KeyframeSearchDirection::Forwards,
                             KeyframeInsertPosition::Prepend);
}

Keyframe* Gecko_GetOrCreateInitialKeyframe(
    nsTArray<Keyframe>* aKeyframes,
    const StyleComputedTimingFunction* aTimingFunction,
    const CompositeOperationOrAuto aComposition) {
  return GetOrCreateKeyframe(aKeyframes, 0., aTimingFunction, aComposition,
                             KeyframeSearchDirection::Forwards,
                             KeyframeInsertPosition::LastForOffset);
}

Keyframe* Gecko_GetOrCreateFinalKeyframe(
    nsTArray<Keyframe>* aKeyframes,
    const StyleComputedTimingFunction* aTimingFunction,
    const CompositeOperationOrAuto aComposition) {
  return GetOrCreateKeyframe(aKeyframes, 1., aTimingFunction, aComposition,
                             KeyframeSearchDirection::Backwards,
                             KeyframeInsertPosition::LastForOffset);
}

PropertyValuePair* Gecko_AppendPropertyValuePair(
    nsTArray<PropertyValuePair>* aProperties, nsCSSPropertyID aProperty) {
  MOZ_ASSERT(aProperties);
  MOZ_ASSERT(aProperty == eCSSPropertyExtra_variable ||
             !nsCSSProps::PropHasFlags(aProperty, CSSPropFlags::IsLogical));
  return aProperties->AppendElement(PropertyValuePair{aProperty});
}

void Gecko_GetComputedURLSpec(const StyleComputedUrl* aURL, nsCString* aOut) {
  MOZ_ASSERT(aURL);
  MOZ_ASSERT(aOut);
  if (aURL->IsLocalRef()) {
    aOut->Assign(aURL->SpecifiedSerialization());
    return;
  }
  Gecko_GetComputedImageURLSpec(aURL, aOut);
}

void Gecko_GetComputedImageURLSpec(const StyleComputedUrl* aURL,
                                   nsCString* aOut) {
  if (aURL->IsLocalRef() &&
      StaticPrefs::layout_css_computed_style_dont_resolve_image_local_refs()) {
    aOut->Assign(aURL->SpecifiedSerialization());
    return;
  }

  if (nsIURI* uri = aURL->GetURI()) {
    nsresult rv = uri->GetSpec(*aOut);
    if (NS_SUCCEEDED(rv)) {
      return;
    }
  }

  aOut->AssignLiteral("about:invalid");
}

bool Gecko_IsSupportedImageMimeType(const uint8_t* aMimeType,
                                    const uint32_t aLen) {
  nsDependentCSubstring mime(reinterpret_cast<const char*>(aMimeType), aLen);
  return imgLoader::SupportImageWithMimeType(
      mime, AcceptedMimeTypes::IMAGES_AND_DOCUMENTS);
}

void Gecko_nsIURI_Debug(nsIURI* aURI, nsCString* aOut) {
  // TODO(emilio): Do we have more useful stuff to put here, maybe?
  if (aURI) {
    *aOut = aURI->GetSpecOrDefault();
  }
}

// XXX Implemented by hand because even though it's thread-safe, only the
// subclasses have the HasThreadSafeRefCnt bits.
void Gecko_AddRefnsIURIArbitraryThread(nsIURI* aPtr) { NS_ADDREF(aPtr); }
void Gecko_ReleasensIURIArbitraryThread(nsIURI* aPtr) { NS_RELEASE(aPtr); }

void Gecko_nsIReferrerInfo_Debug(nsIReferrerInfo* aReferrerInfo,
                                 nsCString* aOut) {
  if (aReferrerInfo) {
    if (nsCOMPtr<nsIURI> referrer = aReferrerInfo->GetComputedReferrer()) {
      *aOut = referrer->GetSpecOrDefault();
    }
  }
}

template <typename ElementLike>
void DebugListAttributes(const ElementLike& aElement, nsCString& aOut) {
  const uint32_t kMaxAttributeLength = 40;

  uint32_t i = 0;
  while (BorrowedAttrInfo info = aElement.GetAttrInfoAt(i++)) {
    aOut.AppendLiteral(" ");
    if (nsAtom* prefix = info.mName->GetPrefix()) {
      aOut.Append(NS_ConvertUTF16toUTF8(nsDependentAtomString(prefix)));
      aOut.AppendLiteral(":");
    }
    aOut.Append(
        NS_ConvertUTF16toUTF8(nsDependentAtomString(info.mName->LocalName())));
    if (!info.mValue) {
      continue;
    }
    aOut.AppendLiteral("=\"");
    nsAutoString value;
    info.mValue->ToString(value);
    if (value.Length() > kMaxAttributeLength) {
      value.Truncate(kMaxAttributeLength - 3);
      value.AppendLiteral("...");
    }
    aOut.Append(NS_ConvertUTF16toUTF8(value));
    aOut.AppendLiteral("\"");
  }
}

void Gecko_Element_DebugListAttributes(const Element* aElement,
                                       nsCString* aOut) {
  DebugListAttributes(*aElement, *aOut);
}

void Gecko_Snapshot_DebugListAttributes(const ServoElementSnapshot* aSnapshot,
                                        nsCString* aOut) {
  DebugListAttributes(*aSnapshot, *aOut);
}

NS_IMPL_THREADSAFE_FFI_REFCOUNTING(URLExtraData, URLExtraData);

void Gecko_nsStyleFont_SetLang(nsStyleFont* aFont, nsAtom* aAtom) {
  aFont->mLanguage = dont_AddRef(aAtom);
  aFont->mExplicitLanguage = true;
}

void Gecko_nsStyleFont_CopyLangFrom(nsStyleFont* aFont,
                                    const nsStyleFont* aSource) {
  aFont->mLanguage = aSource->mLanguage;
}

Length Gecko_nsStyleFont_ComputeMinSize(const nsStyleFont* aFont,
                                        const Document* aDocument) {
  // Don't change font-size:0, since that would un-hide hidden text.
  if (aFont->mSize.IsZero()) {
    return {0};
  }
  // Don't change it for docs where we don't enable the min-font-size.
  if (!aFont->MinFontSizeEnabled()) {
    return {0};
  }
  Length minFontSize;
  bool needsCache = false;

  auto MinFontSize = [&](bool* aNeedsToCache) {
    const auto* prefs =
        aDocument->GetFontPrefsForLang(aFont->mLanguage, aNeedsToCache);
    return prefs ? prefs->mMinimumFontSize : Length{0};
  };

  {
    AutoReadLock guard(*sServoFFILock);
    minFontSize = MinFontSize(&needsCache);
  }

  if (needsCache) {
    AutoWriteLock guard(*sServoFFILock);
    minFontSize = MinFontSize(nullptr);
  }

  if (minFontSize.ToCSSPixels() <= 0.0f) {
    return {0};
  }

  minFontSize.ScaleBy(aFont->mMinFontSizeRatio);
  minFontSize.ScaleBy(1.0f / 100.0f);
  return minFontSize;
}

static StaticRefPtr<UACacheReporter> gUACacheReporter;

namespace mozilla {

void InitializeServo() {
  URLExtraData::Init();
  Servo_Initialize(URLExtraData::Dummy(), URLExtraData::DummyChrome());

  gUACacheReporter = new UACacheReporter();
  RegisterWeakMemoryReporter(gUACacheReporter);

  sServoFFILock = new RWLock("Servo::FFILock");
}

void ShutdownServo() {
  MOZ_ASSERT(sServoFFILock);

  UnregisterWeakMemoryReporter(gUACacheReporter);
  gUACacheReporter = nullptr;

  sServoFFILock = nullptr;
  Servo_Shutdown();

  URLExtraData::Shutdown();
}

void AssertIsMainThreadOrServoFontMetricsLocked() {
  if (!NS_IsMainThread()) {
    MOZ_ASSERT(sServoFFILock &&
               sServoFFILock->LockedForWritingByCurrentThread());
  }
}

}  // namespace mozilla

GeckoFontMetrics Gecko_GetFontMetrics(const nsPresContext* aPresContext,
                                      bool aIsVertical,
                                      const nsStyleFont* aFont,
                                      Length aFontSize, bool aUseUserFontSet,
                                      bool aRetrieveMathScales) {
  AutoWriteLock guard(*sServoFFILock);

  // Getting font metrics can require some main thread only work to be
  // done, such as work that needs to touch non-threadsafe refcounted
  // objects (like the DOM FontFace/FontFaceSet objects), network loads, etc.
  //
  // To handle this work, font code checks whether we are in a Servo traversal
  // and if so, appends PostTraversalTasks to the current ServoStyleSet
  // to be performed immediately after the traversal is finished.  This
  // works well for starting downloadable font loads, since we don't have
  // those fonts available to get metrics for anyway.  Platform fonts and
  // ArrayBuffer-backed FontFace objects are handled synchronously.

  nsPresContext* presContext = const_cast<nsPresContext*>(aPresContext);
  RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetMetricsFor(
      presContext, aIsVertical, aFont, aFontSize, aUseUserFontSet);
  auto* fontGroup = fm->GetThebesFontGroup();
  auto metrics = fontGroup->GetMetricsForCSSUnits(fm->Orientation());

  float scriptPercentScaleDown = 0;
  float scriptScriptPercentScaleDown = 0;
  if (aRetrieveMathScales) {
    RefPtr<gfxFont> font = fontGroup->GetFirstValidFont();
    if (font->TryGetMathTable()) {
      scriptPercentScaleDown = static_cast<float>(
          font->MathTable()->Constant(gfxMathTable::ScriptPercentScaleDown));
      scriptScriptPercentScaleDown =
          static_cast<float>(font->MathTable()->Constant(
              gfxMathTable::ScriptScriptPercentScaleDown));
    }
  }

  int32_t d2a = aPresContext->AppUnitsPerDevPixel();
  auto ToLength = [](nscoord aLen) {
    return Length::FromPixels(CSSPixel::FromAppUnits(aLen));
  };
  return {ToLength(NS_round(metrics.xHeight * d2a)),
          ToLength(NS_round(metrics.zeroWidth * d2a)),
          ToLength(NS_round(metrics.capHeight * d2a)),
          ToLength(NS_round(metrics.ideographicWidth * d2a)),
          ToLength(NS_round(metrics.maxAscent * d2a)),
          scriptPercentScaleDown,
          scriptScriptPercentScaleDown};
}

NS_IMPL_THREADSAFE_FFI_REFCOUNTING(SheetLoadDataHolder, SheetLoadDataHolder);

void Gecko_StyleSheet_FinishAsyncParse(
    SheetLoadDataHolder* aData,
    StyleStrong<StyleStylesheetContents> aSheetContents,
    StyleUseCounters* aUseCounters) {
  UniquePtr<StyleUseCounters> useCounters(aUseCounters);
  RefPtr<SheetLoadDataHolder> loadData = aData;
  RefPtr<StyleStylesheetContents> sheetContents = aSheetContents.Consume();
  NS_DispatchToMainThreadQueue(
      NS_NewRunnableFunction(
          __func__,
          [d = std::move(loadData), contents = std::move(sheetContents),
           counters = std::move(useCounters)]() mutable {
            MOZ_ASSERT(NS_IsMainThread());
            SheetLoadData* data = d->get();
            data->mSheet->FinishAsyncParse(contents.forget(),
                                           std::move(counters));
          }),
      EventQueuePriority::RenderBlocking);
}

static already_AddRefed<StyleSheet> LoadImportSheet(
    Loader* aLoader, StyleSheet* aParent, SheetLoadData* aParentLoadData,
    LoaderReusableStyleSheets* aReusableSheets, const StyleCssUrl& aURL,
    already_AddRefed<StyleLockedMediaList> aMediaList) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(aLoader, "Should've catched this before");
  MOZ_ASSERT(aParent, "Only used for @import, so parent should exist!");

  RefPtr<MediaList> media = new MediaList(std::move(aMediaList));
  nsCOMPtr<nsIURI> uri = aURL.GetURI();
  nsresult rv = uri ? NS_OK : NS_ERROR_FAILURE;

  size_t previousSheetCount = aParent->ChildSheets().Length();
  if (NS_SUCCEEDED(rv)) {
    // TODO(emilio): We should probably make LoadChildSheet return the
    // stylesheet rather than the return code.
    rv = aLoader->LoadChildSheet(*aParent, aParentLoadData, uri, media,
                                 aReusableSheets);
  }

  if (NS_FAILED(rv) || previousSheetCount == aParent->ChildSheets().Length()) {
    // Servo and Gecko have different ideas of what a valid URL is, so we might
    // get in here with a URL string that NS_NewURI can't handle.  We may also
    // reach here via an import cycle.  For the import cycle case, we need some
    // sheet object per spec, even if its empty.  DevTools uses the URI to
    // realize it has hit an import cycle, so we mark it complete to make the
    // sheet readable from JS.
    RefPtr<StyleSheet> emptySheet =
        aParent->CreateEmptyChildSheet(media.forget());
    // Make a dummy URI if we don't have one because some methods assume
    // non-null URIs.
    if (!uri) {
      NS_NewURI(getter_AddRefs(uri), "about:invalid"_ns);
    }
    emptySheet->SetURIs(uri, uri, uri);
    emptySheet->SetPrincipal(aURL.ExtraData().Principal());
    nsCOMPtr<nsIReferrerInfo> referrerInfo =
        ReferrerInfo::CreateForExternalCSSResources(emptySheet);
    emptySheet->SetReferrerInfo(referrerInfo);
    emptySheet->SetComplete();
    aParent->AppendStyleSheet(*emptySheet);
    return emptySheet.forget();
  }

  RefPtr<StyleSheet> sheet = aParent->ChildSheets().LastElement();
  return sheet.forget();
}

StyleSheet* Gecko_LoadStyleSheet(Loader* aLoader, StyleSheet* aParent,
                                 SheetLoadData* aParentLoadData,
                                 LoaderReusableStyleSheets* aReusableSheets,
                                 const StyleCssUrl* aUrl,
                                 StyleStrong<StyleLockedMediaList> aMediaList) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(aUrl);

  return LoadImportSheet(aLoader, aParent, aParentLoadData, aReusableSheets,
                         *aUrl, aMediaList.Consume())
      .take();
}

void Gecko_LoadStyleSheetAsync(SheetLoadDataHolder* aParentData,
                               const StyleCssUrl* aUrl,
                               StyleStrong<StyleLockedMediaList> aMediaList,
                               StyleStrong<StyleLockedImportRule> aImportRule) {
  MOZ_ASSERT(aUrl);
  RefPtr<SheetLoadDataHolder> loadData = aParentData;
  RefPtr<StyleLockedMediaList> mediaList = aMediaList.Consume();
  RefPtr<StyleLockedImportRule> importRule = aImportRule.Consume();
  NS_DispatchToMainThreadQueue(
      NS_NewRunnableFunction(
          __func__,
          [data = std::move(loadData), url = StyleCssUrl(*aUrl),
           media = std::move(mediaList),
           import = std::move(importRule)]() mutable {
            MOZ_ASSERT(NS_IsMainThread());
            SheetLoadData* d = data->get();
            RefPtr<StyleSheet> sheet = LoadImportSheet(
                d->mLoader, d->mSheet, d, nullptr, url, media.forget());
            Servo_ImportRule_SetSheet(import, sheet);
          }),
      EventQueuePriority::RenderBlocking);
}

void Gecko_AddPropertyToSet(nsCSSPropertyIDSet* aPropertySet,
                            nsCSSPropertyID aProperty) {
  aPropertySet->AddProperty(aProperty);
}

#define STYLE_STRUCT(name)                                             \
                                                                       \
  void Gecko_Construct_Default_nsStyle##name(nsStyle##name* ptr,       \
                                             const Document* doc) {    \
    new (ptr) nsStyle##name(*doc);                                     \
  }                                                                    \
                                                                       \
  void Gecko_CopyConstruct_nsStyle##name(nsStyle##name* ptr,           \
                                         const nsStyle##name* other) { \
    new (ptr) nsStyle##name(*other);                                   \
  }                                                                    \
                                                                       \
  void Gecko_Destroy_nsStyle##name(nsStyle##name* ptr) {               \
    ptr->~nsStyle##name();                                             \
  }

bool Gecko_DocumentRule_UseForPresentation(
    const Document* aDocument, const nsACString* aPattern,
    DocumentMatchingFunction aMatchingFunction) {
  MOZ_ASSERT(NS_IsMainThread());

  nsIURI* docURI = aDocument->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);
  }

  return CSSMozDocumentRule::Match(aDocument, docURI, docURISpec, *aPattern,
                                   aMatchingFunction);
}

void Gecko_SetJemallocThreadLocalArena(bool enabled) {
#if defined(MOZ_MEMORY)
  jemalloc_thread_local_arena(enabled);
#endif
}

#include "nsStyleStructList.h"

#undef STYLE_STRUCT

bool Gecko_ErrorReportingEnabled(const StyleSheet* aSheet,
                                 const Loader* aLoader,
                                 uint64_t* aOutWindowId) {
  if (!ErrorReporter::ShouldReportErrors(aSheet, aLoader)) {
    return false;
  }
  *aOutWindowId = ErrorReporter::FindInnerWindowId(aSheet, aLoader);
  return true;
}

void Gecko_ReportUnexpectedCSSError(
    const uint64_t aWindowId, nsIURI* aURI, const char* message,
    const char* param, uint32_t paramLen, const char* prefix,
    const char* prefixParam, uint32_t prefixParamLen, const char* suffix,
    const char* source, uint32_t sourceLen, const char* selectors,
    uint32_t selectorsLen, uint32_t lineNumber, uint32_t colNumber) {
  MOZ_RELEASE_ASSERT(NS_IsMainThread());

  ErrorReporter reporter(aWindowId);

  if (prefix) {
    if (prefixParam) {
      nsDependentCSubstring paramValue(prefixParam, prefixParamLen);
      AutoTArray<nsString, 1> wideParam;
      CopyUTF8toUTF16(paramValue, *wideParam.AppendElement());
      reporter.ReportUnexpectedUnescaped(prefix, wideParam);
    } else {
      reporter.ReportUnexpected(prefix);
    }
  }

  if (param) {
    nsDependentCSubstring paramValue(param, paramLen);
    AutoTArray<nsString, 1> wideParam;
    CopyUTF8toUTF16(paramValue, *wideParam.AppendElement());
    reporter.ReportUnexpectedUnescaped(message, wideParam);
  } else {
    reporter.ReportUnexpected(message);
  }

  if (suffix) {
    reporter.ReportUnexpected(suffix);
  }
  nsDependentCSubstring sourceValue(source, sourceLen);
  nsDependentCSubstring selectorsValue(selectors, selectorsLen);
  reporter.OutputError(sourceValue, selectorsValue, lineNumber, colNumber,
                       aURI);
}

void Gecko_ContentList_AppendAll(nsSimpleContentList* aList,
                                 const Element** aElements, size_t aLength) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(aElements);
  MOZ_ASSERT(aLength);
  MOZ_ASSERT(aList);

  aList->SetCapacity(aLength);

  for (size_t i = 0; i < aLength; ++i) {
    aList->AppendElement(const_cast<Element*>(aElements[i]));
  }
}

const nsTArray<Element*>* Gecko_Document_GetElementsWithId(const Document* aDoc,
                                                           nsAtom* aId) {
  MOZ_ASSERT(aDoc);
  MOZ_ASSERT(aId);

  return aDoc->GetAllElementsForId(nsDependentAtomString(aId));
}

const nsTArray<Element*>* Gecko_ShadowRoot_GetElementsWithId(
    const ShadowRoot* aShadowRoot, nsAtom* aId) {
  MOZ_ASSERT(aShadowRoot);
  MOZ_ASSERT(aId);

  return aShadowRoot->GetAllElementsForId(nsDependentAtomString(aId));
}

bool Gecko_GetBoolPrefValue(const char* aPrefName) {
  MOZ_ASSERT(NS_IsMainThread());
  return Preferences::GetBool(aPrefName);
}

bool Gecko_IsFontFormatSupported(StyleFontFaceSourceFormatKeyword aFormat) {
  return gfxPlatform::GetPlatform()->IsFontFormatSupported(
      aFormat, StyleFontFaceSourceTechFlags::Empty());
}

bool Gecko_IsFontTechSupported(StyleFontFaceSourceTechFlags aFlag) {
  return gfxPlatform::GetPlatform()->IsFontFormatSupported(
      StyleFontFaceSourceFormatKeyword::None, aFlag);
}

bool Gecko_IsKnownIconFontFamily(const nsAtom* aFamilyName) {
  return gfxPlatform::GetPlatform()->IsKnownIconFontFamily(aFamilyName);
}

bool Gecko_IsInServoTraversal() { return ServoStyleSet::IsInServoTraversal(); }

bool Gecko_IsMainThread() { return NS_IsMainThread(); }

bool Gecko_IsDOMWorkerThread() { return !!GetCurrentThreadWorkerPrivate(); }

const nsAttrValue* Gecko_GetSVGAnimatedClass(const Element* aElement) {
  MOZ_ASSERT(aElement->IsSVGElement());
  return static_cast<const SVGElement*>(aElement)->GetAnimatedClassName();
}

bool Gecko_AssertClassAttrValueIsSane(const nsAttrValue* aValue) {
  MOZ_ASSERT(aValue->Type() == nsAttrValue::eAtom ||
             aValue->Type() == nsAttrValue::eString ||
             aValue->Type() == nsAttrValue::eAtomArray);
  MOZ_ASSERT_IF(
      aValue->Type() == nsAttrValue::eString,
      nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
          aValue->GetStringValue())
          .IsEmpty());
  return true;
}

void Gecko_GetSafeAreaInsets(const nsPresContext* aPresContext, float* aTop,
                             float* aRight, float* aBottom, float* aLeft) {
  MOZ_ASSERT(aPresContext);
  ScreenIntMargin safeAreaInsets = aPresContext->GetSafeAreaInsets();
  *aTop = aPresContext->DevPixelsToFloatCSSPixels(safeAreaInsets.top);
  *aRight = aPresContext->DevPixelsToFloatCSSPixels(safeAreaInsets.right);
  *aBottom = aPresContext->DevPixelsToFloatCSSPixels(safeAreaInsets.bottom);
  *aLeft = aPresContext->DevPixelsToFloatCSSPixels(safeAreaInsets.left);
}

void Gecko_PrintfStderr(const nsCString* aStr) {
  printf_stderr("%s", aStr->get());
}

nsAtom* Gecko_Element_ImportedPart(const nsAttrValue* aValue,
                                   nsAtom* aPartName) {
  if (aValue->Type() != nsAttrValue::eShadowParts) {
    return nullptr;
  }
  return aValue->GetShadowPartsValue().GetReverse(aPartName);
}

nsAtom** Gecko_Element_ExportedParts(const nsAttrValue* aValue,
                                     nsAtom* aPartName, size_t* aOutLength) {
  if (aValue->Type() != nsAttrValue::eShadowParts) {
    return nullptr;
  }
  auto* parts = aValue->GetShadowPartsValue().Get(aPartName);
  if (!parts) {
    return nullptr;
  }
  *aOutLength = parts->Length();
  static_assert(sizeof(RefPtr<nsAtom>) == sizeof(nsAtom*));
  static_assert(alignof(RefPtr<nsAtom>) == alignof(nsAtom*));
  return reinterpret_cast<nsAtom**>(parts->Elements());
}

bool StyleSingleFontFamily::IsNamedFamily(const nsAString& aFamilyName) const {
  if (!IsFamilyName()) {
    return false;
  }
  nsDependentAtomString name(AsFamilyName().name.AsAtom());
  return name.Equals(aFamilyName, nsCaseInsensitiveStringComparator);
}

StyleSingleFontFamily StyleSingleFontFamily::Parse(
    const nsACString& aFamilyOrGenericName) {
  // should only be passed a single font - not entirely correct, a family
  // *could* have a comma in it but in practice never does so
  // for debug purposes this is fine
  NS_ASSERTION(aFamilyOrGenericName.FindChar(',') == -1,
               "Convert method should only be passed a single family name");

  auto genericType = Servo_GenericFontFamily_Parse(&aFamilyOrGenericName);
  if (genericType != StyleGenericFontFamily::None) {
    return Generic(genericType);
  }
  return FamilyName({StyleAtom(NS_Atomize(aFamilyOrGenericName)),
                     StyleFontFamilyNameSyntax::Identifiers});
}

void StyleSingleFontFamily::AppendToString(nsACString& aName,
                                           bool aQuote) const {
  if (IsFamilyName()) {
    const auto& name = AsFamilyName();
    bool quote = aQuote && name.syntax == StyleFontFamilyNameSyntax::Quoted;
    if (quote) {
      aName.Append('"');
    }
    aName.Append(nsAtomCString(name.name.AsAtom()));
    if (quote) {
      aName.Append('"');
    }
    return;
  }

  switch (AsGeneric()) {
    case StyleGenericFontFamily::None:
    case StyleGenericFontFamily::MozEmoji:
      MOZ_FALLTHROUGH_ASSERT("Should never appear in a font-family name!");
    case StyleGenericFontFamily::Serif:
      return aName.AppendLiteral("serif");
    case StyleGenericFontFamily::SansSerif:
      return aName.AppendLiteral("sans-serif");
    case StyleGenericFontFamily::Monospace:
      return aName.AppendLiteral("monospace");
    case StyleGenericFontFamily::Cursive:
      return aName.AppendLiteral("cursive");
    case StyleGenericFontFamily::Fantasy:
      return aName.AppendLiteral("fantasy");
    case StyleGenericFontFamily::SystemUi:
      return aName.AppendLiteral("system-ui");
  }
  MOZ_ASSERT_UNREACHABLE("Unknown generic font-family!");
  return aName.AppendLiteral("serif");
}

StyleFontFamilyList StyleFontFamilyList::WithNames(
    nsTArray<StyleSingleFontFamily>&& aNames) {
  StyleFontFamilyList list;
  Servo_FontFamilyList_WithNames(&aNames, &list);
  return list;
}

StyleFontFamilyList StyleFontFamilyList::WithOneUnquotedFamily(
    const nsACString& aName) {
  AutoTArray<StyleSingleFontFamily, 1> names;
  names.AppendElement(StyleSingleFontFamily::FamilyName(
      {StyleAtom(NS_Atomize(aName)), StyleFontFamilyNameSyntax::Identifiers}));
  return WithNames(std::move(names));
}
