layout/style/Loader.cpp
author Jeff Walden <jwalden@mit.edu>
Wed, 11 Dec 2019 06:17:44 +0000
changeset 506368 f83f2771414cf5938a46f44e1b11cdcd5181ea0f
parent 505802 73820a049c644c3f3af58cece3bdb7cd0e4b2185
permissions -rw-r--r--
Bug 1602882 - Move array operations to a new js/Array.h header. r=sfink,bzbarsky Differential Revision: https://phabricator.services.mozilla.com/D56595

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

/* loading of CSS style sheets using the network APIs */

#include "mozilla/css/Loader.h"

#include "mozilla/ArrayUtils.h"
#include "mozilla/dom/DocGroup.h"
#include "mozilla/dom/SRILogHelper.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/Logging.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/SystemGroup.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/URLPreloader.h"
#include "nsIRunnable.h"
#include "nsITimedChannel.h"
#include "nsSyncLoadService.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsIContent.h"
#include "nsIContentInlines.h"
#include "mozilla/dom/Document.h"
#include "nsIURI.h"
#include "nsNetUtil.h"
#include "nsContentUtils.h"
#include "nsIScriptSecurityManager.h"
#include "nsContentPolicyUtils.h"
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIClassOfService.h"
#include "nsIScriptError.h"
#include "nsMimeTypes.h"
#include "nsIStyleSheetLinkingElement.h"
#include "nsICSSLoaderObserver.h"
#include "nsThreadUtils.h"
#include "nsGkAtoms.h"
#include "nsIThreadInternal.h"
#include "nsINetworkPredictor.h"
#include "nsStringStream.h"
#include "mozilla/dom/MediaList.h"
#include "mozilla/dom/ShadowRoot.h"
#include "mozilla/dom/URL.h"
#include "mozilla/net/UrlClassifierFeatureFactory.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/ServoBindings.h"
#include "mozilla/StyleSheet.h"
#include "mozilla/StyleSheetInlines.h"
#include "mozilla/ConsoleReportCollector.h"
#include "mozilla/ServoUtils.h"
#include "mozilla/css/StreamLoader.h"
#include "ReferrerInfo.h"

#ifdef MOZ_XUL
#  include "nsXULPrototypeCache.h"
#endif

#include "nsError.h"

#include "mozilla/dom/SRICheck.h"

#include "mozilla/Encoding.h"

using namespace mozilla::dom;

// 1024 bytes is specified in https://drafts.csswg.org/css-syntax/
#define SNIFFING_BUFFER_SIZE 1024

/**
 * OVERALL ARCHITECTURE
 *
 * The CSS Loader gets requests to load various sorts of style sheets:
 * inline style from <style> elements, linked style, @import-ed child
 * sheets, non-document sheets.  The loader handles the following tasks:
 * 1) Creation of the actual style sheet objects: CreateSheet()
 * 2) setting of the right media, title, enabled state, etc on the
 *    sheet: PrepareSheet()
 * 3) Insertion of the sheet in the proper cascade order:
 *    InsertSheetInTree() and InsertChildSheet()
 * 4) Load of the sheet: LoadSheet() including security checks
 * 5) Parsing of the sheet: ParseSheet()
 * 6) Cleanup: SheetComplete()
 *
 * The detailed documentation for these functions is found with the
 * function implementations.
 *
 * The following helper object is used:
 *    SheetLoadData -- a small class that is used to store all the
 *                     information needed for the loading of a sheet;
 *                     this class handles listening for the stream
 *                     loader completion and also handles charset
 *                     determination.
 */

static mozilla::LazyLogModule sCssLoaderLog("nsCSSLoader");

static mozilla::LazyLogModule gSriPRLog("SRI");

#define LOG_ERROR(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Error, args)
#define LOG_WARN(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Warning, args)
#define LOG_DEBUG(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Debug, args)
#define LOG(args) LOG_DEBUG(args)

#define LOG_ERROR_ENABLED() \
  MOZ_LOG_TEST(sCssLoaderLog, mozilla::LogLevel::Error)
#define LOG_WARN_ENABLED() \
  MOZ_LOG_TEST(sCssLoaderLog, mozilla::LogLevel::Warning)
#define LOG_DEBUG_ENABLED() \
  MOZ_LOG_TEST(sCssLoaderLog, mozilla::LogLevel::Debug)
#define LOG_ENABLED() LOG_DEBUG_ENABLED()

#define LOG_URI(format, uri)                      \
  PR_BEGIN_MACRO                                  \
  NS_ASSERTION(uri, "Logging null uri");          \
  if (LOG_ENABLED()) {                            \
    LOG((format, uri->GetSpecOrDefault().get())); \
  }                                               \
  PR_END_MACRO

// And some convenience strings...
static const char* const gStateStrings[] = {"Unknown", "NeedsParser", "Pending",
                                            "Loading", "Complete"};

namespace mozilla {

class SheetLoadDataHashKey : public nsURIHashKey {
 public:
  typedef SheetLoadDataHashKey* KeyType;
  typedef const SheetLoadDataHashKey* KeyTypePointer;

  explicit SheetLoadDataHashKey(const SheetLoadDataHashKey* aKey)
      : nsURIHashKey(aKey->mKey),
        mPrincipal(aKey->mPrincipal),
        mReferrerInfo(aKey->mReferrerInfo),
        mCORSMode(aKey->mCORSMode),
        mParsingMode(aKey->mParsingMode) {
    MOZ_COUNT_CTOR(SheetLoadDataHashKey);
  }

  SheetLoadDataHashKey(nsIURI* aURI, nsIPrincipal* aPrincipal,
                       nsIReferrerInfo* aReferrerInfo, CORSMode aCORSMode,
                       css::SheetParsingMode aParsingMode)
      : nsURIHashKey(aURI),
        mPrincipal(aPrincipal),
        mReferrerInfo(aReferrerInfo),
        mCORSMode(aCORSMode),
        mParsingMode(aParsingMode) {
    MOZ_COUNT_CTOR(SheetLoadDataHashKey);
  }

  SheetLoadDataHashKey(SheetLoadDataHashKey&& toMove)
      : nsURIHashKey(std::move(toMove)),
        mPrincipal(std::move(toMove.mPrincipal)),
        mReferrerInfo(std::move(toMove.mReferrerInfo)),
        mCORSMode(std::move(toMove.mCORSMode)),
        mParsingMode(std::move(toMove.mParsingMode)) {
    MOZ_COUNT_CTOR(SheetLoadDataHashKey);
  }

  explicit SheetLoadDataHashKey(css::SheetLoadData&);

  ~SheetLoadDataHashKey() { MOZ_COUNT_DTOR(SheetLoadDataHashKey); }

  SheetLoadDataHashKey* GetKey() const {
    return const_cast<SheetLoadDataHashKey*>(this);
  }
  const SheetLoadDataHashKey* GetKeyPointer() const { return this; }

  bool KeyEquals(const SheetLoadDataHashKey* aKey) const {
    if (!nsURIHashKey::KeyEquals(aKey->mKey)) {
      return false;
    }

    if (!mPrincipal != !aKey->mPrincipal) {
      // One or the other has a principal, but not both... not equal
      return false;
    }

    if (mCORSMode != aKey->mCORSMode) {
      // Different CORS modes; we don't match
      return false;
    }

    if (mParsingMode != aKey->mParsingMode) {
      return false;
    }

    bool eq;
    if (NS_FAILED(mReferrerInfo->Equals(aKey->mReferrerInfo, &eq)) || !eq) {
      return false;
    }

    return !mPrincipal ||
           (NS_SUCCEEDED(mPrincipal->Equals(aKey->mPrincipal, &eq)) && eq);
  }

  static const SheetLoadDataHashKey* KeyToPointer(SheetLoadDataHashKey* aKey) {
    return aKey;
  }
  static PLDHashNumber HashKey(const SheetLoadDataHashKey* aKey) {
    return nsURIHashKey::HashKey(aKey->mKey);
  }

  nsIURI* GetURI() const { return nsURIHashKey::GetKey(); }

  nsIPrincipal* GetPrincipal() const { return mPrincipal; }

  css::SheetParsingMode ParsingMode() const { return mParsingMode; }

  enum { ALLOW_MEMMOVE = true };

 protected:
  nsCOMPtr<nsIPrincipal> mPrincipal;
  nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
  CORSMode mCORSMode;
  css::SheetParsingMode mParsingMode;
};

SheetLoadDataHashKey::SheetLoadDataHashKey(css::SheetLoadData& aLoadData)
    : nsURIHashKey(aLoadData.mURI),
      mPrincipal(aLoadData.mLoaderPrincipal),
      mReferrerInfo(aLoadData.ReferrerInfo()),
      mCORSMode(aLoadData.mSheet->GetCORSMode()),
      mParsingMode(aLoadData.mSheet->ParsingMode()) {
  MOZ_COUNT_CTOR(SheetLoadDataHashKey);
}
}  // namespace mozilla

