layout/style/ServoStyleSet.cpp
author Emilio Cobos Álvarez <emilio@crisal.io>
Fri, 06 Apr 2018 14:53:06 +0200
changeset 412359 433416ba2a3a058eb99036f12783dd8f3e2519f2
parent 412347 28a81bb4a0bd1fc67709038c799da980326752f8
child 412385 e3778466517982ca282683f347672c732f340161
permissions -rw-r--r--
Bug 1452080: Rename ComputedStyle::PresContext to PresContextForFrame. r=xidorn And make nsIFrame its only caller, modulo a safety assertion. The safety assertion will be removed at the same time as the pres context member, since the only purpose of it is to ensure we don't keep a pres context reference for too long. MozReview-Commit-ID: CD5zOHVO9ub

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

#include "mozilla/ServoStyleSet.h"
#include "mozilla/ServoStyleSetInlines.h"

#include "gfxPlatformFontList.h"
#include "mozilla/AutoRestyleTimelineMarker.h"
#include "mozilla/DocumentStyleRootIterator.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/ServoBindings.h"
#include "mozilla/RestyleManager.h"
#include "mozilla/ServoStyleRuleMap.h"
#include "mozilla/ServoTypes.h"
#include "mozilla/StyleAnimationValue.h"
#include "mozilla/css/Loader.h"
#include "mozilla/dom/AnonymousContent.h"
#include "mozilla/dom/ChildIterator.h"
#include "mozilla/dom/FontFaceSet.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ElementInlines.h"
#include "nsCSSAnonBoxes.h"
#include "nsCSSFrameConstructor.h"
#include "nsCSSPseudoElements.h"
#include "nsDeviceContext.h"
#include "nsHTMLStyleSheet.h"
#include "nsIAnonymousContentCreator.h"
#include "nsIDocumentInlines.h"
#include "nsMediaFeatures.h"
#include "nsPrintfCString.h"
#include "nsSMILAnimationController.h"
#include "nsXBLPrototypeBinding.h"
#include "gfxUserFontSet.h"
#include "nsBindingManager.h"
#include "nsWindowSizes.h"

using namespace mozilla;
using namespace mozilla::dom;

#ifdef DEBUG
bool
ServoStyleSet::IsCurrentThreadInServoTraversal()
{
  return sInServoTraversal && (NS_IsMainThread() || Servo_IsWorkerThread());
}
#endif

namespace mozilla {
ServoStyleSet* sInServoTraversal = nullptr;


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

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

private:
  ServoStyleSet* mSet;
};

// Sets up for one or more calls to Servo_TraverseSubtree.
class MOZ_RAII AutoPrepareTraversal
{
public:
  explicit AutoPrepareTraversal(ServoStyleSet* aSet)
    // For markers for animations, we have already set the markers in
    // RestyleManager::PostRestyleEventForAnimations so that we don't need
    // to care about animation restyles here.
    : mTimelineMarker(aSet->mDocument->GetDocShell(), false)
    , mSetInServoTraversal(aSet)
  {
    MOZ_ASSERT(!aSet->StylistNeedsUpdate());
  }

private:
  AutoRestyleTimelineMarker mTimelineMarker;
  AutoSetInServoTraversal mSetInServoTraversal;
};

} // namespace mozilla

ServoStyleSet::ServoStyleSet()
  : mDocument(nullptr)
  , mAuthorStyleDisabled(false)
  , mStylistState(StylistState::NotDirty)
  , mUserFontSetUpdateGeneration(0)
  , mNeedsRestyleAfterEnsureUniqueInner(false)
{
}

ServoStyleSet::~ServoStyleSet()
{
  for (auto& sheetArray : mSheets) {
    for (auto& sheet : sheetArray) {
      sheet->DropStyleSet(this);
    }
  }
}

nsPresContext*
ServoStyleSet::GetPresContext()
{
  if (!mDocument) {
    return nullptr;
  }

  return mDocument->GetPresContext();
}

void
ServoStyleSet::Init(nsPresContext* aPresContext)
{
  mDocument = aPresContext->Document();
  MOZ_ASSERT(GetPresContext() == aPresContext);

  mRawSet.reset(Servo_StyleSet_Init(aPresContext));

  aPresContext->DeviceContext()->InitFontCache();

  // Now that we have an mRawSet, go ahead and notify about whatever stylesheets
  // we have so far.
  for (auto& sheetArray : mSheets) {
    for (auto& sheet : sheetArray) {
      // There's no guarantee this will create a list on the servo side whose
      // ordering matches the list that would have been created had all those
      // sheets been appended/prepended/etc after we had mRawSet. That's okay
      // because Servo only needs to maintain relative ordering within a sheet
      // type, which this preserves.

      MOZ_ASSERT(sheet->RawContents(),
                 "We should only append non-null raw sheets.");
      Servo_StyleSet_AppendStyleSheet(mRawSet.get(), sheet);
    }
  }

  // We added prefilled stylesheets into mRawSet, so the stylist is dirty.
  // The Stylist should be updated later when necessary.
  SetStylistStyleSheetsDirty();

  // We may have Shadow DOM style changes that we weren't notified about because
  // the document didn't have a shell, if the ShadowRoot was created in a
  // display: none iframe.
  //
  // Now that we got a shell, we may need to get them up-to-date.
  //
  // TODO(emilio, bug 1418159): This wouldn't be needed if the StyleSet was
  // owned by the document.
  SetStylistXBLStyleSheetsDirty();
}

template<typename Functor>
void
EnumerateShadowRoots(const nsIDocument& aDoc, const Functor& aCb)
{
  if (!aDoc.IsShadowDOMEnabled()) {
    return;
  }

  const nsIDocument::ShadowRootSet& shadowRoots = aDoc.ComposedShadowRoots();
  for (auto iter = shadowRoots.ConstIter(); !iter.Done(); iter.Next()) {
    ShadowRoot* root = iter.Get()->GetKey();
    MOZ_ASSERT(root);
    MOZ_DIAGNOSTIC_ASSERT(root->IsComposedDocParticipant());
    aCb(*root);
  }
}

void
ServoStyleSet::Shutdown()
{
  // Make sure we drop our cached styles before the presshell arena starts going
  // away.
  ClearNonInheritingComputedStyles();
  mRawSet = nullptr;
  mStyleRuleMap = nullptr;
}

void
ServoStyleSet::InvalidateStyleForCSSRuleChanges()
{
  MOZ_ASSERT(StylistNeedsUpdate());
  if (nsPresContext* pc = GetPresContext()) {
    pc->RestyleManager()->PostRestyleEventForCSSRuleChanges();
  }
}

void
ServoStyleSet::RecordShadowStyleChange(ShadowRoot& aShadowRoot)
{
  // TODO(emilio): We could keep track of the actual shadow roots that need
  // their styles recomputed.
  SetStylistXBLStyleSheetsDirty();

  // FIXME(emilio): This should be done using stylesheet invalidation instead.
  if (nsPresContext* pc = GetPresContext()) {
    pc->RestyleManager()->PostRestyleEvent(
      aShadowRoot.Host(), eRestyle_Subtree, nsChangeHint(0));
  }
}