namespace mozilla {
namespace css {

/********************************
 * SheetLoadData implementation *
 ********************************/
NS_IMPL_ISUPPORTS(SheetLoadData, nsIRunnable, nsIThreadObserver)

SheetLoadData::SheetLoadData(
    Loader* aLoader, const nsAString& aTitle, nsIURI* aURI, StyleSheet* aSheet,
    bool aSyncLoad, nsIStyleSheetLinkingElement* aOwningElement,
    IsAlternate aIsAlternate, MediaMatched aMediaMatches,
    nsICSSLoaderObserver* aObserver, nsIPrincipal* aLoaderPrincipal,
    nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode)
    : mLoader(aLoader),
      mTitle(aTitle),
      mEncoding(nullptr),
      mURI(aURI),
      mLineNumber(1),
      mSheet(aSheet),
      mNext(nullptr),
      mPendingChildren(0),
      mSyncLoad(aSyncLoad),
      mIsNonDocumentSheet(false),
      mIsLoading(false),
      mIsBeingParsed(false),
      mIsCancelled(false),
      mMustNotify(false),
      mWasAlternate(aIsAlternate == IsAlternate::Yes),
      mMediaMatched(aMediaMatches == MediaMatched::Yes),
      mUseSystemPrincipal(false),
      mSheetAlreadyComplete(false),
      mIsCrossOriginNoCORS(false),
      mBlockResourceTiming(false),
      mLoadFailed(false),
      mOwningElement(aOwningElement),
      mObserver(aObserver),
      mLoaderPrincipal(aLoaderPrincipal),
      mReferrerInfo(aReferrerInfo),
      mRequestingNode(aRequestingNode),
      mPreloadEncoding(nullptr) {
  MOZ_ASSERT(mLoader, "Must have a loader!");
}

SheetLoadData::SheetLoadData(Loader* aLoader, nsIURI* aURI, StyleSheet* aSheet,
                             SheetLoadData* aParentData,
                             nsICSSLoaderObserver* aObserver,
                             nsIPrincipal* aLoaderPrincipal,
                             nsIReferrerInfo* aReferrerInfo,
                             nsINode* aRequestingNode)
    : mLoader(aLoader),
      mEncoding(nullptr),
      mURI(aURI),
      mLineNumber(1),
      mSheet(aSheet),
      mNext(nullptr),
      mParentData(aParentData),
      mPendingChildren(0),
      mSyncLoad(false),
      mIsNonDocumentSheet(false),
      mIsLoading(false),
      mIsBeingParsed(false),
      mIsCancelled(false),
      mMustNotify(false),
      mWasAlternate(false),
      mMediaMatched(true),
      mUseSystemPrincipal(false),
      mSheetAlreadyComplete(false),
      mIsCrossOriginNoCORS(false),
      mBlockResourceTiming(false),
      mLoadFailed(false),
      mOwningElement(nullptr),
      mObserver(aObserver),
      mLoaderPrincipal(aLoaderPrincipal),
      mReferrerInfo(aReferrerInfo),
      mRequestingNode(aRequestingNode),
      mPreloadEncoding(nullptr) {
  MOZ_ASSERT(mLoader, "Must have a loader!");
  if (mParentData) {
    mSyncLoad = mParentData->mSyncLoad;
    mIsNonDocumentSheet = mParentData->mIsNonDocumentSheet;
    mUseSystemPrincipal = mParentData->mUseSystemPrincipal;
    ++(mParentData->mPendingChildren);
  }

  MOZ_ASSERT(!mUseSystemPrincipal || mSyncLoad,
             "Shouldn't use system principal for async loads");
}

SheetLoadData::SheetLoadData(Loader* aLoader, nsIURI* aURI, StyleSheet* aSheet,
                             bool aSyncLoad, bool aUseSystemPrincipal,
                             const Encoding* aPreloadEncoding,
                             nsICSSLoaderObserver* aObserver,
                             nsIPrincipal* aLoaderPrincipal,
                             nsIReferrerInfo* aReferrerInfo,
                             nsINode* aRequestingNode)
    : mLoader(aLoader),
      mEncoding(nullptr),
      mURI(aURI),
      mLineNumber(1),
      mSheet(aSheet),
      mNext(nullptr),
      mPendingChildren(0),
      mSyncLoad(aSyncLoad),
      mIsNonDocumentSheet(true),
      mIsLoading(false),
      mIsBeingParsed(false),
      mIsCancelled(false),
      mMustNotify(false),
      mWasAlternate(false),
      mMediaMatched(true),
      mUseSystemPrincipal(aUseSystemPrincipal),
      mSheetAlreadyComplete(false),
      mIsCrossOriginNoCORS(false),
      mBlockResourceTiming(false),
      mLoadFailed(false),
      mOwningElement(nullptr),
      mObserver(aObserver),
      mLoaderPrincipal(aLoaderPrincipal),
      mReferrerInfo(aReferrerInfo),
      mRequestingNode(aRequestingNode),
      mPreloadEncoding(aPreloadEncoding) {
  MOZ_ASSERT(mLoader, "Must have a loader!");
  MOZ_ASSERT(!mUseSystemPrincipal || mSyncLoad,
             "Shouldn't use system principal for async loads");
}

SheetLoadData::~SheetLoadData() {
  // Do this iteratively to avoid blowing up the stack.
  RefPtr<SheetLoadData> next = mNext.forget();
  while (next) {
    next = next->mNext.forget();
  }
}

NS_IMETHODIMP
SheetLoadData::Run() {
  mLoader->HandleLoadEvent(*this);
  return NS_OK;
}

NS_IMETHODIMP
SheetLoadData::OnDispatchedEvent() { return NS_OK; }

NS_IMETHODIMP
SheetLoadData::OnProcessNextEvent(nsIThreadInternal* aThread, bool aMayWait) {
  // XXXkhuey this is insane!
  // We want to fire our load even before or after event processing,
  // whichever comes first.
  FireLoadEvent(aThread);
  return NS_OK;
}

NS_IMETHODIMP
SheetLoadData::AfterProcessNextEvent(nsIThreadInternal* aThread,
                                     bool aEventWasProcessed) {
  // XXXkhuey this too!
  // We want to fire our load even before or after event processing,
  // whichever comes first.
  FireLoadEvent(aThread);
  return NS_OK;
}

void SheetLoadData::FireLoadEvent(nsIThreadInternal* aThread) {
  // First remove ourselves as a thread observer.  But we need to keep
  // ourselves alive while doing that!
  RefPtr<SheetLoadData> kungFuDeathGrip(this);
  aThread->RemoveObserver(this);

  // Now fire the event
  nsCOMPtr<nsINode> node = do_QueryInterface(mOwningElement);
  NS_ASSERTION(node, "How did that happen???");

  nsContentUtils::DispatchTrustedEvent(
      node->OwnerDoc(), node,
      mLoadFailed ? NS_LITERAL_STRING("error") : NS_LITERAL_STRING("load"),
      CanBubble::eNo, Cancelable::eNo);

  // And unblock onload
  mLoader->UnblockOnload(true);
}

void SheetLoadData::ScheduleLoadEventIfNeeded() {
  if (!mOwningElement) {
    return;
  }

  nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
  nsCOMPtr<nsIThreadInternal> internalThread = do_QueryInterface(thread);
  if (NS_SUCCEEDED(internalThread->AddObserver(this))) {
    // Make sure to block onload here
    mLoader->BlockOnload();
  }
}

/*********************
 * Style sheet reuse *
 *********************/

bool LoaderReusableStyleSheets::FindReusableStyleSheet(
    nsIURI* aURL, RefPtr<StyleSheet>& aResult) {
  MOZ_ASSERT(aURL);
  for (size_t i = mReusableSheets.Length(); i > 0; --i) {
    size_t index = i - 1;
    bool sameURI;
    MOZ_ASSERT(mReusableSheets[index]->GetOriginalURI());
    nsresult rv =
        aURL->Equals(mReusableSheets[index]->GetOriginalURI(), &sameURI);
    if (!NS_FAILED(rv) && sameURI) {
      aResult = mReusableSheets[index];
      mReusableSheets.RemoveElementAt(index);
      return true;
    }
  }
  return false;
}

// A struct keeping alive various records of sheets that are loading, deferred,
// or already loaded (the later one for caching purposes).
struct Loader::Sheets {
  nsRefPtrHashtable<SheetLoadDataHashKey, StyleSheet> mCompleteSheets;

  nsRefPtrHashtable<SheetLoadDataHashKey, SheetLoadData> mPendingDatas;

  // The SheetLoadData pointers in mLoadingDatas below are weak references.
  nsDataHashtable<SheetLoadDataHashKey, SheetLoadData*> mLoadingDatas;

  nsRefPtrHashtable<nsStringHashKey, StyleSheet> mInlineSheets;

  RefPtr<StyleSheet> LookupInline(const nsAString&);

  // A cache hit or miss. It is a miss if the `StyleSheet` is null.
  using CacheResult = Tuple<RefPtr<StyleSheet>, SheetState>;
  CacheResult Lookup(SheetLoadDataHashKey&, bool aSyncLoad);

  size_t SizeOfIncludingThis(MallocSizeOf) const;
};

RefPtr<StyleSheet> Loader::Sheets::LookupInline(const nsAString& aBuffer) {
  auto result = mInlineSheets.Lookup(aBuffer);
  if (!result) {
    return nullptr;
  }
  if (result.Data()->HasModifiedRules()) {
    // Remove it now that we know that we're never going to use this stylesheet
    // again.
    result.Remove();
    return nullptr;
  }
  return result.Data()->Clone(nullptr, nullptr, nullptr, nullptr);
}

static void AssertComplete(const StyleSheet& aSheet) {
  // This sheet came from the XUL cache or our per-document hashtable; it
  // better be a complete sheet.
  MOZ_ASSERT(aSheet.IsComplete(),
             "Sheet thinks it's not complete while we think it is");
}

static void AssertIncompleteSheetMatches(const SheetLoadData& aData,
                                         const SheetLoadDataHashKey& aKey) {
#ifdef DEBUG
  bool debugEqual;
  MOZ_ASSERT((!aKey.GetPrincipal() && !aData.mLoaderPrincipal) ||
                 (aKey.GetPrincipal() && aData.mLoaderPrincipal &&
                  NS_SUCCEEDED(aKey.GetPrincipal()->Equals(
                      aData.mLoaderPrincipal, &debugEqual)) &&
                  debugEqual),
             "Principals should be the same");
#endif
  MOZ_ASSERT(!aData.mSheet->HasForcedUniqueInner(),
             "CSSOM shouldn't allow access to incomplete sheets");
}

auto Loader::Sheets::Lookup(SheetLoadDataHashKey& aKey, bool aSyncLoad)
    -> CacheResult {
  auto CloneSheet = [](StyleSheet& aSheet) -> RefPtr<StyleSheet> {
    return aSheet.Clone(nullptr, nullptr, nullptr, nullptr);
  };

  nsIURI* uri = aKey.GetURI();
  // Try to find first in the XUL prototype cache.
#ifdef MOZ_XUL
  if (IsChromeURI(uri)) {
    nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
    if (cache && cache->IsEnabled()) {
      if (StyleSheet* sheet = cache->GetStyleSheet(uri)) {
        LOG(("  From XUL cache: %p", sheet));
        AssertComplete(*sheet);
        // We need to check the parsing mode manually because the XUL cache only
        // keys off the URI. See below for the unique inner check.
        if (!sheet->HasModifiedRules() &&
            sheet->ParsingMode() == aKey.ParsingMode()) {
          return MakeTuple(CloneSheet(*sheet), SheetState::Complete);
        }
        LOG(
            ("    Not cloning due to forced unique inner or mismatched "
             "parsing mode"));
      }
    }
  }
#endif

  // Now complete sheets.
  if (auto lookup = mCompleteSheets.Lookup(&aKey)) {
    LOG(("  From completed: %p", lookup.Data().get()));
    AssertComplete(*lookup.Data());
    MOZ_ASSERT(lookup.Data()->ParsingMode() == aKey.ParsingMode());
    // Make sure the stylesheet hasn't been modified, as otherwise it may not
    // contain the rules we care about.
    if (!lookup.Data()->HasModifiedRules()) {
      RefPtr<StyleSheet>& cachedSheet = lookup.Data();
      RefPtr<StyleSheet> clone = CloneSheet(*cachedSheet);
      MOZ_ASSERT(!clone->HasForcedUniqueInner());
      MOZ_ASSERT(!clone->HasModifiedRules());

      const bool oldSheetIsWorthKeeping = ([&cachedSheet] {
        // If our current stylesheet in the cache has been touched by CSSOM, we
        // need to do a full copy of it. The new clone still hasn't been
        // touched, so we have better odds of doing a less-expensive clone in
        // the future.
        if (cachedSheet->HasForcedUniqueInner()) {
          return false;
        }
        // The sheet we're cloning isn't actually referenced by anyone.  Replace
        // it in the cache, so that if our CSSOM is later modified we don't end
        // up with two copies of our inner hanging around.
        if (!cachedSheet->GetOwnerNode() && !cachedSheet->GetParentSheet()) {
          return false;
        }
        return true;
      }());

      if (!oldSheetIsWorthKeeping) {
        cachedSheet = clone;
      }

      return MakeTuple(std::move(clone), SheetState::Complete);
    }
    LOG(("    Not cloning due to modified rules"));
    // Remove it now that we know that we're never going to use this stylesheet
    // again.
    lookup.Remove();
  }

  if (aSyncLoad) {
    return {};
  }

  if (SheetLoadData* data = mLoadingDatas.Get(&aKey)) {
    LOG(("  From loading: %p", data->mSheet.get()));
    AssertIncompleteSheetMatches(*data, aKey);
    return MakeTuple(CloneSheet(*data->mSheet), SheetState::Loading);
  }

  if (SheetLoadData* data = mPendingDatas.GetWeak(&aKey)) {
    LOG(("  From pending: %p", data->mSheet.get()));
    AssertIncompleteSheetMatches(*data, aKey);
    return MakeTuple(CloneSheet(*data->mSheet), SheetState::Pending);
  }

  return {};
}

size_t Loader::Sheets::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
  size_t n = aMallocSizeOf(this);

  n += mCompleteSheets.ShallowSizeOfExcludingThis(aMallocSizeOf);
  for (auto iter = mCompleteSheets.ConstIter(); !iter.Done(); iter.Next()) {
    // If the sheet has a parent, then its parent will report it so we don't
    // have to worry about it here. Likewise, if aSheet has an owning node, then
    // the document that node is in will report it.
    const StyleSheet* sheet = iter.UserData();
    if (!sheet->GetOwnerNode() && !sheet->GetParentSheet()) {
      n += sheet->SizeOfIncludingThis(aMallocSizeOf);
    }
  }

  n += mInlineSheets.ShallowSizeOfExcludingThis(aMallocSizeOf);
  for (auto iter = mInlineSheets.ConstIter(); !iter.Done(); iter.Next()) {
    n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
    // If the sheet has a parent, then its parent will report it so we don't
    // have to worry about it here.
    const StyleSheet* sheet = iter.UserData();
    MOZ_ASSERT(!sheet->GetParentSheet(),
               "How did an @import rule end up here?");
    if (!sheet->GetOwnerNode()) {
      n += sheet->SizeOfIncludingThis(aMallocSizeOf);
    }
  }

  // Measurement of the following members may be added later if DMD finds it is
  // worthwhile:
  // - mLoadingDatas: transient, and should be small
  // - mPendingDatas: transient, and should be small
  return n;
}

/*************************
 * Loader Implementation *
 *************************/

Loader::Loader()
    : mDocument(nullptr),
      mDatasToNotifyOn(0),
      mCompatMode(eCompatibility_FullStandards),
      mEnabled(true),
      mReporter(new ConsoleReportCollector()) {}

Loader::Loader(DocGroup* aDocGroup) : Loader() { mDocGroup = aDocGroup; }

Loader::Loader(Document* aDocument) : Loader() {
  MOZ_ASSERT(aDocument, "We should get a valid document from the caller!");
  mDocument = aDocument;
  mCompatMode = aDocument->GetCompatibilityMode();
}

Loader::~Loader() {
  NS_ASSERTION(!mSheets || mSheets->mLoadingDatas.Count() == 0,
               "How did we get destroyed when there are loading data?");
  NS_ASSERTION(!mSheets || mSheets->mPendingDatas.Count() == 0,
               "How did we get destroyed when there are pending data?");
  // Note: no real need to revoke our stylesheet loaded events -- they
  // hold strong references to us, so if we're going away that means
  // they're all done.
}

void Loader::DropDocumentReference(void) {
  mDocument = nullptr;
  // Flush out pending datas just so we don't leak by accident.  These
  // loads should short-circuit through the mDocument check in
  // LoadSheet and just end up in SheetComplete immediately
  if (mSheets) {
    StartDeferredLoads();
  }
}

void Loader::DocumentStyleSheetSetChanged() {
  MOZ_ASSERT(mDocument);

  // start any pending alternates that aren't alternates anymore
  if (!mSheets) {
    return;
  }

  LoadDataArray arr(mSheets->mPendingDatas.Count());
  for (auto iter = mSheets->mPendingDatas.Iter(); !iter.Done(); iter.Next()) {
    RefPtr<SheetLoadData>& data = iter.Data();
    MOZ_ASSERT(data, "Must have a data");

    // Note that we don't want to affect what the selected style set is, so
    // use true for aHasAlternateRel.
    auto isAlternate = data->mLoader->IsAlternateSheet(data->mTitle, true);
    if (isAlternate == IsAlternate::No) {
      arr.AppendElement(std::move(data));
      iter.Remove();
    }
  }

  mDatasToNotifyOn += arr.Length();
  for (RefPtr<SheetLoadData>& data : arr) {
    --mDatasToNotifyOn;
    LoadSheet(*data, SheetState::NeedsParser, IsPreload::No);
  }
}

static const char kCharsetSym[] = "@charset \"";

static bool GetCharsetFromData(const char* aStyleSheetData,
                               uint32_t aDataLength, nsACString& aCharset) {
  aCharset.Truncate();
  if (aDataLength <= sizeof(kCharsetSym) - 1) return false;

  if (strncmp(aStyleSheetData, kCharsetSym, sizeof(kCharsetSym) - 1)) {
    return false;
  }

  for (uint32_t i = sizeof(kCharsetSym) - 1; i < aDataLength; ++i) {
    char c = aStyleSheetData[i];
    if (c == '"') {
      ++i;
      if (i < aDataLength && aStyleSheetData[i] == ';') {
        return true;
      }
      // fail
      break;
    }
    aCharset.Append(c);
  }

  // Did not see end quote or semicolon
  aCharset.Truncate();
  return false;
}

NotNull<const Encoding*> SheetLoadData::DetermineNonBOMEncoding(
    nsACString const& aSegment, nsIChannel* aChannel) {
  const Encoding* encoding;
  nsAutoCString label;

  // Check HTTP
  if (aChannel && NS_SUCCEEDED(aChannel->GetContentCharset(label))) {
    encoding = Encoding::ForLabel(label);
    if (encoding) {
      return WrapNotNull(encoding);
    }
  }

  // Check @charset
  auto sniffingLength = aSegment.Length();
  if (sniffingLength > SNIFFING_BUFFER_SIZE) {
    sniffingLength = SNIFFING_BUFFER_SIZE;
  }
  if (GetCharsetFromData(aSegment.BeginReading(), sniffingLength, label)) {
    encoding = Encoding::ForLabel(label);
    if (encoding == UTF_16BE_ENCODING || encoding == UTF_16LE_ENCODING) {
      return UTF_8_ENCODING;
    }
    if (encoding) {
      return WrapNotNull(encoding);
    }
  }

  // Now try the charset on the <link> or processing instruction
  // that loaded us
  if (mOwningElement) {
    nsAutoString label16;
    mOwningElement->GetCharset(label16);
    encoding = Encoding::ForLabel(label16);
    if (encoding) {
      return WrapNotNull(encoding);
    }
  }

  // In the preload case, the value of the charset attribute on <link> comes
  // in via mPreloadEncoding instead.
  if (mPreloadEncoding) {
    return WrapNotNull(mPreloadEncoding);
  }

  // Try charset from the parent stylesheet.
  if (mParentData) {
    encoding = mParentData->mEncoding;
    if (encoding) {
      return WrapNotNull(encoding);
    }
  }

  if (mLoader->mDocument) {
    // Use the document charset.
    return mLoader->mDocument->GetDocumentCharacterSet();
  }

  return UTF_8_ENCODING;
}

static nsresult VerifySheetIntegrity(const SRIMetadata& aMetadata,
                                     nsIChannel* aChannel,
                                     const nsACString& aFirst,
                                     const nsACString& aSecond,
                                     const nsACString& aSourceFileURI,
                                     nsIConsoleReportCollector* aReporter) {
  NS_ENSURE_ARG_POINTER(aReporter);

  if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), LogLevel::Debug)) {
    nsAutoCString requestURL;
    nsCOMPtr<nsIURI> originalURI;
    if (aChannel &&
        NS_SUCCEEDED(aChannel->GetOriginalURI(getter_AddRefs(originalURI))) &&
        originalURI) {
      originalURI->GetAsciiSpec(requestURL);
    }
    MOZ_LOG(SRILogHelper::GetSriLog(), LogLevel::Debug,
            ("VerifySheetIntegrity (unichar stream)"));
  }

  SRICheckDataVerifier verifier(aMetadata, aSourceFileURI, aReporter);
  nsresult rv =
      verifier.Update(aFirst.Length(), (const uint8_t*)aFirst.BeginReading());
  NS_ENSURE_SUCCESS(rv, rv);
  rv =
      verifier.Update(aSecond.Length(), (const uint8_t*)aSecond.BeginReading());
  NS_ENSURE_SUCCESS(rv, rv);

  return verifier.Verify(aMetadata, aChannel, aSourceFileURI, aReporter);
}

/*
 * Stream completion code shared by Stylo and the old style system.
 *
 * Here we need to check that the load did not give us an http error
 * page and check the mimetype on the channel to make sure we're not
 * loading non-text/css data in standards mode.
 */
nsresult SheetLoadData::VerifySheetReadyToParse(nsresult aStatus,
                                                const nsACString& aBytes1,
                                                const nsACString& aBytes2,
                                                nsIChannel* aChannel) {
  LOG(("SheetLoadData::VerifySheetReadyToParse"));
  NS_ASSERTION(!mLoader->mSyncCallback, "Synchronous callback from necko");

  if (mIsCancelled) {
    // Just return.  Don't call SheetComplete -- it's already been
    // called and calling it again will lead to an extra NS_RELEASE on
    // this data and a likely crash.
    return NS_OK;
  }

  if (!mLoader->mDocument && !mIsNonDocumentSheet) {
    // Sorry, we don't care about this load anymore
    LOG_WARN(("  No document and not non-document sheet; dropping load"));
    mLoader->SheetComplete(*this, NS_BINDING_ABORTED);
    return NS_OK;
  }

  if (NS_FAILED(aStatus)) {
    LOG_WARN(
        ("  Load failed: status 0x%" PRIx32, static_cast<uint32_t>(aStatus)));
    // Handle sheet not loading error because source was a tracking URL (or
    // fingerprinting, cryptomining, etc).
    // We make a note of this sheet node by including it in a dedicated
    // array of blocked tracking nodes under its parent document.
    //
    // Multiple sheet load instances might be tied to this request,
    // we annotate each one linked to a valid owning element (node).
    if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
            aStatus)) {
      if (Document* doc = mLoader->GetDocument()) {
        for (SheetLoadData* data = this; data; data = data->mNext) {
          // mOwningElement may be null but AddBlockTrackingNode can cope
          nsCOMPtr<nsIContent> content =
              do_QueryInterface(data->mOwningElement);
          doc->AddBlockedNodeByClassifier(content);
        }
      }
    }
    mLoader->SheetComplete(*this, aStatus);
    return NS_OK;
  }

  if (!aChannel) {
    mLoader->SheetComplete(*this, NS_OK);
    return NS_OK;
  }

  nsCOMPtr<nsIURI> originalURI;
  aChannel->GetOriginalURI(getter_AddRefs(originalURI));

  // If the channel's original URI is "chrome:", we want that, since
  // the observer code in nsXULPrototypeCache depends on chrome stylesheets
  // having a chrome URI.  (Whether or not chrome stylesheets come through
  // this codepath seems nondeterministic.)
  // Otherwise we want the potentially-HTTP-redirected URI.
  nsCOMPtr<nsIURI> channelURI;
  NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));

  if (!channelURI || !originalURI) {
    NS_ERROR("Someone just violated the nsIRequest contract");
    LOG_WARN(("  Channel without a URI.  Bad!"));
    mLoader->SheetComplete(*this, NS_ERROR_UNEXPECTED);
    return NS_OK;
  }

  nsCOMPtr<nsIPrincipal> principal;
  nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
  nsresult result = NS_ERROR_NOT_AVAILABLE;
  if (secMan) {  // Could be null if we already shut down
    if (mUseSystemPrincipal) {
      result = secMan->GetSystemPrincipal(getter_AddRefs(principal));
    } else {
      result = secMan->GetChannelResultPrincipal(aChannel,
                                                 getter_AddRefs(principal));
    }
  }

  if (NS_FAILED(result)) {
    LOG_WARN(("  Couldn't get principal"));
    mLoader->SheetComplete(*this, result);
    return NS_OK;
  }

  mSheet->SetPrincipal(principal);

  if (mLoaderPrincipal && mSheet->GetCORSMode() == CORS_NONE) {
    bool subsumed;
    result = mLoaderPrincipal->Subsumes(principal, &subsumed);
    if (NS_FAILED(result) || !subsumed) {
      mIsCrossOriginNoCORS = true;
    }
  }

  // If it's an HTTP channel, we want to make sure this is not an
  // error document we got.
  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
  if (httpChannel) {
    bool requestSucceeded;
    result = httpChannel->GetRequestSucceeded(&requestSucceeded);
    if (NS_SUCCEEDED(result) && !requestSucceeded) {
      LOG(("  Load returned an error page"));
      mLoader->SheetComplete(*this, NS_ERROR_NOT_AVAILABLE);
      return NS_OK;
    }

    nsAutoCString sourceMapURL;
    if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) {
      mSheet->SetSourceMapURL(NS_ConvertUTF8toUTF16(sourceMapURL));
    }
  }

  nsAutoCString contentType;
  aChannel->GetContentType(contentType);

  // In standards mode, a style sheet must have one of these MIME
  // types to be processed at all.  In quirks mode, we accept any
  // MIME type, but only if the style sheet is same-origin with the
  // requesting document or parent sheet.  See bug 524223.

  bool validType = contentType.EqualsLiteral("text/css") ||
                   contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE) ||
                   contentType.IsEmpty();

  if (!validType) {
    const char* errorMessage;
    uint32_t errorFlag;
    bool sameOrigin = true;

    if (mLoaderPrincipal) {
      bool subsumed;
      result = mLoaderPrincipal->Subsumes(principal, &subsumed);
      if (NS_FAILED(result) || !subsumed) {
        sameOrigin = false;
      }
    }

    if (sameOrigin && mLoader->mCompatMode == eCompatibility_NavQuirks) {
      errorMessage = "MimeNotCssWarn";
      errorFlag = nsIScriptError::warningFlag;
    } else {
      errorMessage = "MimeNotCss";
      errorFlag = nsIScriptError::errorFlag;
    }

    AutoTArray<nsString, 2> strings;
    CopyUTF8toUTF16(channelURI->GetSpecOrDefault(), *strings.AppendElement());
    CopyASCIItoUTF16(contentType, *strings.AppendElement());

    nsCOMPtr<nsIURI> referrer = ReferrerInfo()->GetOriginalReferrer();
    nsContentUtils::ReportToConsole(
        errorFlag, NS_LITERAL_CSTRING("CSS Loader"), mLoader->mDocument,
        nsContentUtils::eCSS_PROPERTIES, errorMessage, strings, referrer);

    if (errorFlag == nsIScriptError::errorFlag) {
      LOG_WARN(
          ("  Ignoring sheet with improper MIME type %s", contentType.get()));
      mLoader->SheetComplete(*this, NS_ERROR_NOT_AVAILABLE);
      return NS_OK;
    }
  }

  SRIMetadata sriMetadata;
  mSheet->GetIntegrity(sriMetadata);
  if (!sriMetadata.IsEmpty()) {
    nsAutoCString sourceUri;
    if (mLoader->mDocument && mLoader->mDocument->GetDocumentURI()) {
      mLoader->mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
    }
    nsresult rv = VerifySheetIntegrity(sriMetadata, aChannel, aBytes1, aBytes2,
                                       sourceUri, mLoader->mReporter);

    nsCOMPtr<nsILoadGroup> loadGroup;
    aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
    if (loadGroup) {
      mLoader->mReporter->FlushConsoleReports(loadGroup);
    } else {
      mLoader->mReporter->FlushConsoleReports(mLoader->mDocument);
    }

    if (NS_FAILED(rv)) {
      LOG(("  Load was blocked by SRI"));
      MOZ_LOG(gSriPRLog, LogLevel::Debug,
              ("css::Loader::OnStreamComplete, bad metadata"));
      mLoader->SheetComplete(*this, NS_ERROR_SRI_CORRUPT);
      return NS_OK;
    }
  }

  // Enough to set the URIs on mSheet, since any sibling datas we have share
  // the same mInner as mSheet and will thus get the same URI.
  mSheet->SetURIs(channelURI, originalURI, channelURI);

  ReferrerPolicy policy =
      nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
  nsCOMPtr<nsIReferrerInfo> referrerInfo =
      ReferrerInfo::CreateForExternalCSSResources(mSheet, policy);

  mSheet->SetReferrerInfo(referrerInfo);
  return NS_OK_PARSE_SHEET;
}