void
ServoStyleSet::InvalidateStyleForDocumentStateChanges(EventStates aStatesChanged)
{
  MOZ_ASSERT(mDocument);
  MOZ_ASSERT(!aStatesChanged.IsEmpty());

  nsPresContext* pc = GetPresContext();
  if (!pc) {
    return;
  }

  Element* root = mDocument->GetRootElement();
  if (!root) {
    return;
  }

  // TODO(emilio): It may be nicer to just invalidate stuff in a given subtree
  // for XBL sheets / Shadow DOM. Consider just enumerating bound content
  // instead and run invalidation individually, passing mRawSet for the UA /
  // User sheets.
  AutoTArray<RawServoAuthorStylesBorrowed, 20> nonDocumentStyles;

  EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
    nonDocumentStyles.AppendElement(aShadowRoot.ServoStyles());
  });

  // FIXME(emilio): When bug 1425759 is fixed we need to enumerate ShadowRoots
  // too.
  mDocument->BindingManager()->EnumerateBoundContentBindings(
    [&](nsXBLBinding* aBinding) {
      if (auto* authorStyles = aBinding->PrototypeBinding()->GetServoStyles()) {
        nonDocumentStyles.AppendElement(authorStyles);
      }
      return true;
    });

  Servo_InvalidateStyleForDocStateChanges(
    root, mRawSet.get(), &nonDocumentStyles, aStatesChanged.ServoValue());
}

static const MediaFeatureChangeReason kMediaFeaturesAffectingDefaultStyle =
  // Zoom changes change the meaning of em units.
  MediaFeatureChangeReason::ZoomChange |
  // Changes the meaning of em units, depending on which one is the actual
  // min-font-size.
  MediaFeatureChangeReason::MinFontSizeChange |
  // A resolution change changes the app-units-per-dev-pixels ratio, which some
  // structs (Border, Outline, Column) store for clamping. We should arguably
  // not do that, maybe doing it on layout directly, to try to avoid relying on
  // the pres context (bug 1418159).
  MediaFeatureChangeReason::ResolutionChange;

nsRestyleHint
ServoStyleSet::MediumFeaturesChanged(MediaFeatureChangeReason aReason)
{
  AutoTArray<RawServoAuthorStylesBorrowedMut, 20> nonDocumentStyles;

  EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
    nonDocumentStyles.AppendElement(aShadowRoot.ServoStyles());
  });

  // FIXME(emilio): This is broken for XBL. See bug 1406875.
  mDocument->BindingManager()->EnumerateBoundContentBindings(
    [&](nsXBLBinding* aBinding) {
      if (auto* authorStyles = aBinding->PrototypeBinding()->GetServoStyles()) {
        nonDocumentStyles.AppendElement(authorStyles);
      }
      return true;
    });

  bool mayAffectDefaultStyle =
    bool(aReason & kMediaFeaturesAffectingDefaultStyle);

  const MediumFeaturesChangedResult result =
    Servo_StyleSet_MediumFeaturesChanged(
      mRawSet.get(), &nonDocumentStyles, mayAffectDefaultStyle);

  const bool rulesChanged =
    result.mAffectsDocumentRules || result.mAffectsNonDocumentRules;

  if (result.mAffectsDocumentRules) {
    SetStylistStyleSheetsDirty();
  }

  if (result.mAffectsNonDocumentRules) {
    SetStylistXBLStyleSheetsDirty();
  }

  if (rulesChanged) {
    return eRestyle_Subtree;
  }

  const bool viewportChanged =
    bool(aReason & MediaFeatureChangeReason::ViewportChange);
  if (result.mUsesViewportUnits && viewportChanged) {
    return eRestyle_ForceDescendants;
  }

  return nsRestyleHint(0);
}

MOZ_DEFINE_MALLOC_SIZE_OF(ServoStyleSetMallocSizeOf)
MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ServoStyleSetMallocEnclosingSizeOf)

void
ServoStyleSet::AddSizeOfIncludingThis(nsWindowSizes& aSizes) const
{
  MallocSizeOf mallocSizeOf = aSizes.mState.mMallocSizeOf;

  aSizes.mLayoutStyleSetsOther += mallocSizeOf(this);

  if (mRawSet) {
    aSizes.mLayoutStyleSetsOther += mallocSizeOf(mRawSet.get());
    ServoStyleSetSizes sizes;
    // Measure mRawSet. We use ServoStyleSetMallocSizeOf rather than
    // aMallocSizeOf to distinguish in DMD's output the memory measured within
    // Servo code.
    Servo_StyleSet_AddSizeOfExcludingThis(ServoStyleSetMallocSizeOf,
                                          ServoStyleSetMallocEnclosingSizeOf,
                                          &sizes, mRawSet.get());

    // The StyleSet does not contain precomputed pseudos; they are in the UA
    // cache.
    MOZ_RELEASE_ASSERT(sizes.mPrecomputedPseudos == 0);

    aSizes.mLayoutStyleSetsStylistRuleTree += sizes.mRuleTree;
    aSizes.mLayoutStyleSetsStylistElementAndPseudosMaps +=
      sizes.mElementAndPseudosMaps;
    aSizes.mLayoutStyleSetsStylistInvalidationMap +=
      sizes.mInvalidationMap;
    aSizes.mLayoutStyleSetsStylistRevalidationSelectors +=
      sizes.mRevalidationSelectors;
    aSizes.mLayoutStyleSetsStylistOther += sizes.mOther;
  }

  if (mStyleRuleMap) {
    aSizes.mLayoutStyleSetsOther +=
      mStyleRuleMap->SizeOfIncludingThis(aSizes.mState.mMallocSizeOf);
  }

  // Measurement of the following members may be added later if DMD finds it is
  // worthwhile:
  // - mSheets
  // - mNonInheritingComputedStyles
  //
  // The following members are not measured:
  // - mDocument, because it a non-owning pointer
}

void
ServoStyleSet::SetAuthorStyleDisabled(bool aStyleDisabled)
{
  if (mAuthorStyleDisabled == aStyleDisabled) {
    return;
  }

  mAuthorStyleDisabled = aStyleDisabled;
  if (Element* root = mDocument->GetRootElement()) {
    if (nsPresContext* pc = GetPresContext()) {
      pc->RestyleManager()->PostRestyleEvent(root, eRestyle_Subtree, nsChangeHint(0));
    }
  }
  Servo_StyleSet_SetAuthorStyleDisabled(mRawSet.get(), mAuthorStyleDisabled);
  // XXX Workaround for the assertion in InvalidateStyleForDocumentStateChanges
  // which is called by nsIPresShell::SetAuthorStyleDisabled via nsIPresShell::
  // RestyleForCSSRuleChanges. It is not really necessary because we don't need
  // to rebuild stylist for this change. But we have bug around this, and we
  // may want to rethink how things should work. See bug 1437785.
  SetStylistStyleSheetsDirty();
}

void
ServoStyleSet::BeginUpdate()
{
}

nsresult
ServoStyleSet::EndUpdate()
{
  return NS_OK;
}

already_AddRefed<ComputedStyle>
ServoStyleSet::ResolveStyleFor(Element* aElement,
                               ComputedStyle* aParentContext,
                               LazyComputeBehavior aMayCompute)
{
  if (aMayCompute == LazyComputeBehavior::Allow) {
    PreTraverseSync();
    return ResolveStyleLazilyInternal(
        aElement, CSSPseudoElementType::NotPseudo);
  }

  return ResolveServoStyle(aElement);
}

const ServoElementSnapshotTable&
ServoStyleSet::Snapshots()
{
  MOZ_ASSERT(GetPresContext(), "Styling a document without a shell?");
  return GetPresContext()->RestyleManager()->Snapshots();
}

void
ServoStyleSet::ResolveMappedAttrDeclarationBlocks()
{
  if (nsHTMLStyleSheet* sheet = mDocument->GetAttributeStyleSheet()) {
    sheet->CalculateMappedServoDeclarations();
  }

  mDocument->ResolveScheduledSVGPresAttrs();
}