Loader::IsAlternate Loader::IsAlternateSheet(const nsAString& aTitle,
                                             bool aHasAlternateRel) {
  // A sheet is alternate if it has a nonempty title that doesn't match the
  // currently selected style set.  But if there _is_ no currently selected
  // style set, the sheet wasn't marked as an alternate explicitly, and aTitle
  // is nonempty, we should select the style set corresponding to aTitle, since
  // that's a preferred sheet.
  if (aTitle.IsEmpty()) {
    return IsAlternate::No;
  }

  if (mDocument) {
    const nsString& currentSheetSet = mDocument->GetCurrentStyleSheetSet();
    if (!aHasAlternateRel && currentSheetSet.IsEmpty()) {
      // There's no preferred set yet, and we now have a sheet with a title.
      // Make that be the preferred set.
      // FIXME(emilio): This is kinda wild, can we do it somewhere else?
      mDocument->SetPreferredStyleSheetSet(aTitle);
      // We're definitely not an alternate. Also, beware that at this point
      // currentSheetSet may dangle.
      return IsAlternate::No;
    }

    if (aTitle.Equals(currentSheetSet)) {
      return IsAlternate::No;
    }
  }

  return IsAlternate::Yes;
}

nsresult Loader::CheckContentPolicy(nsIPrincipal* aLoadingPrincipal,
                                    nsIPrincipal* aTriggeringPrincipal,
                                    nsIURI* aTargetURI,
                                    nsINode* aRequestingNode,
                                    IsPreload aIsPreload) {
  // When performing a system load (e.g. aUseSystemPrincipal = true)
  // then aLoadingPrincipal == null; don't consult content policies.
  if (!aLoadingPrincipal) {
    return NS_OK;
  }

  nsContentPolicyType contentPolicyType =
      aIsPreload == IsPreload::No
          ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
          : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;

  nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
      aLoadingPrincipal, aTriggeringPrincipal, aRequestingNode,
      nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, contentPolicyType);

  // snapshot the nonce at load start time for performing CSP checks
  if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET) {
    nsCOMPtr<Element> element = do_QueryInterface(aRequestingNode);
    if (element && element->IsHTMLElement()) {
      nsAutoString cspNonce;
      element->GetAttribute(NS_LITERAL_STRING("nonce"), cspNonce);
      secCheckLoadInfo->SetCspNonce(cspNonce);
    }
  }

  int16_t shouldLoad = nsIContentPolicy::ACCEPT;
  nsresult rv = NS_CheckContentLoadPolicy(
      aTargetURI, secCheckLoadInfo, NS_LITERAL_CSTRING("text/css"), &shouldLoad,
      nsContentUtils::GetContentPolicy());
  if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
    return NS_ERROR_CONTENT_BLOCKED;
  }
  return NS_OK;
}

/**
 * CreateSheet() creates a StyleSheet object for the given URI.
 *
 * We check for an existing style sheet object for that uri in various caches
 * and clone it if we find it.  Cloned sheets will have the title/media/enabled
 * state of the sheet they are clones off; make sure to call PrepareSheet() on
 * the result of CreateSheet().
 */
Tuple<RefPtr<StyleSheet>, Loader::SheetState> Loader::CreateSheet(
    nsIURI* aURI, nsIContent* aLinkingContent, nsIPrincipal* aLoaderPrincipal,
    css::SheetParsingMode aParsingMode, CORSMode aCORSMode,
    nsIReferrerInfo* aLoadingReferrerInfo, const nsAString& aIntegrity,
    bool aSyncLoad) {
  MOZ_ASSERT(aURI, "This path is not taken for inline stylesheets");
  LOG(("css::Loader::CreateSheet(%s)", aURI->GetSpecOrDefault().get()));

  if (!mSheets) {
    mSheets = MakeUnique<Sheets>();
  }

  SheetLoadDataHashKey key(aURI, aLoaderPrincipal, aLoadingReferrerInfo,
                           aCORSMode, aParsingMode);
  auto cacheResult = mSheets->Lookup(key, aSyncLoad);
  if (Get<0>(cacheResult)) {
    LOG(("  Hit cache with state: %s",
         gStateStrings[size_t(Get<1>(cacheResult))]));
    return cacheResult;
  }

  nsIURI* sheetURI = aURI;
  nsIURI* baseURI = aURI;
  nsIURI* originalURI = aURI;

  SRIMetadata sriMetadata;
  if (!aIntegrity.IsEmpty()) {
    MOZ_LOG(gSriPRLog, LogLevel::Debug,
            ("css::Loader::CreateSheet, integrity=%s",
             NS_ConvertUTF16toUTF8(aIntegrity).get()));
    nsAutoCString sourceUri;
    if (mDocument && mDocument->GetDocumentURI()) {
      mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
    }
    SRICheck::IntegrityMetadata(aIntegrity, sourceUri, mReporter, &sriMetadata);
  }

  auto sheet = MakeRefPtr<StyleSheet>(aParsingMode, aCORSMode, sriMetadata);
  sheet->SetURIs(sheetURI, originalURI, baseURI);
  nsCOMPtr<nsIReferrerInfo> referrerInfo =
      ReferrerInfo::CreateForExternalCSSResources(sheet);
  sheet->SetReferrerInfo(referrerInfo);
  LOG(("  Needs parser"));
  return MakeTuple(std::move(sheet), SheetState::NeedsParser);
}

static Loader::MediaMatched MediaListMatches(const MediaList* aMediaList,
                                             const Document* aDocument) {
  if (!aMediaList || !aDocument) {
    return Loader::MediaMatched::Yes;
  }

  if (aMediaList->Matches(*aDocument)) {
    return Loader::MediaMatched::Yes;
  }

  return Loader::MediaMatched::No;
}

/**
 * PrepareSheet() handles setting the media and title on the sheet, as
 * well as setting the enabled state based on the title and whether
 * the sheet had "alternate" in its rel.
 */
Loader::MediaMatched Loader::PrepareSheet(
    StyleSheet& aSheet, const nsAString& aTitle, const nsAString& aMediaString,
    MediaList* aMediaList, IsAlternate aIsAlternate,
    IsExplicitlyEnabled aIsExplicitlyEnabled) {
  RefPtr<MediaList> mediaList(aMediaList);

  if (!aMediaString.IsEmpty()) {
    NS_ASSERTION(!aMediaList,
                 "must not provide both aMediaString and aMediaList");
    mediaList = MediaList::Create(aMediaString);
  }

  aSheet.SetMedia(mediaList);

  aSheet.SetTitle(aTitle);
  aSheet.SetEnabled(aIsAlternate == IsAlternate::No ||
                    aIsExplicitlyEnabled == IsExplicitlyEnabled::Yes);
  return MediaListMatches(mediaList, mDocument);
}

/**
 * InsertSheetInTree handles ordering of sheets in the document or shadow root.
 *
 * Here we have two types of sheets -- those with linking elements and
 * those without.  The latter are loaded by Link: headers, and are only added to
 * the document.
 *
 * The following constraints are observed:
 * 1) Any sheet with a linking element comes after all sheets without
 *    linking elements
 * 2) Sheets without linking elements are inserted in the order in
 *    which the inserting requests come in, since all of these are
 *    inserted during header data processing in the content sink
 * 3) Sheets with linking elements are ordered based on document order
 *    as determined by CompareDocumentPosition.
 */
void Loader::InsertSheetInTree(StyleSheet& aSheet,
                               nsIContent* aLinkingContent) {
  LOG(("css::Loader::InsertSheetInTree"));
  MOZ_ASSERT(mDocument, "Must have a document to insert into");
  MOZ_ASSERT(!aLinkingContent || aLinkingContent->IsInUncomposedDoc() ||
                 aLinkingContent->IsInShadowTree(),
             "Why would we insert it anywhere?");

  nsCOMPtr<nsIStyleSheetLinkingElement> linkingElement =
      do_QueryInterface(aLinkingContent);
  if (linkingElement) {
    linkingElement->SetStyleSheet(&aSheet);
  }

  ShadowRoot* shadow =
      aLinkingContent ? aLinkingContent->GetContainingShadow() : nullptr;

  auto& target = shadow ? static_cast<DocumentOrShadowRoot&>(*shadow)
                        : static_cast<DocumentOrShadowRoot&>(*mDocument);

  // XXX Need to cancel pending sheet loads for this element, if any

  int32_t sheetCount = target.SheetCount();

  /*
   * Start the walk at the _end_ of the list, since in the typical
   * case we'll just want to append anyway.  We want to break out of
   * the loop when insertionPoint points to just before the index we
   * want to insert at.  In other words, when we leave the loop
   * insertionPoint is the index of the stylesheet that immediately
   * precedes the one we're inserting.
   */
  int32_t insertionPoint = sheetCount - 1;
  for (; insertionPoint >= 0; --insertionPoint) {
    nsINode* sheetOwner = target.SheetAt(insertionPoint)->GetOwnerNode();
    if (sheetOwner && !aLinkingContent) {
      // Keep moving; all sheets with a sheetOwner come after all
      // sheets without a linkingNode
      continue;
    }

    if (!sheetOwner) {
      // Aha!  The current sheet has no sheet owner, so we want to insert after
      // it no matter whether we have a linking content or not.
      break;
    }

    MOZ_ASSERT(aLinkingContent != sheetOwner,
               "Why do we still have our old sheet?");

    // Have to compare
    if (nsContentUtils::PositionIsBefore(sheetOwner, aLinkingContent)) {
      // The current sheet comes before us, and it better be the first
      // such, because now we break
      break;
    }
  }

  ++insertionPoint;

  if (shadow) {
    shadow->InsertSheetAt(insertionPoint, aSheet);
  } else {
    mDocument->InsertSheetAt(insertionPoint, aSheet);
  }

  LOG(("  Inserting into target (doc: %d) at position %d",
       target.AsNode().IsDocument(), insertionPoint));
}

/**
 * InsertChildSheet handles ordering of @import-ed sheet in their
 * parent sheets.  Here we want to just insert based on order of the
 * @import rules that imported the sheets.  In theory we can't just
 * append to the end because the CSSOM can insert @import rules.  In
 * practice, we get the call to load the child sheet before the CSSOM
 * has finished inserting the @import rule, so we have no idea where
 * to put it anyway.  So just append for now.  (In the future if we
 * want to insert the sheet at the correct position, we'll need to
 * restore CSSStyleSheet::InsertStyleSheetAt, which was removed in
 * bug 1220506.)
 */
void Loader::InsertChildSheet(StyleSheet& aSheet, StyleSheet& aParentSheet) {
  LOG(("css::Loader::InsertChildSheet"));

  // child sheets should always start out enabled, even if they got
  // cloned off of top-level sheets which were disabled
  aSheet.SetEnabled(true);
  aParentSheet.AppendStyleSheet(aSheet);

  LOG(("  Inserting into parent sheet"));
}

/**
 * LoadSheet handles the actual load of a sheet.  If the load is
 * supposed to be synchronous it just opens a channel synchronously
 * using the given uri, wraps the resulting stream in a converter
 * stream and calls ParseSheet.  Otherwise it tries to look for an
 * existing load for this URI and piggyback on it.  Failing all that,
 * a new load is kicked off asynchronously.
 */
nsresult Loader::LoadSheet(SheetLoadData& aLoadData, SheetState aSheetState,
                           IsPreload aIsPreload) {
  LOG(("css::Loader::LoadSheet"));
  MOZ_ASSERT(aLoadData.mURI, "Need a URI to load");
  MOZ_ASSERT(aLoadData.mSheet, "Need a sheet to load into");
  MOZ_ASSERT(aSheetState != SheetState::Complete, "Why bother?");
  MOZ_ASSERT(!aLoadData.mUseSystemPrincipal || aLoadData.mSyncLoad,
             "Shouldn't use system principal for async loads");
  NS_ASSERTION(mSheets, "mLoadingDatas should be initialized by now.");

  LOG_URI("  Load from: '%s'", aLoadData.mURI);

  nsresult rv = NS_OK;

  if (!mDocument && !aLoadData.mIsNonDocumentSheet) {
    // No point starting the load; just release all the data and such.
    LOG_WARN(("  No document and not non-document sheet; pre-dropping load"));
    SheetComplete(aLoadData, NS_BINDING_ABORTED);
    return NS_BINDING_ABORTED;
  }

  SRIMetadata sriMetadata;
  aLoadData.mSheet->GetIntegrity(sriMetadata);

  if (aLoadData.mSyncLoad) {
    LOG(("  Synchronous load"));
    MOZ_ASSERT(!aLoadData.mObserver, "Observer for a sync load?");
    MOZ_ASSERT(aSheetState == SheetState::NeedsParser,
               "Sync loads can't reuse existing async loads");

    // Create a StreamLoader instance to which we will feed
    // the data from the sync load.  Do this before creating the
    // channel to make error recovery simpler.
    nsCOMPtr<nsIStreamListener> streamLoader = new StreamLoader(aLoadData);

    if (mDocument) {
      net::PredictorLearn(aLoadData.mURI, mDocument->GetDocumentURI(),
                          nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
                          mDocument);
    }

    nsSecurityFlags securityFlags =
        nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
        nsILoadInfo::SEC_ALLOW_CHROME;

    nsContentPolicyType contentPolicyType =
        aIsPreload == IsPreload::No
            ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
            : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;

    // Just load it
    nsCOMPtr<nsIChannel> channel;
    // Note that we are calling NS_NewChannelWithTriggeringPrincipal() with both
    // a node and a principal.
    // This is because of a case where the node is the document being styled and
    // the principal is the stylesheet (perhaps from a different origin) that is
    // applying the styles.
    if (aLoadData.mRequestingNode && aLoadData.mLoaderPrincipal) {
      rv = NS_NewChannelWithTriggeringPrincipal(
          getter_AddRefs(channel), aLoadData.mURI, aLoadData.mRequestingNode,
          aLoadData.mLoaderPrincipal, securityFlags, contentPolicyType);
    } else {
      // either we are loading something inside a document, in which case
      // we should always have a requestingNode, or we are loading something
      // outside a document, in which case the loadingPrincipal and the
      // triggeringPrincipal should always be the systemPrincipal.
      auto result = URLPreloader::ReadURI(aLoadData.mURI);
      if (result.isOk()) {
        nsCOMPtr<nsIInputStream> stream;
        MOZ_TRY(
            NS_NewCStringInputStream(getter_AddRefs(stream), result.unwrap()));

        rv = NS_NewInputStreamChannel(getter_AddRefs(channel), aLoadData.mURI,
                                      stream.forget(),
                                      nsContentUtils::GetSystemPrincipal(),
                                      securityFlags, contentPolicyType);
      } else {
        rv = NS_NewChannel(getter_AddRefs(channel), aLoadData.mURI,
                           nsContentUtils::GetSystemPrincipal(), securityFlags,
                           contentPolicyType);
      }
    }
    if (NS_FAILED(rv)) {
      LOG_ERROR(("  Failed to create channel"));
      SheetComplete(aLoadData, rv);
      return rv;
    }

    // snapshot the nonce at load start time for performing CSP checks
    if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET) {
      nsCOMPtr<Element> element = do_QueryInterface(aLoadData.mRequestingNode);
      if (element && element->IsHTMLElement()) {
        nsAutoString cspNonce;
        element->GetAttribute(NS_LITERAL_STRING("nonce"), cspNonce);
        nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
        loadInfo->SetCspNonce(cspNonce);
      }
    }

    nsCOMPtr<nsIInputStream> stream;
    rv = channel->Open(getter_AddRefs(stream));

    if (NS_FAILED(rv)) {
      LOG_ERROR(("  Failed to open URI synchronously"));
      SheetComplete(aLoadData, rv);
      return rv;
    }

    // Force UA sheets to be UTF-8.
    // XXX this is only necessary because the default in
    // SheetLoadData::OnDetermineCharset is wrong (bug 521039).
    channel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8"));

    // Manually feed the streamloader the contents of the stream.
    // This will call back into OnStreamComplete
    // and thence to ParseSheet.  Regardless of whether this fails,
    // SheetComplete has been called.
    return nsSyncLoadService::PushSyncStreamToListener(stream.forget(),
                                                       streamLoader, channel);
  }

  SheetLoadData* existingData = nullptr;

  SheetLoadDataHashKey key(aLoadData);

  if (aSheetState == SheetState::Loading) {
    existingData = mSheets->mLoadingDatas.Get(&key);
    NS_ASSERTION(existingData, "CreateSheet lied about the state");
  } else if (aSheetState == SheetState::Pending) {
    existingData = mSheets->mPendingDatas.GetWeak(&key);
    NS_ASSERTION(existingData, "CreateSheet lied about the state");
  }

  if (existingData) {
    LOG(("  Glomming on to existing load"));
    SheetLoadData* data = existingData;
    while (data->mNext) {
      data = data->mNext;
    }
    data->mNext = &aLoadData;
    if (aSheetState == SheetState::Pending && !aLoadData.ShouldDefer()) {
      // Kick the load off; someone cares about it right away
      RefPtr<SheetLoadData> removedData;
      mSheets->mPendingDatas.Remove(&key, getter_AddRefs(removedData));
      MOZ_ASSERT(removedData == existingData, "Bad loading table");

      LOG(("  Forcing load of pending data"));
      return LoadSheet(*removedData, SheetState::NeedsParser, aIsPreload);
    }
    // All done here; once the load completes we'll be marked complete
    // automatically
    return NS_OK;
  }

  nsCOMPtr<nsILoadGroup> loadGroup;
  nsCOMPtr<nsICookieSettings> cookieSettings;
  if (mDocument) {
    loadGroup = mDocument->GetDocumentLoadGroup();
    // load for a document with no loadgrup indicates that something is
    // completely bogus, let's bail out early.
    if (!loadGroup) {
      LOG_ERROR(("  Failed to query loadGroup from document"));
      SheetComplete(aLoadData, NS_ERROR_UNEXPECTED);
      return NS_ERROR_UNEXPECTED;
    }

    cookieSettings = mDocument->CookieSettings();
  }

#ifdef DEBUG
  AutoRestore<bool> syncCallbackGuard(mSyncCallback);
  mSyncCallback = true;
#endif

  CORSMode ourCORSMode = aLoadData.mSheet->GetCORSMode();
  nsSecurityFlags securityFlags =
      ourCORSMode == CORS_NONE
          ? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS
          : nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
  if (ourCORSMode == CORS_ANONYMOUS) {
    securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
  } else if (ourCORSMode == CORS_USE_CREDENTIALS) {
    securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
  }
  securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;

  nsContentPolicyType contentPolicyType =
      aIsPreload == IsPreload::No
          ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
          : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;

  nsCOMPtr<nsIChannel> channel;
  // Note we are calling NS_NewChannelWithTriggeringPrincipal here with a node
  // and a principal. This is because of a case where the node is the document
  // being styled and the principal is the stylesheet (perhaps from a different
  // origin)  that is applying the styles.
  if (aLoadData.mRequestingNode && aLoadData.mLoaderPrincipal) {
    rv = NS_NewChannelWithTriggeringPrincipal(
        getter_AddRefs(channel), aLoadData.mURI, aLoadData.mRequestingNode,
        aLoadData.mLoaderPrincipal, securityFlags, contentPolicyType,
        /* PerformanceStorage */ nullptr, loadGroup);
  } else {
    // either we are loading something inside a document, in which case
    // we should always have a requestingNode, or we are loading something
    // outside a document, in which case the loadingPrincipal and the
    // triggeringPrincipal should always be the systemPrincipal.
    rv = NS_NewChannel(getter_AddRefs(channel), aLoadData.mURI,
                       nsContentUtils::GetSystemPrincipal(), securityFlags,
                       contentPolicyType, cookieSettings,
                       /* aPerformanceStorage */ nullptr, loadGroup);
  }

  if (NS_FAILED(rv)) {
    LOG_ERROR(("  Failed to create channel"));
    SheetComplete(aLoadData, rv);
    return rv;
  }

  // snapshot the nonce at load start time for performing CSP checks
  if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET) {
    nsCOMPtr<Element> element = do_QueryInterface(aLoadData.mRequestingNode);
    if (element && element->IsHTMLElement()) {
      nsAutoString cspNonce;
      element->GetAttribute(NS_LITERAL_STRING("nonce"), cspNonce);
      nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
      loadInfo->SetCspNonce(cspNonce);
    }
  }

  if (!aLoadData.ShouldDefer()) {
    if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(channel)) {
      cos->AddClassFlags(nsIClassOfService::Leader);
    }
    if (aIsPreload == IsPreload::FromLink) {
      if (nsCOMPtr<nsISupportsPriority> sp = do_QueryInterface(channel)) {
        sp->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST);
      }
    }
  }

  if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel)) {
    if (nsCOMPtr<nsIReferrerInfo> referrerInfo = aLoadData.ReferrerInfo()) {
      rv = httpChannel->SetReferrerInfo(referrerInfo);
      Unused << NS_WARN_IF(NS_FAILED(rv));
    }

    nsCOMPtr<nsIHttpChannelInternal> internalChannel =
        do_QueryInterface(httpChannel);
    if (internalChannel) {
      rv = internalChannel->SetIntegrityMetadata(
          sriMetadata.GetIntegrityString());
      NS_ENSURE_SUCCESS(rv, rv);
    }

    // Set the initiator type
    if (nsCOMPtr<nsITimedChannel> timedChannel =
            do_QueryInterface(httpChannel)) {
      if (aLoadData.mParentData) {
        timedChannel->SetInitiatorType(NS_LITERAL_STRING("css"));

        // This is a child sheet load.
        //
        // The resource timing of the sub-resources that a document loads
        // should normally be reported to the document.  One exception is any
        // sub-resources of any cross-origin resources that are loaded.  We
        // don't mind reporting timing data for a direct child cross-origin
        // resource since the resource that linked to it (and hence potentially
        // anything in that parent origin) is aware that the cross-origin
        // resources is to be loaded.  However, we do not want to report
        // timings for any sub-resources that a cross-origin resource may load
        // since that obviously leaks information about what the cross-origin
        // resource loads, which is bad.
        //
        // In addition to checking whether we're an immediate child resource of
        // a cross-origin resource (by checking if mIsCrossOriginNoCORS is set
        // to true on our parent), we also check our parent to see whether it
        // itself is a sub-resource of a cross-origin resource by checking
        // mBlockResourceTiming.  If that is set then we too are such a
        // sub-resource and so we set the flag on ourself too to propagate it
        // on down.
        //
        if (aLoadData.mParentData->mIsCrossOriginNoCORS ||
            aLoadData.mParentData->mBlockResourceTiming) {
          // Set a flag so any other stylesheet triggered by this one will
          // not be reported
          aLoadData.mBlockResourceTiming = true;

          // Mark the channel so PerformanceMainThread::AddEntry will not
          // report the resource.
          timedChannel->SetReportResourceTiming(false);
        }

      } else {
        timedChannel->SetInitiatorType(NS_LITERAL_STRING("link"));
      }
    }
  }

  // Now tell the channel we expect text/css data back....  We do
  // this before opening it, so it's only treated as a hint.
  channel->SetContentType(NS_LITERAL_CSTRING("text/css"));

  // We don't have to hold on to the stream loader.  The ownership
  // model is: Necko owns the stream loader, which owns the load data,
  // which owns us
  nsCOMPtr<nsIStreamListener> streamLoader = new StreamLoader(aLoadData);

  if (mDocument) {
    net::PredictorLearn(aLoadData.mURI, mDocument->GetDocumentURI(),
                        nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, mDocument);
  }

  rv = channel->AsyncOpen(streamLoader);

  if (NS_FAILED(rv)) {
    LOG_ERROR(("  Failed to create stream loader"));
    SheetComplete(aLoadData, rv);
    return rv;
  }

  mSheets->mLoadingDatas.Put(&key, &aLoadData);
  aLoadData.mIsLoading = true;

  return NS_OK;
}