void
ServoStyleSet::PreTraverseSync()
{
  // Get the Document's root element to ensure that the cache is valid before
  // calling into the (potentially-parallel) Servo traversal, where a cache hit
  // is necessary to avoid a data race when updating the cache.
  mozilla::Unused << mDocument->GetRootElement();

  ResolveMappedAttrDeclarationBlocks();

  nsMediaFeatures::InitSystemMetrics();

  LookAndFeel::NativeInit();

  nsPresContext* presContext = GetPresContext();
  MOZ_ASSERT(presContext,
             "For now, we don't call into here without a pres context");
  if (gfxUserFontSet* userFontSet = mDocument->GetUserFontSet()) {
    // Ensure that the @font-face data is not stale
    uint64_t generation = userFontSet->GetGeneration();
    if (generation != mUserFontSetUpdateGeneration) {
      mDocument->GetFonts()->CacheFontLoadability();
      presContext->DeviceContext()->UpdateFontCacheUserFonts(userFontSet);
      mUserFontSetUpdateGeneration = generation;
    }
  }

  MOZ_ASSERT(!StylistNeedsUpdate());
  presContext->CacheAllLangs();
}

void
ServoStyleSet::PreTraverse(ServoTraversalFlags aFlags, Element* aRoot)
{
  PreTraverseSync();

  // Process animation stuff that we should avoid doing during the parallel
  // traversal.
  nsSMILAnimationController* smilController =
    mDocument->HasAnimationController()
    ? mDocument->GetAnimationController()
    : nullptr;

  MOZ_ASSERT(GetPresContext());
  if (aRoot) {
    GetPresContext()->EffectCompositor()->PreTraverseInSubtree(aFlags, aRoot);
    if (smilController) {
      smilController->PreTraverseInSubtree(aRoot);
    }
  } else {
    GetPresContext()->EffectCompositor()->PreTraverse(aFlags);
    if (smilController) {
      smilController->PreTraverse();
    }
  }
}

static inline already_AddRefed<ComputedStyle>
ResolveStyleForTextOrFirstLetterContinuation(
    RawServoStyleSetBorrowed aStyleSet,
    ComputedStyle& aParent,
    nsAtom* aAnonBox)
{
  MOZ_ASSERT(aAnonBox == nsCSSAnonBoxes::mozText ||
             aAnonBox == nsCSSAnonBoxes::firstLetterContinuation);
  auto inheritTarget = aAnonBox == nsCSSAnonBoxes::mozText
    ? InheritTarget::Text
    : InheritTarget::FirstLetterContinuation;

  RefPtr<ComputedStyle> style =
    aParent.GetCachedInheritingAnonBoxStyle(aAnonBox);
  if (!style) {
    style = Servo_ComputedValues_Inherit(aStyleSet,
                                         aAnonBox,
                                         &aParent,
                                         inheritTarget).Consume();
    MOZ_ASSERT(style);
    aParent.SetCachedInheritedAnonBoxStyle(aAnonBox, style);
  }

  return style.forget();
}

already_AddRefed<ComputedStyle>
ServoStyleSet::ResolveStyleForText(nsIContent* aTextNode,
                                   ComputedStyle* aParentContext)
{
  MOZ_ASSERT(aTextNode && aTextNode->IsNodeOfType(nsINode::eTEXT));
  MOZ_ASSERT(aTextNode->GetParent());
  MOZ_ASSERT(aParentContext);

  return ResolveStyleForTextOrFirstLetterContinuation(
      mRawSet.get(), *aParentContext, nsCSSAnonBoxes::mozText);
}

already_AddRefed<ComputedStyle>
ServoStyleSet::ResolveStyleForFirstLetterContinuation(ComputedStyle* aParentContext)
{
  MOZ_ASSERT(aParentContext);

  return ResolveStyleForTextOrFirstLetterContinuation(
      mRawSet.get(), *aParentContext, nsCSSAnonBoxes::firstLetterContinuation);
}

already_AddRefed<ComputedStyle>
ServoStyleSet::ResolveStyleForPlaceholder()
{
  RefPtr<ComputedStyle>& cache =
    mNonInheritingComputedStyles[nsCSSAnonBoxes::NonInheriting::oofPlaceholder];
  if (cache) {
    RefPtr<ComputedStyle> retval = cache;
    return retval.forget();
  }

  RefPtr<ComputedStyle> computedValues =
    Servo_ComputedValues_Inherit(mRawSet.get(),
                                 nsCSSAnonBoxes::oofPlaceholder,
                                 nullptr,
                                 InheritTarget::PlaceholderFrame)
                                 .Consume();
  MOZ_ASSERT(computedValues);

  cache = computedValues;
  return computedValues.forget();
}

static inline bool
LazyPseudoIsCacheable(CSSPseudoElementType aType,
                      Element* aOriginatingElement,
                      ComputedStyle* aParentContext)
{
  return aParentContext &&
         !nsCSSPseudoElements::IsEagerlyCascadedInServo(aType) &&
         aOriginatingElement->HasServoData() &&
         !Servo_Element_IsPrimaryStyleReusedViaRuleNode(aOriginatingElement);
}

already_AddRefed<ComputedStyle>
ServoStyleSet::ResolvePseudoElementStyle(Element* aOriginatingElement,
                                         CSSPseudoElementType aType,
                                         ComputedStyle* aParentContext,
                                         Element* aPseudoElement)
{
  // Runs from frame construction, this should have clean styles already, except
  // with non-lazy FC...
  UpdateStylistIfNeeded();
  MOZ_ASSERT(aType < CSSPseudoElementType::Count);

  RefPtr<ComputedStyle> computedValues;

  if (aPseudoElement) {
    MOZ_ASSERT(aType == aPseudoElement->GetPseudoElementType());
    computedValues =
      Servo_ResolveStyle(aPseudoElement, mRawSet.get()).Consume();
  } else {
    bool cacheable =
      LazyPseudoIsCacheable(aType, aOriginatingElement, aParentContext);
    computedValues =
      cacheable ? aParentContext->GetCachedLazyPseudoStyle(aType) : nullptr;

    if (!computedValues) {
      computedValues = Servo_ResolvePseudoStyle(aOriginatingElement,
                                                aType,
                                                /* is_probe = */ false,
                                                aParentContext,
                                                mRawSet.get()).Consume();
      if (cacheable) {
        aParentContext->SetCachedLazyPseudoStyle(computedValues);
      }
    }
  }

  MOZ_ASSERT(computedValues);
  return computedValues.forget();
}

already_AddRefed<ComputedStyle>
ServoStyleSet::ResolveStyleLazily(Element* aElement,
                                  CSSPseudoElementType aPseudoType,
                                  StyleRuleInclusion aRuleInclusion)
{
  PreTraverseSync();

  return ResolveStyleLazilyInternal(aElement, aPseudoType,
                                    aRuleInclusion);
}

already_AddRefed<ComputedStyle>
ServoStyleSet::ResolveInheritingAnonymousBoxStyle(nsAtom* aPseudoTag,
                                                  ComputedStyle* aParentContext)
{
  MOZ_ASSERT(nsCSSAnonBoxes::IsAnonBox(aPseudoTag) &&
             !nsCSSAnonBoxes::IsNonInheritingAnonBox(aPseudoTag));
  MOZ_ASSERT_IF(aParentContext, !StylistNeedsUpdate());

  UpdateStylistIfNeeded();

  RefPtr<ComputedStyle> style = nullptr;

  if (aParentContext) {
    style = aParentContext->GetCachedInheritingAnonBoxStyle(aPseudoTag);
  }

  if (!style) {
    style =
      Servo_ComputedValues_GetForAnonymousBox(aParentContext,
                                              aPseudoTag,
                                              mRawSet.get()).Consume();
    MOZ_ASSERT(style);
    if (aParentContext) {
      aParentContext->SetCachedInheritedAnonBoxStyle(aPseudoTag, style);
    }
  }

  return style.forget();
}