/**
 * ParseSheet handles parsing the data stream.
 */
Loader::Completed Loader::ParseSheet(const nsACString& aBytes,
                                     SheetLoadData& aLoadData,
                                     AllowAsyncParse aAllowAsync) {
  LOG(("css::Loader::ParseSheet"));
  AUTO_PROFILER_LABEL("css::Loader::ParseSheet", LAYOUT_CSSParsing);
  aLoadData.mIsBeingParsed = true;

  // Tell the record/replay system about any sheets that are being parsed,
  // so that devtools code can find them later.
  if (recordreplay::IsRecordingOrReplaying() && aLoadData.mURI) {
    recordreplay::NoteContentParse(
        &aLoadData, aLoadData.mURI->GetSpecOrDefault().get(), "text/css",
        reinterpret_cast<const Utf8Unit*>(aBytes.BeginReading()),
        aBytes.Length());
  }

  StyleSheet* sheet = aLoadData.mSheet;
  MOZ_ASSERT(sheet);

  // Some cases, like inline style and UA stylesheets, need to be parsed
  // synchronously. The former may trigger child loads, the latter must not.
  if (aLoadData.mSyncLoad || aAllowAsync == AllowAsyncParse::No) {
    sheet->ParseSheetSync(this, aBytes, &aLoadData, aLoadData.mLineNumber);
    aLoadData.mIsBeingParsed = false;

    bool noPendingChildren = aLoadData.mPendingChildren == 0;
    MOZ_ASSERT_IF(aLoadData.mSyncLoad, noPendingChildren);
    if (noPendingChildren) {
      SheetComplete(aLoadData, NS_OK);
      return Completed::Yes;
    }
    return Completed::No;
  }

  // This parse does not need to be synchronous. \o/
  //
  // Note that we need to block onload because there may be no network requests
  // pending.
  BlockOnload();
  nsCOMPtr<nsISerialEventTarget> target = DispatchTarget();
  sheet->ParseSheet(*this, aBytes, aLoadData)
      ->Then(
          target, __func__,
          [loadData = RefPtr<SheetLoadData>(&aLoadData)](bool aDummy) {
            MOZ_ASSERT(NS_IsMainThread());
            loadData->mIsBeingParsed = false;
            loadData->mLoader->UnblockOnload(/* aFireSync = */ false);
            // If there are no child sheets outstanding, mark us as complete.
            // Otherwise, the children are holding strong refs to the data
            // and will call SheetComplete() on it when they complete.
            if (loadData->mPendingChildren == 0) {
              loadData->mLoader->SheetComplete(*loadData, NS_OK);
            }
          },
          [] { MOZ_CRASH("rejected parse promise"); });
  return Completed::No;
}

/**
 * SheetComplete is the do-it-all cleanup function.  It removes the
 * load data from the "loading" hashtable, adds the sheet to the
 * "completed" hashtable, massages the XUL cache, handles siblings of
 * the load data (other loads for the same URI), handles unblocking
 * blocked parent loads as needed, and most importantly calls
 * NS_RELEASE on the load data to destroy the whole mess.
 */
void Loader::SheetComplete(SheetLoadData& aLoadData, nsresult aStatus) {
  LOG(("css::Loader::SheetComplete, status: 0x%" PRIx32,
       static_cast<uint32_t>(aStatus)));

  // If aStatus is a failure we need to mark this data failed.  We also need to
  // mark any ancestors of a failing data as failed and any sibling of a
  // failing data as failed.  Note that SheetComplete is never called on a
  // SheetLoadData that is the mNext of some other SheetLoadData.
  if (NS_FAILED(aStatus)) {
    MarkLoadTreeFailed(aLoadData);
  }

  if (mDocument) {
    mDocument->MaybeWarnAboutZoom();
  }

  // 8 is probably big enough for all our common cases.  It's not likely that
  // imports will nest more than 8 deep, and multiple sheets with the same URI
  // are rare.
  AutoTArray<RefPtr<SheetLoadData>, 8> datasToNotify;
  DoSheetComplete(aLoadData, datasToNotify);

  // Now it's safe to go ahead and notify observers
  uint32_t count = datasToNotify.Length();
  mDatasToNotifyOn += count;
  for (RefPtr<SheetLoadData>& data : datasToNotify) {
    --mDatasToNotifyOn;

    MOZ_ASSERT(data, "How did this data get here?");
    if (data->mObserver) {
      LOG(("  Notifying observer %p for data %p.  deferred: %d",
           data->mObserver.get(), data.get(), data->ShouldDefer()));
      data->mObserver->StyleSheetLoaded(data->mSheet, data->ShouldDefer(),
                                        aStatus);
    }

    nsTObserverArray<nsCOMPtr<nsICSSLoaderObserver>>::ForwardIterator iter(
        mObservers);
    nsCOMPtr<nsICSSLoaderObserver> obs;
    while (iter.HasMore()) {
      obs = iter.GetNext();
      LOG(("  Notifying global observer %p for data %p.  deferred: %d",
           obs.get(), data.get(), data->ShouldDefer()));
      obs->StyleSheetLoaded(data->mSheet, data->ShouldDefer(), aStatus);
    }
  }

  if (mSheets && mSheets->mLoadingDatas.Count() == 0 &&
      mSheets->mPendingDatas.Count() > 0) {
    LOG(("  No more loading sheets; starting deferred loads"));
    StartDeferredLoads();
  }
}

void Loader::DoSheetComplete(SheetLoadData& aLoadData,
                             LoadDataArray& aDatasToNotify) {
  LOG(("css::Loader::DoSheetComplete"));
  MOZ_ASSERT(aLoadData.mSheet, "Must have a sheet");
  NS_ASSERTION(mSheets || !aLoadData.mURI,
               "mLoadingDatas should be initialized by now.");

  // Twiddle the hashtables
  if (aLoadData.mURI) {
    LOG_URI("  Finished loading: '%s'", aLoadData.mURI);
    // Remove the data from the list of loading datas
    if (aLoadData.mIsLoading) {
      SheetLoadDataHashKey key(aLoadData);
#ifdef DEBUG
      SheetLoadData* loadingData;
      NS_ASSERTION(mSheets->mLoadingDatas.Get(&key, &loadingData) &&
                       loadingData == &aLoadData,
                   "Bad loading table");
#endif

      mSheets->mLoadingDatas.Remove(&key);
      aLoadData.mIsLoading = false;
    }
  }

  // Go through and deal with the whole linked list.
  SheetLoadData* data = &aLoadData;
  do {
    if (!data->mSheetAlreadyComplete) {
      // If mSheetAlreadyComplete, then the sheet could well be modified between
      // when we posted the async call to SheetComplete and now, since the sheet
      // was page-accessible during that whole time.
      MOZ_ASSERT(!data->mSheet->HasForcedUniqueInner(),
                 "should not get a forced unique inner during parsing");
      data->mSheet->SetComplete();
      data->ScheduleLoadEventIfNeeded();
    }
    if (data->mMustNotify && (data->mObserver || !mObservers.IsEmpty())) {
      // Don't notify here so we don't trigger script.  Remember the
      // info we need to notify, then do it later when it's safe.
      aDatasToNotify.AppendElement(data);

      // On append failure, just press on.  We'll fail to notify the observer,
      // but not much we can do about that....
    }

    NS_ASSERTION(!data->mParentData || data->mParentData->mPendingChildren != 0,
                 "Broken pending child count on our parent");

    // If we have a parent, our parent is no longer being parsed, and
    // we are the last pending child, then our load completion
    // completes the parent too.  Note that the parent _can_ still be
    // being parsed (eg if the child (us) failed to open the channel
    // or some such).
    if (data->mParentData && --(data->mParentData->mPendingChildren) == 0 &&
        !data->mParentData->mIsBeingParsed) {
      DoSheetComplete(*data->mParentData, aDatasToNotify);
    }

    data = data->mNext;
  } while (data);

  // Now that it's marked complete, put the sheet in our cache.
  // If we ever start doing this for failed loads, we'll need to
  // adjust the PostLoadEvent code that thinks anything already
  // complete must have loaded succesfully.
  if (!aLoadData.mLoadFailed && aLoadData.mURI) {
    // Pick our sheet to cache carefully.  Ideally, we want to cache
    // one of the sheets that will be kept alive by a document or
    // parent sheet anyway, so that if someone then accesses it via
    // CSSOM we won't have extra clones of the inner lying around.
    data = &aLoadData;
    StyleSheet* sheet = aLoadData.mSheet;
    do {
      if (data->mSheet->GetParentSheet() || data->mSheet->GetOwnerNode()) {
        sheet = data->mSheet;
        break;
      }
      data = data->mNext;
    } while (data);

#ifdef MOZ_XUL
    if (IsChromeURI(aLoadData.mURI)) {
      nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
      if (cache && cache->IsEnabled()) {
        if (!cache->GetStyleSheet(aLoadData.mURI)) {
          LOG(("  Putting sheet in XUL prototype cache"));
          NS_ASSERTION(sheet->IsComplete(),
                       "Should only be caching complete sheets");
          cache->PutStyleSheet(sheet);
        }
      }
    } else {
#endif
      SheetLoadDataHashKey key(aLoadData);
      NS_ASSERTION(sheet->IsComplete(),
                   "Should only be caching complete sheets");
      mSheets->mCompleteSheets.Put(&key, sheet);
#ifdef MOZ_XUL
    }
#endif
  }
}

void Loader::MarkLoadTreeFailed(SheetLoadData& aLoadData) {
  if (aLoadData.mURI) {
    LOG_URI("  Load failed: '%s'", aLoadData.mURI);
  }

  SheetLoadData* data = &aLoadData;
  do {
    data->mLoadFailed = true;

    if (data->mParentData) {
      MarkLoadTreeFailed(*data->mParentData);
    }

    data = data->mNext;
  } while (data);
}