already_AddRefed<ComputedStyle>
ServoStyleSet::ResolveNonInheritingAnonymousBoxStyle(nsAtom* aPseudoTag)
{
  MOZ_ASSERT(nsCSSAnonBoxes::IsAnonBox(aPseudoTag) &&
             nsCSSAnonBoxes::IsNonInheritingAnonBox(aPseudoTag));
  MOZ_ASSERT(aPseudoTag != nsCSSAnonBoxes::pageContent,
             "If nsCSSAnonBoxes::pageContent ends up non-inheriting, check "
             "whether we need to do anything to move the "
             "@page handling from ResolveInheritingAnonymousBoxStyle to "
             "ResolveNonInheritingAnonymousBoxStyle");

  nsCSSAnonBoxes::NonInheriting type =
    nsCSSAnonBoxes::NonInheritingTypeForPseudoTag(aPseudoTag);
  RefPtr<ComputedStyle>& cache = mNonInheritingComputedStyles[type];
  if (cache) {
    RefPtr<ComputedStyle> retval = cache;
    return retval.forget();
  }

  UpdateStylistIfNeeded();

  // We always want to skip parent-based display fixup here.  It never makes
  // sense for non-inheriting anonymous boxes.  (Static assertions in
  // nsCSSAnonBoxes.cpp ensure that all non-inheriting non-anonymous boxes
  // are indeed annotated as skipping this fixup.)
  MOZ_ASSERT(!nsCSSAnonBoxes::IsNonInheritingAnonBox(nsCSSAnonBoxes::viewport),
             "viewport needs fixup to handle blockifying it");
  RefPtr<ComputedStyle> computedValues =
    Servo_ComputedValues_GetForAnonymousBox(nullptr,
                                            aPseudoTag,
                                            mRawSet.get()).Consume();
#ifdef DEBUG
  if (!computedValues) {
    nsString pseudo;
    aPseudoTag->ToString(pseudo);
    NS_ERROR(nsPrintfCString("stylo: could not get anon-box: %s",
             NS_ConvertUTF16toUTF8(pseudo).get()).get());
    MOZ_CRASH();
  }
#endif

  cache = computedValues;
  return computedValues.forget();
}

#ifdef MOZ_XUL
already_AddRefed<ComputedStyle>
ServoStyleSet::ResolveXULTreePseudoStyle(dom::Element* aParentElement,
                                         nsICSSAnonBoxPseudo* aPseudoTag,
                                         ComputedStyle* aParentContext,
                                         const AtomArray& aInputWord)
{
  MOZ_ASSERT(nsCSSAnonBoxes::IsTreePseudoElement(aPseudoTag));
  MOZ_ASSERT(aParentContext);
  MOZ_ASSERT(!StylistNeedsUpdate());

  return Servo_ComputedValues_ResolveXULTreePseudoStyle(
      aParentElement,
      aPseudoTag,
      aParentContext,
      &aInputWord,
      mRawSet.get()
  ).Consume();
}
#endif

// manage the set of style sheets in the style set
nsresult
ServoStyleSet::AppendStyleSheet(SheetType aType,
                                ServoStyleSheet* aSheet)
{
  MOZ_ASSERT(aSheet);
  MOZ_ASSERT(aSheet->IsApplicable());
  MOZ_ASSERT(IsCSSSheetType(aType));
  MOZ_ASSERT(aSheet->RawContents(), "Raw sheet should be in place before insertion.");

  RemoveSheetOfType(aType, aSheet);
  AppendSheetOfType(aType, aSheet);

  if (mRawSet) {
    // Maintain a mirrored list of sheets on the servo side.
    // Servo will remove aSheet from its original position as part of the call
    // to Servo_StyleSet_AppendStyleSheet.
    Servo_StyleSet_AppendStyleSheet(mRawSet.get(), aSheet);
    SetStylistStyleSheetsDirty();
  }

  if (mStyleRuleMap) {
    mStyleRuleMap->SheetAdded(*aSheet);
  }

  return NS_OK;
}

nsresult
ServoStyleSet::PrependStyleSheet(SheetType aType,
                                 ServoStyleSheet* aSheet)
{
  MOZ_ASSERT(aSheet);
  MOZ_ASSERT(aSheet->IsApplicable());
  MOZ_ASSERT(IsCSSSheetType(aType));
  MOZ_ASSERT(aSheet->RawContents(),
             "Raw sheet should be in place before insertion.");

  RemoveSheetOfType(aType, aSheet);
  PrependSheetOfType(aType, aSheet);

  if (mRawSet) {
    // Maintain a mirrored list of sheets on the servo side.
    // Servo will remove aSheet from its original position as part of the call
    // to Servo_StyleSet_PrependStyleSheet.
    Servo_StyleSet_PrependStyleSheet(mRawSet.get(), aSheet);
    SetStylistStyleSheetsDirty();
  }

  if (mStyleRuleMap) {
    mStyleRuleMap->SheetAdded(*aSheet);
  }

  return NS_OK;
}

nsresult
ServoStyleSet::RemoveStyleSheet(SheetType aType,
                                ServoStyleSheet* aSheet)
{
  MOZ_ASSERT(aSheet);
  MOZ_ASSERT(IsCSSSheetType(aType));

  RemoveSheetOfType(aType, aSheet);
  if (mRawSet) {
    // Maintain a mirrored list of sheets on the servo side.
    Servo_StyleSet_RemoveStyleSheet(mRawSet.get(), aSheet);
    SetStylistStyleSheetsDirty();
  }

  if (mStyleRuleMap) {
    mStyleRuleMap->SheetRemoved(*aSheet);
  }

  return NS_OK;
}

nsresult
ServoStyleSet::ReplaceSheets(SheetType aType,
                             const nsTArray<RefPtr<ServoStyleSheet>>& aNewSheets)
{
  // Gecko uses a two-dimensional array keyed by sheet type, whereas Servo
  // stores a flattened list. This makes ReplaceSheets a pretty clunky thing
  // to express. If the need ever arises, we can easily make this more efficent,
  // probably by aligning the representations better between engines.

  SetStylistStyleSheetsDirty();

  // Remove all the existing sheets first.
  for (const auto& sheet : mSheets[aType]) {
    sheet->DropStyleSet(this);
    if (mRawSet) {
      Servo_StyleSet_RemoveStyleSheet(mRawSet.get(), sheet);
    }
  }
  mSheets[aType].Clear();

  // Add in all the new sheets.
  for (auto& sheet : aNewSheets) {
    AppendSheetOfType(aType, sheet);
    if (mRawSet) {
      MOZ_ASSERT(sheet->RawContents(), "Raw sheet should be in place before replacement.");
      Servo_StyleSet_AppendStyleSheet(mRawSet.get(), sheet);
    }
  }

  // Just don't bother calling SheetRemoved / SheetAdded, and recreate the rule
  // map when needed.
  mStyleRuleMap = nullptr;
  return NS_OK;
}

nsresult
ServoStyleSet::InsertStyleSheetBefore(SheetType aType,
                                      ServoStyleSheet* aNewSheet,
                                      ServoStyleSheet* aReferenceSheet)
{
  MOZ_ASSERT(aNewSheet);
  MOZ_ASSERT(aReferenceSheet);
  MOZ_ASSERT(aNewSheet->IsApplicable());
  MOZ_ASSERT(aNewSheet != aReferenceSheet, "Can't place sheet before itself.");
  MOZ_ASSERT(aNewSheet->RawContents(), "Raw sheet should be in place before insertion.");
  MOZ_ASSERT(aReferenceSheet->RawContents(), "Reference sheet should have a raw sheet.");

  // Servo will remove aNewSheet from its original position as part of the
  // call to Servo_StyleSet_InsertStyleSheetBefore.
  RemoveSheetOfType(aType, aNewSheet);
  InsertSheetOfType(aType, aNewSheet, aReferenceSheet);

  if (mRawSet) {
    // Maintain a mirrored list of sheets on the servo side.
    Servo_StyleSet_InsertStyleSheetBefore(
        mRawSet.get(), aNewSheet, aReferenceSheet);
    SetStylistStyleSheetsDirty();
  }

  if (mStyleRuleMap) {
    mStyleRuleMap->SheetAdded(*aNewSheet);
  }

  return NS_OK;
}

int32_t
ServoStyleSet::SheetCount(SheetType aType) const
{
  MOZ_ASSERT(IsCSSSheetType(aType));
  return mSheets[aType].Length();
}

ServoStyleSheet*
ServoStyleSet::StyleSheetAt(SheetType aType, int32_t aIndex) const
{
  MOZ_ASSERT(IsCSSSheetType(aType));
  return mSheets[aType][aIndex];
}

void
ServoStyleSet::AppendAllXBLStyleSheets(nsTArray<StyleSheet*>& aArray) const
{
  if (mDocument) {
    mDocument->BindingManager()->AppendAllSheets(aArray);
  }
}

nsresult
ServoStyleSet::RemoveDocStyleSheet(ServoStyleSheet* aSheet)
{
  return RemoveStyleSheet(SheetType::Doc, aSheet);
}

nsresult
ServoStyleSet::AddDocStyleSheet(ServoStyleSheet* aSheet,
                                nsIDocument* aDocument)
{
  MOZ_ASSERT(aSheet->IsApplicable());
  MOZ_ASSERT(aSheet->RawContents(), "Raw sheet should be in place by this point.");

  RefPtr<StyleSheet> strong(aSheet);

  RemoveSheetOfType(SheetType::Doc, aSheet);

  size_t index =
    aDocument->FindDocStyleSheetInsertionPoint(mSheets[SheetType::Doc], *aSheet);

  if (index < mSheets[SheetType::Doc].Length()) {
    // This case is insert before.
    ServoStyleSheet *beforeSheet = mSheets[SheetType::Doc][index];
    InsertSheetOfType(SheetType::Doc, aSheet, beforeSheet);

    if (mRawSet) {
      // Maintain a mirrored list of sheets on the servo side.
      Servo_StyleSet_InsertStyleSheetBefore(mRawSet.get(), aSheet, beforeSheet);
      SetStylistStyleSheetsDirty();
    }
  } else {
    // This case is append.
    AppendSheetOfType(SheetType::Doc, aSheet);

    if (mRawSet) {
      // Maintain a mirrored list of sheets on the servo side.
      Servo_StyleSet_AppendStyleSheet(mRawSet.get(), aSheet);
      SetStylistStyleSheetsDirty();
    }
  }

  if (mStyleRuleMap) {
    mStyleRuleMap->SheetAdded(*aSheet);
  }

  return NS_OK;
}

already_AddRefed<ComputedStyle>
ServoStyleSet::ProbePseudoElementStyle(Element* aOriginatingElement,
                                       CSSPseudoElementType aType,
                                       ComputedStyle* aParentContext)
{
  // Runs from frame construction, this should have clean styles already, except
  // with non-lazy FC...
  UpdateStylistIfNeeded();

  // NB: We ignore aParentContext, because in some cases
  // (first-line/first-letter on anonymous box blocks) Gecko passes something
  // nonsensical there.  In all other cases we want to inherit directly from
  // aOriginatingElement's styles anyway.
  MOZ_ASSERT(aType < CSSPseudoElementType::Count);

  bool cacheable =
    LazyPseudoIsCacheable(aType, aOriginatingElement, aParentContext);

  RefPtr<ComputedStyle> computedValues =
    cacheable ? aParentContext->GetCachedLazyPseudoStyle(aType) : nullptr;
  if (!computedValues) {
    computedValues = Servo_ResolvePseudoStyle(aOriginatingElement, aType,
                                              /* is_probe = */ true,
                                              nullptr,
                                              mRawSet.get()).Consume();
    if (!computedValues) {
      return nullptr;
    }

    if (cacheable) {
      // NB: We don't need to worry about the before/after handling below
      // because those are eager and thus not |cacheable| anyway.
      aParentContext->SetCachedLazyPseudoStyle(computedValues);
    }
  }

  // For :before and :after pseudo-elements, having display: none or no
  // 'content' property is equivalent to not having the pseudo-element
  // at all.
  bool isBeforeOrAfter = aType == CSSPseudoElementType::before ||
                         aType == CSSPseudoElementType::after;
  if (isBeforeOrAfter) {
    const nsStyleDisplay* display = computedValues->ComputedData()->GetStyleDisplay();
    const nsStyleContent* content = computedValues->ComputedData()->GetStyleContent();
    if (display->mDisplay == StyleDisplay::None ||
        content->ContentCount() == 0) {
      return nullptr;
    }
  }

  return computedValues.forget();
}