Result<Loader::LoadSheetResult, nsresult> Loader::LoadInlineStyle(
    const SheetInfo& aInfo, const nsAString& aBuffer, uint32_t aLineNumber,
    nsICSSLoaderObserver* aObserver) {
  LOG(("css::Loader::LoadInlineStyle"));

  if (!mEnabled) {
    LOG_WARN(("  Not enabled"));
    return Err(NS_ERROR_NOT_AVAILABLE);
  }

  if (!mDocument) {
    return Err(NS_ERROR_NOT_INITIALIZED);
  }

  nsCOMPtr<nsIStyleSheetLinkingElement> owningElement(
      do_QueryInterface(aInfo.mContent));
  NS_ASSERTION(owningElement, "Element is not a style linking element!");

  // Since we're not planning to load a URI, no need to hand a principal to the
  // load data or to CreateSheet().

  // Check IsAlternateSheet now, since it can mutate our document.
  auto isAlternate = IsAlternateSheet(aInfo.mTitle, aInfo.mHasAlternateRel);
  LOG(("  Sheet is alternate: %d", static_cast<int>(isAlternate)));

  // Use the document's base URL so that @import in the inline sheet picks up
  // the right base.
  nsIURI* baseURI = aInfo.mContent->GetBaseURI();
  nsIURI* sheetURI = aInfo.mContent->OwnerDoc()->GetDocumentURI();
  nsIURI* originalURI = nullptr;

  MOZ_ASSERT(aInfo.mIntegrity.IsEmpty());

  // We only cache sheets if in shadow trees, since regular document sheets are
  // likely to be unique.
  const bool isWorthCaching = aInfo.mContent->IsInShadowTree();
  RefPtr<StyleSheet> sheet;
  if (isWorthCaching) {
    if (!mSheets) {
      mSheets = MakeUnique<Sheets>();
    }
    sheet = mSheets->LookupInline(aBuffer);
  }
  const bool sheetFromCache = !!sheet;
  if (!sheet) {
    sheet = MakeRefPtr<StyleSheet>(eAuthorSheetFeatures, aInfo.mCORSMode,
                                   SRIMetadata{});
    sheet->SetURIs(sheetURI, originalURI, baseURI);
    nsCOMPtr<nsIReferrerInfo> referrerInfo =
        ReferrerInfo::CreateForInternalCSSResources(aInfo.mContent->OwnerDoc());
    sheet->SetReferrerInfo(referrerInfo);

    nsIPrincipal* principal = aInfo.mContent->NodePrincipal();
    if (aInfo.mTriggeringPrincipal) {
      // The triggering principal may be an expanded principal, which is safe to
      // use for URL security checks, but not as the loader principal for a
      // stylesheet. So treat this as principal inheritance, and downgrade if
      // necessary.
      principal =
          BasePrincipal::Cast(aInfo.mTriggeringPrincipal)->PrincipalToInherit();
    }

    // We never actually load this, so just set its principal directly
    sheet->SetPrincipal(principal);
  }

  auto matched = PrepareSheet(*sheet, aInfo.mTitle, aInfo.mMedia, nullptr,
                              isAlternate, aInfo.mIsExplicitlyEnabled);

  InsertSheetInTree(*sheet, aInfo.mContent);

  Completed completed;
  if (sheetFromCache) {
    MOZ_ASSERT(sheet->IsComplete());
    completed = Completed::Yes;
  } else {
    auto data = MakeRefPtr<SheetLoadData>(
        this, aInfo.mTitle, nullptr, sheet, false, owningElement, isAlternate,
        matched, aObserver, nullptr, aInfo.mReferrerInfo, aInfo.mContent);
    data->mLineNumber = aLineNumber;
    // Parse completion releases the load data.
    //
    // Note that we need to parse synchronously, since the web expects that the
    // effects of inline stylesheets are visible immediately (aside from
    // @imports).
    NS_ConvertUTF16toUTF8 utf8(aBuffer);
    completed = ParseSheet(utf8, *data, AllowAsyncParse::No);
    if (completed == Completed::Yes) {
      // TODO(emilio): Try to cache sheets with @import rules, maybe?
      if (isWorthCaching) {
        mSheets->mInlineSheets.Put(aBuffer, sheet);
      }
    } else {
      data->mMustNotify = true;
    }
  }

  return LoadSheetResult{completed, isAlternate, matched};
}

Result<Loader::LoadSheetResult, nsresult> Loader::LoadStyleLink(
    const SheetInfo& aInfo, nsICSSLoaderObserver* aObserver) {
  MOZ_ASSERT(aInfo.mURI, "Must have URL to load");
  LOG(("css::Loader::LoadStyleLink"));
  LOG_URI("  Link uri: '%s'", aInfo.mURI);
  LOG(("  Link title: '%s'", NS_ConvertUTF16toUTF8(aInfo.mTitle).get()));
  LOG(("  Link media: '%s'", NS_ConvertUTF16toUTF8(aInfo.mMedia).get()));
  LOG(("  Link alternate rel: %d", aInfo.mHasAlternateRel));

  if (!mEnabled) {
    LOG_WARN(("  Not enabled"));
    return Err(NS_ERROR_NOT_AVAILABLE);
  }

  if (!mDocument) {
    return Err(NS_ERROR_NOT_INITIALIZED);
  }

  nsIPrincipal* loadingPrincipal = aInfo.mContent
                                       ? aInfo.mContent->NodePrincipal()
                                       : mDocument->NodePrincipal();

  nsIPrincipal* principal = aInfo.mTriggeringPrincipal
                                ? aInfo.mTriggeringPrincipal.get()
                                : loadingPrincipal;

  nsINode* context = aInfo.mContent;
  if (!context) {
    context = mDocument;
  }

  bool syncLoad = aInfo.mContent && aInfo.mContent->IsInUAWidget() &&
                  IsChromeURI(aInfo.mURI);
  LOG(("  Link sync load: '%s'", syncLoad ? "true" : "false"));
  MOZ_ASSERT_IF(syncLoad, !aObserver);

  nsresult rv = CheckContentPolicy(loadingPrincipal, principal, aInfo.mURI,
                                   context, IsPreload::No);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    // Don't fire the error event if our document is loaded as data.  We're
    // supposed to not even try to do loads in that case... Unfortunately, we
    // implement that via nsDataDocumentContentPolicy, which doesn't have a good
    // way to communicate back to us that _it_ is the thing that blocked the
    // load.
    if (aInfo.mContent && !mDocument->IsLoadedAsData()) {
      // Fire an async error event on it.
      RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
          new LoadBlockingAsyncEventDispatcher(
              aInfo.mContent, NS_LITERAL_STRING("error"), CanBubble::eNo,
              ChromeOnlyDispatch::eNo);
      loadBlockingAsyncDispatcher->PostDOMEvent();
    }
    return Err(rv);
  }

  // Check IsAlternateSheet now, since it can mutate our document and make
  // pending sheets go to the non-pending state.
  auto isAlternate = IsAlternateSheet(aInfo.mTitle, aInfo.mHasAlternateRel);

  SheetState state;
  RefPtr<StyleSheet> sheet;
  Tie(sheet, state) =
      CreateSheet(aInfo, principal, eAuthorSheetFeatures, syncLoad);

  LOG(("  Sheet is alternate: %d", static_cast<int>(isAlternate)));

  auto matched = PrepareSheet(*sheet, aInfo.mTitle, aInfo.mMedia, nullptr,
                              isAlternate, aInfo.mIsExplicitlyEnabled);

  InsertSheetInTree(*sheet, aInfo.mContent);

  nsCOMPtr<nsIStyleSheetLinkingElement> owningElement(
      do_QueryInterface(aInfo.mContent));

  if (state == SheetState::Complete) {
    LOG(("  Sheet already complete: 0x%p", sheet.get()));
    if (aObserver || !mObservers.IsEmpty() || owningElement) {
      rv = PostLoadEvent(aInfo.mURI, sheet, aObserver, isAlternate, matched,
                         aInfo.mReferrerInfo, owningElement);
      if (NS_FAILED(rv)) {
        return Err(rv);
      }
    }

    // The load hasn't been completed yet, will be done in PostLoadEvent.
    return LoadSheetResult{Completed::No, isAlternate, matched};
  }

  // Now we need to actually load it.
  auto data = MakeRefPtr<SheetLoadData>(
      this, aInfo.mTitle, aInfo.mURI, sheet, syncLoad, owningElement,
      isAlternate, matched, aObserver, principal, aInfo.mReferrerInfo, context);

  auto result = LoadSheetResult{Completed::No, isAlternate, matched};

  MOZ_ASSERT(result.ShouldBlock() == !data->ShouldDefer(),
             "These should better match!");

  // If we have to parse and it's a non-blocking non-inline sheet, defer it.
  if (!syncLoad && state == SheetState::NeedsParser &&
      mSheets->mLoadingDatas.Count() != 0 && !result.ShouldBlock()) {
    LOG(("  Deferring sheet load"));
    SheetLoadDataHashKey key(*data);
    mSheets->mPendingDatas.Put(&key, data);
    data->mMustNotify = true;
    return result;
  }

  // Load completion will free the data
  rv = LoadSheet(*data, state, IsPreload::No);
  if (NS_FAILED(rv)) {
    return Err(rv);
  }

  if (!syncLoad) {
    data->mMustNotify = true;
  }
  return result;
}

static bool HaveAncestorDataWithURI(SheetLoadData& aData, nsIURI* aURI) {
  if (!aData.mURI) {
    // Inline style; this won't have any ancestors
    MOZ_ASSERT(!aData.mParentData, "How does inline style have a parent?");
    return false;
  }

  bool equal;
  if (NS_FAILED(aData.mURI->Equals(aURI, &equal)) || equal) {
    return true;
  }

  // Datas down the mNext chain have the same URI as aData, so we
  // don't have to compare to them.  But they might have different
  // parents, and we have to check all of those.
  SheetLoadData* data = &aData;
  do {
    if (data->mParentData &&
        HaveAncestorDataWithURI(*data->mParentData, aURI)) {
      return true;
    }

    data = data->mNext;
  } while (data);

  return false;
}

nsresult Loader::LoadChildSheet(StyleSheet& aParentSheet,
                                SheetLoadData* aParentData, nsIURI* aURL,
                                dom::MediaList* aMedia,
                                LoaderReusableStyleSheets* aReusableSheets) {
  LOG(("css::Loader::LoadChildSheet"));
  MOZ_ASSERT(aURL, "Must have a URI to load");

  if (!mEnabled) {
    LOG_WARN(("  Not enabled"));
    return NS_ERROR_NOT_AVAILABLE;
  }

  LOG_URI("  Child uri: '%s'", aURL);

  nsCOMPtr<nsINode> owningNode;

  // Check for an associated document or shadow root: if none, don't bother
  // walking up the parent sheets.
  if (aParentSheet.GetAssociatedDocumentOrShadowRoot()) {
    StyleSheet* topSheet = &aParentSheet;
    while (StyleSheet* parent = topSheet->GetParentSheet()) {
      topSheet = parent;
    }
    owningNode = topSheet->GetOwnerNode();
  }

  nsINode* context = nullptr;
  nsIPrincipal* loadingPrincipal = nullptr;
  if (owningNode) {
    context = owningNode;
    loadingPrincipal = owningNode->NodePrincipal();
  } else if (mDocument) {
    context = mDocument;
    loadingPrincipal = mDocument->NodePrincipal();
  }

  nsIPrincipal* principal = aParentSheet.Principal();
  nsresult rv = CheckContentPolicy(loadingPrincipal, principal, aURL, context,
                                   IsPreload::No);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    if (aParentData) {
      MarkLoadTreeFailed(*aParentData);
    }
    return rv;
  }

  nsCOMPtr<nsICSSLoaderObserver> observer;

  if (aParentData) {
    LOG(("  Have a parent load"));
    // Check for cycles
    if (HaveAncestorDataWithURI(*aParentData, aURL)) {
      // Houston, we have a loop, blow off this child and pretend this never
      // happened
      LOG_ERROR(("  @import cycle detected, dropping load"));
      return NS_OK;
    }

    NS_ASSERTION(aParentData->mSheet == &aParentSheet,
                 "Unexpected call to LoadChildSheet");
  } else {
    LOG(("  No parent load; must be CSSOM"));
    // No parent load data, so the sheet will need to be notified when
    // we finish, if it can be, if we do the load asynchronously.
    observer = &aParentSheet;
  }

  // Now that we know it's safe to load this (passes security check and not a
  // loop) do so.
  RefPtr<StyleSheet> sheet;
  SheetState state;
  if (aReusableSheets && aReusableSheets->FindReusableStyleSheet(aURL, sheet)) {
    state = SheetState::Complete;
  } else {
    // For now, use CORS_NONE for child sheets
    Tie(sheet, state) =
        CreateSheet(aURL, nullptr, principal, aParentSheet.ParsingMode(),
                    CORS_NONE, aParentSheet.GetReferrerInfo(),
                    EmptyString(),  // integrity is only checked on main sheet
                    aParentData ? aParentData->mSyncLoad : false);
    PrepareSheet(*sheet, EmptyString(), EmptyString(), aMedia, IsAlternate::No,
                 IsExplicitlyEnabled::No);
  }

  MOZ_ASSERT(sheet);
  InsertChildSheet(*sheet, aParentSheet);

  if (state == SheetState::Complete) {
    LOG(("  Sheet already complete"));
    // We're completely done.  No need to notify, even, since the
    // @import rule addition/modification will trigger the right style
    // changes automatically.
    return NS_OK;
  }

  auto data = MakeRefPtr<SheetLoadData>(
      this, aURL, sheet, aParentData, observer, principal,
      aParentSheet.GetReferrerInfo(), context);

  bool syncLoad = data->mSyncLoad;

  // Load completion will release the data
  rv = LoadSheet(*data, state, IsPreload::No);
  NS_ENSURE_SUCCESS(rv, rv);

  if (!syncLoad) {
    data->mMustNotify = true;
  }
  return rv;
}