bool
ServoStyleSet::StyleDocument(ServoTraversalFlags aFlags)
{
  MOZ_ASSERT(GetPresContext(), "Styling a document without a shell?");

  if (!mDocument->GetServoRestyleRoot()) {
    return false;
  }

  PreTraverse(aFlags);
  AutoPrepareTraversal guard(this);
  const SnapshotTable& snapshots = Snapshots();

  // Restyle the document from the root element and each of the document level
  // NAC subtree roots.
  bool postTraversalRequired = false;

  Element* rootElement = mDocument->GetRootElement();
  MOZ_ASSERT_IF(rootElement, rootElement->HasServoData());

  if (ShouldTraverseInParallel()) {
    aFlags |= ServoTraversalFlags::ParallelTraversal;
  }

  // Do the first traversal.
  DocumentStyleRootIterator iter(mDocument->GetServoRestyleRoot());
  while (Element* root = iter.GetNextStyleRoot()) {
    MOZ_ASSERT(MayTraverseFrom(root));

    Element* parent = root->GetFlattenedTreeParentElementForStyle();
    MOZ_ASSERT_IF(parent,
                  !parent->HasAnyOfFlags(Element::kAllServoDescendantBits));

    postTraversalRequired |=
      Servo_TraverseSubtree(root, mRawSet.get(), &snapshots, aFlags);
    postTraversalRequired |=
      root->HasAnyOfFlags(Element::kAllServoDescendantBits | NODE_NEEDS_FRAME);

    if (parent) {
      MOZ_ASSERT(root == mDocument->GetServoRestyleRoot());
      if (parent->HasDirtyDescendantsForServo()) {
        // If any style invalidation was triggered in our siblings, then we may
        // need to post-traverse them, even if the root wasn't restyled after
        // all.
        uint32_t existingBits = mDocument->GetServoRestyleRootDirtyBits();
        // We need to propagate the existing bits to the parent.
        parent->SetFlags(existingBits);
        mDocument->SetServoRestyleRoot(
            parent,
            existingBits | ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
        postTraversalRequired = true;
      }
    }
  }

  // If there are still animation restyles needed, trigger a second traversal to
  // update CSS animations or transitions' styles.
  //
  // Note that we need to check the style root again, because doing another
  // PreTraverse on the EffectCompositor might alter the style root. But we
  // don't need to worry about NAC, since document-level NAC shouldn't have
  // animations.
  //
  // We don't need to do this for SMIL since SMIL only updates its animation
  // values once at the begin of a tick. As a result, even if the previous
  // traversal caused, for example, the font-size to change, the SMIL style
  // won't be updated until the next tick anyway.
  if (GetPresContext()->EffectCompositor()->PreTraverse(aFlags)) {
    nsINode* styleRoot = mDocument->GetServoRestyleRoot();
    Element* root = styleRoot->IsElement() ? styleRoot->AsElement() : rootElement;

    postTraversalRequired |=
      Servo_TraverseSubtree(root, mRawSet.get(), &snapshots, aFlags);
    postTraversalRequired |=
      root->HasAnyOfFlags(Element::kAllServoDescendantBits | NODE_NEEDS_FRAME);
  }

  return postTraversalRequired;
}

void
ServoStyleSet::StyleNewSubtree(Element* aRoot)
{
  MOZ_ASSERT(GetPresContext());
  MOZ_ASSERT(!aRoot->HasServoData());
  PreTraverseSync();
  AutoPrepareTraversal guard(this);

  // Do the traversal. The snapshots will not be used.
  const SnapshotTable& snapshots = Snapshots();
  auto flags = ServoTraversalFlags::Empty;
  if (ShouldTraverseInParallel()) {
    flags |= ServoTraversalFlags::ParallelTraversal;
  }

  DebugOnly<bool> postTraversalRequired =
    Servo_TraverseSubtree(aRoot, mRawSet.get(), &snapshots, flags);
  MOZ_ASSERT(!postTraversalRequired);

  // Annoyingly, the newly-styled content may have animations that need
  // starting, which requires traversing them again. Mark the elements
  // that need animation processing, then do a forgetful traversal to
  // update the styles and clear the animation bits.
  if (GetPresContext()->EffectCompositor()->PreTraverseInSubtree(flags, aRoot)) {
    postTraversalRequired =
      Servo_TraverseSubtree(aRoot, mRawSet.get(), &snapshots,
                            ServoTraversalFlags::AnimationOnly |
                            ServoTraversalFlags::Forgetful |
                            ServoTraversalFlags::ClearAnimationOnlyDirtyDescendants);
    MOZ_ASSERT(!postTraversalRequired);
  }
}

void
ServoStyleSet::MarkOriginsDirty(OriginFlags aChangedOrigins)
{
  if (MOZ_UNLIKELY(!mRawSet)) {
    return;
  }

  SetStylistStyleSheetsDirty();
  Servo_StyleSet_NoteStyleSheetsChanged(mRawSet.get(), aChangedOrigins);
}

void
ServoStyleSet::SetStylistStyleSheetsDirty()
{
  // Note that there's another hidden mutator of mStylistState for XBL style
  // sets in MediumFeaturesChanged...
  //
  // We really need to stop using a full-blown StyleSet there...
  mStylistState |= StylistState::StyleSheetsDirty;

  // We need to invalidate cached style in getComputedStyle for undisplayed
  // elements, since we don't know if any of the style sheet change that we
  // do would affect undisplayed elements.
  if (nsPresContext* presContext = GetPresContext()) {
    // XBL sheets don't have a pres context, but invalidating the restyle
    // generation in that case is handled by SetXBLStyleSheetsDirty in the
    // "master" stylist.
    presContext->RestyleManager()->IncrementUndisplayedRestyleGeneration();
  }
}

void
ServoStyleSet::SetStylistXBLStyleSheetsDirty()
{
  mStylistState |= StylistState::XBLStyleSheetsDirty;

  // We need to invalidate cached style in getComputedStyle for undisplayed
  // elements, since we don't know if any of the style sheet change that we
  // do would affect undisplayed elements.
  MOZ_ASSERT(GetPresContext());
  GetPresContext()->RestyleManager()->IncrementUndisplayedRestyleGeneration();
}

void
ServoStyleSet::RuleAdded(ServoStyleSheet& aSheet, css::Rule& aRule)
{
  if (mStyleRuleMap) {
    mStyleRuleMap->RuleAdded(aSheet, aRule);
  }

  // FIXME(emilio): Could be more granular based on aRule.
  MarkOriginsDirty(aSheet.GetOrigin());
}

void
ServoStyleSet::RuleRemoved(ServoStyleSheet& aSheet, css::Rule& aRule)
{
  if (mStyleRuleMap) {
    mStyleRuleMap->RuleRemoved(aSheet, aRule);
  }

  // FIXME(emilio): Could be more granular based on aRule.
  MarkOriginsDirty(aSheet.GetOrigin());
}

void
ServoStyleSet::RuleChanged(ServoStyleSheet& aSheet, css::Rule* aRule)
{
  // FIXME(emilio): Could be more granular based on aRule.
  MarkOriginsDirty(aSheet.GetOrigin());
}

#ifdef DEBUG
void
ServoStyleSet::AssertTreeIsClean()
{
  DocumentStyleRootIterator iter(mDocument);
  while (Element* root = iter.GetNextStyleRoot()) {
    Servo_AssertTreeIsClean(root);
  }
}
#endif

bool
ServoStyleSet::GetKeyframesForName(nsAtom* aName,
                                   const nsTimingFunction& aTimingFunction,
                                   nsTArray<Keyframe>& aKeyframes)
{
  // TODO(emilio): This may need to look at the element itself for handling
  // @keyframes properly in Shadow DOM.
  MOZ_ASSERT(!StylistNeedsUpdate());
  return Servo_StyleSet_GetKeyframesForName(mRawSet.get(),
                                            aName,
                                            &aTimingFunction,
                                            &aKeyframes);
}

nsTArray<ComputedKeyframeValues>
ServoStyleSet::GetComputedKeyframeValuesFor(
  const nsTArray<Keyframe>& aKeyframes,
  Element* aElement,
  const mozilla::ComputedStyle* aStyle)
{
  nsTArray<ComputedKeyframeValues> result(aKeyframes.Length());

  // Construct each nsTArray<PropertyStyleAnimationValuePair> here.
  result.AppendElements(aKeyframes.Length());

  Servo_GetComputedKeyframeValues(&aKeyframes,
                                  aElement,
                                  aStyle,
                                  mRawSet.get(),
                                  &result);
  return result;
}

void
ServoStyleSet::GetAnimationValues(
  RawServoDeclarationBlock* aDeclarations,
  Element* aElement,
  const ComputedStyle* aComputedStyle,
  nsTArray<RefPtr<RawServoAnimationValue>>& aAnimationValues)
{
  // Servo_GetAnimationValues below won't handle ignoring existing element
  // data for bfcached documents. (See comment in ResolveStyleLazily
  // about these bfcache issues.)
  Servo_GetAnimationValues(aDeclarations,
                           aElement,
                           aComputedStyle,
                           mRawSet.get(),
                           &aAnimationValues);
}

already_AddRefed<ComputedStyle>
ServoStyleSet::GetBaseContextForElement(
  Element* aElement,
  const ComputedStyle* aStyle)
{
  return Servo_StyleSet_GetBaseComputedValuesForElement(mRawSet.get(),
                                                        aElement,
                                                        aStyle,
                                                        &Snapshots()).Consume();
}

already_AddRefed<ComputedStyle>
ServoStyleSet::ResolveServoStyleByAddingAnimation(
  Element* aElement,
  const ComputedStyle* aStyle,
  RawServoAnimationValue* aAnimationValue)
{
  return Servo_StyleSet_GetComputedValuesByAddingAnimation(
    mRawSet.get(),
    aElement,
    aStyle,
    &Snapshots(),
    aAnimationValue).Consume();
}

already_AddRefed<RawServoAnimationValue>
ServoStyleSet::ComputeAnimationValue(
  Element* aElement,
  RawServoDeclarationBlock* aDeclarations,
  const mozilla::ComputedStyle* aStyle)
{
  return Servo_AnimationValue_Compute(aElement,
                                      aDeclarations,
                                      aStyle,
                                      mRawSet.get()).Consume();
}

bool
ServoStyleSet::EnsureUniqueInnerOnCSSSheets()
{
  using SheetOwner = Variant<ServoStyleSet*, nsXBLPrototypeBinding*, ShadowRoot*>;

  AutoTArray<Pair<StyleSheet*, SheetOwner>, 32> queue;
  for (auto& entryArray : mSheets) {
    for (auto& sheet : entryArray) {
      StyleSheet* downcasted = sheet;
      queue.AppendElement(MakePair(downcasted, SheetOwner { this }));
    }
  }

  EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
    for (auto index : IntegerRange(aShadowRoot.SheetCount())) {
      queue.AppendElement(
        MakePair(aShadowRoot.SheetAt(index), SheetOwner { &aShadowRoot }));
    }
  });

  mDocument->BindingManager()->EnumerateBoundContentBindings(
      [&](nsXBLBinding* aBinding) {
        AutoTArray<StyleSheet*, 3> sheets;
        aBinding->PrototypeBinding()->AppendStyleSheetsTo(sheets);
        for (auto* sheet : sheets) {
          queue.AppendElement(MakePair(sheet, SheetOwner { aBinding->PrototypeBinding() }));
        }
        return true;
      });

  bool anyNonDocStyleChanged = false;
  while (!queue.IsEmpty()) {
    uint32_t idx = queue.Length() - 1;
    auto* sheet = queue[idx].first();
    SheetOwner owner = queue[idx].second();
    queue.RemoveElementAt(idx);

    if (!sheet->HasUniqueInner()) {
      if (owner.is<ShadowRoot*>()) {
        Servo_AuthorStyles_ForceDirty(owner.as<ShadowRoot*>()->ServoStyles());
        mNeedsRestyleAfterEnsureUniqueInner = true;
        anyNonDocStyleChanged = true;
      } else if (owner.is<nsXBLPrototypeBinding*>()) {
        if (auto* styles = owner.as<nsXBLPrototypeBinding*>()->GetServoStyles()) {
          Servo_AuthorStyles_ForceDirty(styles);
          mNeedsRestyleAfterEnsureUniqueInner = true;
          anyNonDocStyleChanged = true;
        }
      }
    }

    // Only call EnsureUniqueInner for complete sheets. If we do call it on
    // incomplete sheets, we'll cause problems when the sheet is actually
    // loaded. We don't care about incomplete sheets here anyway, because this
    // method is only invoked by nsPresContext::EnsureSafeToHandOutCSSRules.
    // The CSSRule objects we are handing out won't contain any rules derived
    // from incomplete sheets (because they aren't yet applied in styling).
    if (sheet->IsComplete()) {
      sheet->EnsureUniqueInner();
    }

    // Enqueue all the sheet's children.
    AutoTArray<StyleSheet*, 3> children;
    sheet->AppendAllChildSheets(children);
    for (auto* sheet : children) {
      queue.AppendElement(MakePair(sheet, owner));
    }
  }

  if (anyNonDocStyleChanged) {
    SetStylistXBLStyleSheetsDirty();
  }

  if (mNeedsRestyleAfterEnsureUniqueInner) {
    // TODO(emilio): We could make this faster if needed tracking the specific
    // origins and all that, but the only caller of this doesn't seem to really
    // care about perf.
    MarkOriginsDirty(OriginFlags::All);
  }
  bool res = mNeedsRestyleAfterEnsureUniqueInner;
  mNeedsRestyleAfterEnsureUniqueInner = false;
  return res;
}

void
ServoStyleSet::ClearCachedStyleData()
{
  ClearNonInheritingComputedStyles();
  Servo_StyleSet_RebuildCachedData(mRawSet.get());
}

void
ServoStyleSet::CompatibilityModeChanged()
{
  Servo_StyleSet_CompatModeChanged(mRawSet.get());
  SetStylistStyleSheetsDirty();
}

void
ServoStyleSet::ClearNonInheritingComputedStyles()
{
  for (RefPtr<ComputedStyle>& ptr : mNonInheritingComputedStyles) {
    ptr = nullptr;
  }
}

already_AddRefed<ComputedStyle>
ServoStyleSet::ResolveStyleLazilyInternal(Element* aElement,
                                          CSSPseudoElementType aPseudoType,
                                          StyleRuleInclusion aRuleInclusion)
{
  MOZ_ASSERT(GetPresContext(),
             "For now, no style resolution without a pres context");
  GetPresContext()->EffectCompositor()->PreTraverse(aElement, aPseudoType);
  MOZ_ASSERT(!StylistNeedsUpdate());

  AutoSetInServoTraversal guard(this);

  /**
   * NB: This is needed because we process animations and transitions on the
   * pseudo-elements themselves, not on the parent's EagerPseudoStyles.
   *
   * That means that that style doesn't account for animations, and we can't do
   * that easily from the traversal without doing wasted work.
   *
   * As such, we just lie here a bit, which is the entrypoint of
   * getComputedStyle, the only API where this can be observed, to look at the
   * style of the pseudo-element if it exists instead.
   */
  Element* elementForStyleResolution = aElement;
  CSSPseudoElementType pseudoTypeForStyleResolution = aPseudoType;
  if (aPseudoType == CSSPseudoElementType::before) {
    if (Element* pseudo = nsLayoutUtils::GetBeforePseudo(aElement)) {
      elementForStyleResolution = pseudo;
      pseudoTypeForStyleResolution = CSSPseudoElementType::NotPseudo;
    }
  } else if (aPseudoType == CSSPseudoElementType::after) {
    if (Element* pseudo = nsLayoutUtils::GetAfterPseudo(aElement)) {
      elementForStyleResolution = pseudo;
      pseudoTypeForStyleResolution = CSSPseudoElementType::NotPseudo;
    }
  }

  RefPtr<ComputedStyle> computedValues =
    Servo_ResolveStyleLazily(elementForStyleResolution,
                             pseudoTypeForStyleResolution,
                             aRuleInclusion,
                             &Snapshots(),
                             mRawSet.get()).Consume();

  if (GetPresContext()->EffectCompositor()->PreTraverse(aElement, aPseudoType)) {
    computedValues =
      Servo_ResolveStyleLazily(elementForStyleResolution,
                               pseudoTypeForStyleResolution,
                               aRuleInclusion,
                               &Snapshots(),
                               mRawSet.get()).Consume();
  }

  MOZ_DIAGNOSTIC_ASSERT(computedValues->PresContextForFrame() == GetPresContext() ||
                        aElement->OwnerDoc()->GetBFCacheEntry());

  return computedValues.forget();
}

bool
ServoStyleSet::AppendFontFaceRules(nsTArray<nsFontFaceRuleContainer>& aArray)
{
  UpdateStylistIfNeeded();
  Servo_StyleSet_GetFontFaceRules(mRawSet.get(), &aArray);
  return true;
}

const RawServoCounterStyleRule*
ServoStyleSet::CounterStyleRuleForName(nsAtom* aName)
{
  MOZ_ASSERT(!StylistNeedsUpdate());
  return Servo_StyleSet_GetCounterStyleRule(mRawSet.get(), aName);
}

already_AddRefed<gfxFontFeatureValueSet>
ServoStyleSet::BuildFontFeatureValueSet()
{
  // FIXME(emilio): This should assert once we update the stylist from
  // FlushPendingNotifications explicitly.
  UpdateStylistIfNeeded();
  RefPtr<gfxFontFeatureValueSet> set =
    Servo_StyleSet_BuildFontFeatureValueSet(mRawSet.get());
  return set.forget();
}

already_AddRefed<ComputedStyle>
ServoStyleSet::ResolveForDeclarations(
  const ComputedStyle* aParentOrNull,
  RawServoDeclarationBlockBorrowed aDeclarations)
{
  // No need to update the stylist, we're only cascading aDeclarations.
  return Servo_StyleSet_ResolveForDeclarations(mRawSet.get(),
                                               aParentOrNull,
                                               aDeclarations).Consume();
}