Result<RefPtr<StyleSheet>, nsresult> Loader::LoadSheetSync(
    nsIURI* aURL, SheetParsingMode aParsingMode,
    UseSystemPrincipal aUseSystemPrincipal) {
  LOG(("css::Loader::LoadSheetSync"));
  nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr);
  return InternalLoadNonDocumentSheet(
      aURL, IsPreload::No, aParsingMode, aUseSystemPrincipal, nullptr, nullptr,
      referrerInfo, nullptr, CORS_NONE, EmptyString());
}

Result<RefPtr<StyleSheet>, nsresult> Loader::LoadSheet(
    nsIURI* aURI, SheetParsingMode aParsingMode,
    UseSystemPrincipal aUseSystemPrincipal, nsICSSLoaderObserver* aObserver) {
  nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr);
  return InternalLoadNonDocumentSheet(
      aURI, IsPreload::No, aParsingMode, aUseSystemPrincipal, nullptr, nullptr,
      referrerInfo, aObserver, CORS_NONE, EmptyString());
}

Result<RefPtr<StyleSheet>, nsresult> Loader::LoadSheet(
    nsIURI* aURL, IsPreload aIsPreload, nsIPrincipal* aOriginPrincipal,
    const Encoding* aPreloadEncoding, nsIReferrerInfo* aReferrerInfo,
    nsICSSLoaderObserver* aObserver, CORSMode aCORSMode,
    const nsAString& aIntegrity) {
  LOG(("css::Loader::LoadSheet(aURL, aObserver) api call"));
  return InternalLoadNonDocumentSheet(aURL, aIsPreload, eAuthorSheetFeatures,
                                      UseSystemPrincipal::No, aOriginPrincipal,
                                      aPreloadEncoding, aReferrerInfo,
                                      aObserver, aCORSMode, aIntegrity);
}

Result<RefPtr<StyleSheet>, nsresult> Loader::InternalLoadNonDocumentSheet(
    nsIURI* aURL, IsPreload aIsPreload, SheetParsingMode aParsingMode,
    UseSystemPrincipal aUseSystemPrincipal, nsIPrincipal* aOriginPrincipal,
    const Encoding* aPreloadEncoding, nsIReferrerInfo* aReferrerInfo,
    nsICSSLoaderObserver* aObserver, CORSMode aCORSMode,
    const nsAString& aIntegrity) {
  MOZ_ASSERT(aURL, "Must have a URI to load");
  MOZ_ASSERT(aUseSystemPrincipal == UseSystemPrincipal::No || !aObserver,
             "Shouldn't load system-principal sheets async");
  MOZ_ASSERT(aReferrerInfo, "Must have referrerInfo");

  LOG_URI("  Non-document sheet uri: '%s'", aURL);

  if (!mEnabled) {
    LOG_WARN(("  Not enabled"));
    return Err(NS_ERROR_NOT_AVAILABLE);
  }

  nsCOMPtr<nsIPrincipal> loadingPrincipal =
      (aOriginPrincipal && mDocument ? mDocument->NodePrincipal() : nullptr);
  nsresult rv = CheckContentPolicy(loadingPrincipal, aOriginPrincipal, aURL,
                                   mDocument, aIsPreload);
  if (NS_FAILED(rv)) {
    return Err(rv);
  }

  bool syncLoad = !aObserver;

  SheetState state;
  RefPtr<StyleSheet> sheet;
  Tie(sheet, state) =
      CreateSheet(aURL, nullptr, aOriginPrincipal, aParsingMode, aCORSMode,
                  aReferrerInfo, aIntegrity, syncLoad);

  PrepareSheet(*sheet, EmptyString(), EmptyString(), nullptr, IsAlternate::No,
               IsExplicitlyEnabled::No);

  if (state == SheetState::Complete) {
    LOG(("  Sheet already complete"));
    if (aObserver || !mObservers.IsEmpty()) {
      rv = PostLoadEvent(aURL, sheet, aObserver, IsAlternate::No,
                         MediaMatched::Yes, aReferrerInfo, nullptr);
      if (NS_FAILED(rv)) {
        return Err(rv);
      }
    }
    return sheet;
  }

  auto data = MakeRefPtr<SheetLoadData>(
      this, aURL, sheet, syncLoad,
      aUseSystemPrincipal == UseSystemPrincipal::Yes, aPreloadEncoding,
      aObserver, aOriginPrincipal, aReferrerInfo, mDocument);
  rv = LoadSheet(*data, state, aIsPreload);
  if (NS_FAILED(rv)) {
    return Err(rv);
  }
  if (aObserver) {
    data->mMustNotify = true;
  }
  return sheet;
}

nsresult Loader::PostLoadEvent(nsIURI* aURI, StyleSheet* aSheet,
                               nsICSSLoaderObserver* aObserver,
                               IsAlternate aWasAlternate,
                               MediaMatched aMediaMatched,
                               nsIReferrerInfo* aReferrerInfo,
                               nsIStyleSheetLinkingElement* aElement) {
  LOG(("css::Loader::PostLoadEvent"));
  MOZ_ASSERT(aSheet, "Must have sheet");
  MOZ_ASSERT(aObserver || !mObservers.IsEmpty() || aElement,
             "Must have observer or element");

  RefPtr<SheetLoadData> evt = new SheetLoadData(
      this,
      EmptyString(),  // title doesn't matter here
      aURI, aSheet, false, aElement, aWasAlternate, aMediaMatched, aObserver,
      nullptr, aReferrerInfo, mDocument);

  mPostedEvents.AppendElement(evt);

  nsresult rv;
  RefPtr<SheetLoadData> runnable(evt);
  if (mDocument) {
    rv = mDocument->Dispatch(TaskCategory::Other, runnable.forget());
  } else if (mDocGroup) {
    rv = mDocGroup->Dispatch(TaskCategory::Other, runnable.forget());
  } else {
    rv = SystemGroup::Dispatch(TaskCategory::Other, runnable.forget());
  }

  if (NS_FAILED(rv)) {
    NS_WARNING("failed to dispatch stylesheet load event");
    mPostedEvents.RemoveElement(evt);
  } else {
    // We'll unblock onload when we handle the event.
    BlockOnload();

    // We want to notify the observer for this data.
    evt->mMustNotify = true;
    evt->mSheetAlreadyComplete = true;

    // If we get to this code, aSheet loaded correctly at some point, so
    // we can just schedule a load event and don't need to touch the
    // data's mLoadFailed.  Note that we do this here and not from
    // inside our SheetComplete so that we don't end up running the load
    // event async.
    MOZ_ASSERT(!evt->mLoadFailed, "Why are we marked as failed?");
    evt->ScheduleLoadEventIfNeeded();
  }

  return rv;
}

void Loader::HandleLoadEvent(SheetLoadData& aEvent) {
  // XXXbz can't assert this yet.... May not have an observer because
  // we're unblocking the parser
  // NS_ASSERTION(aEvent->mObserver, "Must have observer");
  NS_ASSERTION(aEvent.mSheet, "Must have sheet");

  // Very important: this needs to come before the SheetComplete call
  // below, so that HasPendingLoads() will test true as needed under
  // notifications we send from that SheetComplete call.
  mPostedEvents.RemoveElement(&aEvent);

  if (!aEvent.mIsCancelled) {
    SheetComplete(aEvent, NS_OK);
  }

  UnblockOnload(true);
}

void Loader::Stop() {
  uint32_t pendingCount = mSheets ? mSheets->mPendingDatas.Count() : 0;
  uint32_t loadingCount = mSheets ? mSheets->mLoadingDatas.Count() : 0;
  LoadDataArray arr(pendingCount + loadingCount + mPostedEvents.Length());

  if (pendingCount) {
    for (auto iter = mSheets->mPendingDatas.Iter(); !iter.Done(); iter.Next()) {
      RefPtr<SheetLoadData>& data = iter.Data();
      data->mIsLoading = false;  // we will handle the removal right here
      data->mIsCancelled = true;
      arr.AppendElement(std::move(data));
    }
    mSheets->mPendingDatas.Clear();
  }
  if (loadingCount) {
    for (auto iter = mSheets->mLoadingDatas.Iter(); !iter.Done(); iter.Next()) {
      SheetLoadData* data = iter.Data();
      data->mIsLoading = false;  // we will handle the removal right here
      data->mIsCancelled = true;
      arr.AppendElement(data);
    }
    mSheets->mLoadingDatas.Clear();
  }

  for (RefPtr<SheetLoadData>& data : mPostedEvents) {
    data->mIsCancelled = true;
    // Move since we're about to get rid of the array below.
    arr.AppendElement(std::move(data));
  }
  mPostedEvents.Clear();

  mDatasToNotifyOn += arr.Length();
  for (RefPtr<SheetLoadData>& data : arr) {
    --mDatasToNotifyOn;
    SheetComplete(*data, NS_BINDING_ABORTED);
  }
}

bool Loader::HasPendingLoads() {
  return (mSheets && mSheets->mLoadingDatas.Count() != 0) ||
         (mSheets && mSheets->mPendingDatas.Count() != 0) ||
         mPostedEvents.Length() != 0 || mDatasToNotifyOn != 0;
}

void Loader::AddObserver(nsICSSLoaderObserver* aObserver) {
  MOZ_ASSERT(aObserver, "Must have observer");
  mObservers.AppendElementUnlessExists(aObserver);
}

void Loader::RemoveObserver(nsICSSLoaderObserver* aObserver) {
  mObservers.RemoveElement(aObserver);
}

void Loader::StartDeferredLoads() {
  MOZ_ASSERT(mSheets, "Don't call me!");
  LoadDataArray arr(mSheets->mPendingDatas.Count());
  for (auto iter = mSheets->mPendingDatas.Iter(); !iter.Done(); iter.Next()) {
    arr.AppendElement(iter.Data());
    iter.Remove();
  }

  mDatasToNotifyOn += arr.Length();
  for (RefPtr<SheetLoadData>& data : arr) {
    --mDatasToNotifyOn;
    LoadSheet(*data, SheetState::NeedsParser, IsPreload::No);
  }
}

NS_IMPL_CYCLE_COLLECTION_CLASS(Loader)

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Loader)
  if (tmp->mSheets) {
    for (auto iter = tmp->mSheets->mCompleteSheets.Iter(); !iter.Done();
         iter.Next()) {
      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "OOL sheet cache in Loader");
      cb.NoteXPCOMChild(iter.UserData());
    }
    for (auto iter = tmp->mSheets->mInlineSheets.Iter(); !iter.Done();
         iter.Next()) {
      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "Inline sheet cache in Loader");
      cb.NoteXPCOMChild(iter.UserData());
    }
  }
  nsTObserverArray<nsCOMPtr<nsICSSLoaderObserver>>::ForwardIterator it(
      tmp->mObservers);
  while (it.HasMore()) {
    ImplCycleCollectionTraverse(cb, it.GetNext(),
                                "mozilla::css::Loader.mObservers");
  }
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Loader)
  if (tmp->mSheets) {
    tmp->mSheets->mCompleteSheets.Clear();
    tmp->mSheets->mInlineSheets.Clear();
  }
  tmp->mObservers.Clear();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Loader, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Loader, Release)

size_t Loader::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
  size_t n = aMallocSizeOf(this);

  if (mSheets) {
    n += mSheets->SizeOfIncludingThis(aMallocSizeOf);
  }
  n += mObservers.ShallowSizeOfExcludingThis(aMallocSizeOf);

  // Measurement of the following members may be added later if DMD finds it is
  // worthwhile:
  // - mPostedEvents: transient, and should be small
  //
  // The following members aren't measured:
  // - mDocument, because it's a weak backpointer

  return n;
}

void Loader::BlockOnload() {
  if (mDocument) {
    mDocument->BlockOnload();
  }
}

void Loader::UnblockOnload(bool aFireSync) {
  if (mDocument) {
    mDocument->UnblockOnload(aFireSync);
  }
}

already_AddRefed<nsISerialEventTarget> Loader::DispatchTarget() {
  nsCOMPtr<nsISerialEventTarget> target;
  if (mDocument) {
    target = mDocument->EventTargetFor(TaskCategory::Other);
  } else if (mDocGroup) {
    target = mDocGroup->EventTargetFor(TaskCategory::Other);
  } else {
    target = SystemGroup::EventTargetFor(TaskCategory::Other);
  }

  return target.forget();
}

}  // namespace css
}  // namespace mozilla