void
ServoStyleSet::UpdateStylist()
{
  MOZ_ASSERT(StylistNeedsUpdate());

  if (mStylistState & StylistState::StyleSheetsDirty) {
    // There's no need to compute invalidations and such for an XBL styleset,
    // since they are loaded and unloaded synchronously, and they don't have to
    // deal with dynamic content changes.
    Element* root = mDocument->GetRootElement();
    const ServoElementSnapshotTable* snapshots = nullptr;
    if (nsPresContext* pc = GetPresContext()) {
      snapshots = &pc->RestyleManager()->Snapshots();
    }
    Servo_StyleSet_FlushStyleSheets(mRawSet.get(), root, snapshots);
  }

  if (MOZ_UNLIKELY(mStylistState & StylistState::XBLStyleSheetsDirty)) {
    MOZ_ASSERT(GetPresContext(), "How did they get dirty?");

    EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
      Servo_AuthorStyles_Flush(aShadowRoot.ServoStyles(), mRawSet.get());
    });

    mDocument->BindingManager()->EnumerateBoundContentBindings(
      [&](nsXBLBinding* aBinding) {
        if (auto* authorStyles = aBinding->PrototypeBinding()->GetServoStyles()) {
          Servo_AuthorStyles_Flush(authorStyles, mRawSet.get());
        }
        return true;
      });
  }

  mStylistState = StylistState::NotDirty;
}

void
ServoStyleSet::MaybeGCRuleTree()
{
  MOZ_ASSERT(NS_IsMainThread());
  Servo_MaybeGCRuleTree(mRawSet.get());
}

/* static */ bool
ServoStyleSet::MayTraverseFrom(const Element* aElement)
{
  MOZ_ASSERT(aElement->IsInComposedDoc());
  nsINode* parent = aElement->GetFlattenedTreeParentNodeForStyle();
  if (!parent) {
    return false;
  }

  if (!parent->IsElement()) {
    MOZ_ASSERT(parent->IsNodeOfType(nsINode::eDOCUMENT));
    return true;
  }

  if (!parent->AsElement()->HasServoData()) {
    return false;
  }

  return !Servo_Element_IsDisplayNone(parent->AsElement());
}

bool
ServoStyleSet::ShouldTraverseInParallel() const
{
  MOZ_ASSERT(mDocument->GetShell(), "Styling a document without a shell?");
  return mDocument->GetShell()->IsActive();
}

void
ServoStyleSet::PrependSheetOfType(SheetType aType,
                                  ServoStyleSheet* aSheet)
{
  aSheet->AddStyleSet(this);
  mSheets[aType].InsertElementAt(0, aSheet);
}

void
ServoStyleSet::AppendSheetOfType(SheetType aType,
                                 ServoStyleSheet* aSheet)
{
  aSheet->AddStyleSet(this);
  mSheets[aType].AppendElement(aSheet);
}

void
ServoStyleSet::InsertSheetOfType(SheetType aType,
                                 ServoStyleSheet* aSheet,
                                 ServoStyleSheet* aBeforeSheet)
{
  for (uint32_t i = 0; i < mSheets[aType].Length(); ++i) {
    if (mSheets[aType][i] == aBeforeSheet) {
      aSheet->AddStyleSet(this);
      mSheets[aType].InsertElementAt(i, aSheet);
      return;
    }
  }
}

void
ServoStyleSet::RemoveSheetOfType(SheetType aType,
                                 ServoStyleSheet* aSheet)
{
  for (uint32_t i = 0; i < mSheets[aType].Length(); ++i) {
    if (mSheets[aType][i] == aSheet) {
      aSheet->DropStyleSet(this);
      mSheets[aType].RemoveElementAt(i);
    }
  }
}

void
ServoStyleSet::RunPostTraversalTasks()
{
  MOZ_ASSERT(!IsInServoTraversal());

  if (mPostTraversalTasks.IsEmpty()) {
    return;
  }

  nsTArray<PostTraversalTask> tasks;
  tasks.SwapElements(mPostTraversalTasks);

  for (auto& task : tasks) {
    task.Run();
  }
}

ServoStyleRuleMap*
ServoStyleSet::StyleRuleMap()
{
  if (!mStyleRuleMap) {
    mStyleRuleMap = MakeUnique<ServoStyleRuleMap>();
  }
  mStyleRuleMap->EnsureTable(*this);
  return mStyleRuleMap.get();
}

bool
ServoStyleSet::MightHaveAttributeDependency(const Element& aElement,
                                            nsAtom* aAttribute) const
{
  return Servo_StyleSet_MightHaveAttributeDependency(
      mRawSet.get(), &aElement, aAttribute);
}

bool
ServoStyleSet::HasStateDependency(const Element& aElement,
                                  EventStates aState) const
{
  return Servo_StyleSet_HasStateDependency(
      mRawSet.get(), &aElement, aState.ServoValue());
}

bool
ServoStyleSet::HasDocumentStateDependency(EventStates aState) const
{
  return Servo_StyleSet_HasDocumentStateDependency(
      mRawSet.get(), aState.ServoValue());
}

already_AddRefed<ComputedStyle>
ServoStyleSet::ReparentComputedStyle(ComputedStyle* aComputedStyle,
                                     ComputedStyle* aNewParent,
                                     ComputedStyle* aNewParentIgnoringFirstLine,
                                     ComputedStyle* aNewLayoutParent,
                                     Element* aElement)
{
  return Servo_ReparentStyle(aComputedStyle, aNewParent,
                             aNewParentIgnoringFirstLine, aNewLayoutParent,
                             aElement, mRawSet.get()).Consume();
}

NS_IMPL_ISUPPORTS(UACacheReporter, nsIMemoryReporter)

MOZ_DEFINE_MALLOC_SIZE_OF(ServoUACacheMallocSizeOf)
MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ServoUACacheMallocEnclosingSizeOf)

NS_IMETHODIMP
UACacheReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
                                nsISupports* aData, bool aAnonymize)
{
  ServoStyleSetSizes sizes;
  Servo_UACache_AddSizeOf(ServoUACacheMallocSizeOf,
                          ServoUACacheMallocEnclosingSizeOf, &sizes);

#define REPORT(_path, _amount, _desc) \
  do { \
    size_t __amount = _amount;  /* evaluate _amount only once */ \
    if (__amount > 0) { \
      MOZ_COLLECT_REPORT(_path, KIND_HEAP, UNITS_BYTES, __amount, _desc); \
    } \
  } while (0)

  // The UA cache does not contain the rule tree; that's in the StyleSet.
  MOZ_RELEASE_ASSERT(sizes.mRuleTree == 0);

  REPORT("explicit/layout/servo-ua-cache/precomputed-pseudos",
         sizes.mPrecomputedPseudos,
         "Memory used by precomputed pseudo-element declarations within the "
         "UA cache.");

  REPORT("explicit/layout/servo-ua-cache/element-and-pseudos-maps",
         sizes.mElementAndPseudosMaps,
         "Memory used by element and pseudos maps within the UA cache.");

  REPORT("explicit/layout/servo-ua-cache/invalidation-map",
         sizes.mInvalidationMap,
         "Memory used by invalidation maps within the UA cache.");

  REPORT("explicit/layout/servo-ua-cache/revalidation-selectors",
         sizes.mRevalidationSelectors,
         "Memory used by selectors for cache revalidation within the UA "
         "cache.");

  REPORT("explicit/layout/servo-ua-cache/other",
         sizes.mOther,
         "Memory used by other data within the UA cache");

  return NS_OK;
}