Bug 1354989 - Avoid pivoting via UTF-16 when loading CSS in the Stylo mode. r=jdm,SimonSapin
authorHenri Sivonen <hsivonen@hsivonen.fi>
Tue, 29 Aug 2017 16:01:42 +0300
changeset 378296 32fb897ebe85c0a5134e987f2dfc15284f8ae8b4
parent 378295 8b71bab736b58e93777c095995f31b10e5eb797f
child 378297 9a66967ab703f9a348a7d73b47d2731448602988
push id32428
push userarchaeopteryx@coole-files.de
push dateSat, 02 Sep 2017 08:52:28 +0000
treeherdermozilla-central@b01a7e57425b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm, SimonSapin
bugs1354989
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1354989 - Avoid pivoting via UTF-16 when loading CSS in the Stylo mode. r=jdm,SimonSapin MozReview-Commit-ID: Llt29dvB4Io
dom/base/nsDocument.cpp
dom/base/nsDocument.h
dom/base/nsIDocument.h
dom/security/SRICheck.cpp
dom/security/SRICheck.h
dom/xbl/nsXBLResourceLoader.cpp
dom/xul/XULDocument.cpp
editor/libeditor/HTMLEditor.cpp
intl/Encoding.h
intl/encoding_glue/src/lib.rs
layout/style/Loader.cpp
layout/style/Loader.h
layout/style/ServoBindingList.h
layout/style/ServoStyleSheet.cpp
layout/style/ServoStyleSheet.h
layout/style/SheetLoadData.h
layout/style/StreamLoader.cpp
layout/style/StreamLoader.h
layout/style/moz.build
layout/style/nsLayoutStylesheetCache.cpp
layout/style/test/gtest/StyloParsingBench.cpp
parser/html/nsHtml5TreeOpExecutor.cpp
xpcom/base/ErrorList.py
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -9992,30 +9992,34 @@ public:
   }
   NS_DECL_ISUPPORTS
 };
 NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
 
 } // namespace
 
 void
-nsDocument::PreloadStyle(nsIURI* uri, const nsAString& charset,
+nsDocument::PreloadStyle(nsIURI* uri,
+                         const Encoding* aEncoding,
                          const nsAString& aCrossOriginAttr,
                          const ReferrerPolicy aReferrerPolicy,
                          const nsAString& aIntegrity)
 {
   // The CSSLoader will retain this object after we return.
   nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
 
   // Charset names are always ASCII.
-  CSSLoader()->LoadSheet(uri, true, NodePrincipal(),
-                         NS_LossyConvertUTF16toASCII(charset),
+  CSSLoader()->LoadSheet(uri,
+                         true,
+                         NodePrincipal(),
+                         aEncoding,
                          obs,
                          Element::StringToCORSMode(aCrossOriginAttr),
-                         aReferrerPolicy, aIntegrity);
+                         aReferrerPolicy,
+                         aIntegrity);
 }
 
 nsresult
 nsDocument::LoadChromeSheetSync(nsIURI* uri, bool isAgentSheet,
                                 RefPtr<mozilla::StyleSheet>* aSheet)
 {
   css::SheetParsingMode mode =
     isAgentSheet ? css::eAgentSheetFeatures
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -831,17 +831,18 @@ public:
                                  ReferrerPolicy aReferrerPolicy,
                                  bool aIsImgSet) override;
 
   virtual void ForgetImagePreload(nsIURI* aURI) override;
 
   virtual void MaybePreconnect(nsIURI* uri,
                                mozilla::CORSMode aCORSMode) override;
 
-  virtual void PreloadStyle(nsIURI* uri, const nsAString& charset,
+  virtual void PreloadStyle(nsIURI* uri,
+                            const mozilla::Encoding* aEncoding,
                             const nsAString& aCrossOriginAttr,
                             ReferrerPolicy aReferrerPolicy,
                             const nsAString& aIntegrity) override;
 
   virtual nsresult LoadChromeSheetSync(nsIURI* uri, bool isAgentSheet,
                                        RefPtr<mozilla::StyleSheet>* aSheet) override;
 
   virtual nsISupports* GetCurrentContentSink() override;
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2459,17 +2459,18 @@ public:
    */
   virtual void ForgetImagePreload(nsIURI* aURI) = 0;
 
   /**
    * Called by nsParser to preload style sheets.  Can also be merged into the
    * parser if and when the parser is merged with libgklayout.  aCrossOriginAttr
    * should be a void string if the attr is not present.
    */
-  virtual void PreloadStyle(nsIURI* aURI, const nsAString& aCharset,
+  virtual void PreloadStyle(nsIURI* aURI,
+                            const mozilla::Encoding* aEncoding,
                             const nsAString& aCrossOriginAttr,
                             ReferrerPolicyEnum aReferrerPolicy,
                             const nsAString& aIntegrity) = 0;
 
   /**
    * Called by the chrome registry to load style sheets.  Can be put
    * back there if and when when that module is merged with libgklayout.
    *
--- a/dom/security/SRICheck.cpp
+++ b/dom/security/SRICheck.cpp
@@ -177,47 +177,40 @@ SRICheck::IntegrityMetadata(const nsAStr
       SRILOG(("SRICheck::IntegrityMetadata, no valid metadata found"));
     }
   }
   return NS_OK;
 }
 
 /* static */ nsresult
 SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata,
-                          nsIUnicharStreamLoader* aLoader,
-                          const nsAString& aString,
+                          nsIChannel* aChannel,
+                          const nsACString& aBytes,
                           const nsACString& aSourceFileURI,
                           nsIConsoleReportCollector* aReporter)
 {
-  NS_ENSURE_ARG_POINTER(aLoader);
   NS_ENSURE_ARG_POINTER(aReporter);
 
-  nsCOMPtr<nsIChannel> channel;
-  aLoader->GetChannel(getter_AddRefs(channel));
-
   if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
     nsAutoCString requestURL;
     nsCOMPtr<nsIURI> originalURI;
-    if (channel &&
-        NS_SUCCEEDED(channel->GetOriginalURI(getter_AddRefs(originalURI))) &&
+    if (aChannel &&
+        NS_SUCCEEDED(aChannel->GetOriginalURI(getter_AddRefs(originalURI))) &&
         originalURI) {
       originalURI->GetAsciiSpec(requestURL);
     }
     SRILOG(("SRICheck::VerifyIntegrity (unichar stream)"));
   }
 
   SRICheckDataVerifier verifier(aMetadata, aSourceFileURI, aReporter);
-  nsresult rv;
-  nsDependentCString rawBuffer;
-  rv = aLoader->GetRawBuffer(rawBuffer);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = verifier.Update(rawBuffer.Length(), (const uint8_t*)rawBuffer.get());
+  nsresult rv =
+    verifier.Update(aBytes.Length(), (const uint8_t*)aBytes.BeginReading());
   NS_ENSURE_SUCCESS(rv, rv);
 
-  return verifier.Verify(aMetadata, channel, aSourceFileURI, aReporter);
+  return verifier.Verify(aMetadata, aChannel, aSourceFileURI, aReporter);
 }
 
 //////////////////////////////////////////////////////////////
 //
 //////////////////////////////////////////////////////////////
 SRICheckDataVerifier::SRICheckDataVerifier(const SRIMetadata& aMetadata,
                                            const nsACString& aSourceFileURI,
                                            nsIConsoleReportCollector* aReporter)
--- a/dom/security/SRICheck.h
+++ b/dom/security/SRICheck.h
@@ -6,17 +6,16 @@
 
 #ifndef mozilla_dom_SRICheck_h
 #define mozilla_dom_SRICheck_h
 
 #include "nsCOMPtr.h"
 #include "nsICryptoHash.h"
 
 class nsIChannel;
-class nsIUnicharStreamLoader;
 class nsIConsoleReportCollector;
 
 namespace mozilla {
 namespace dom {
 
 class SRIMetadata;
 
 class SRICheck final
@@ -34,18 +33,18 @@ public:
                                     nsIConsoleReportCollector* aReporter,
                                     SRIMetadata* outMetadata);
 
   /**
    * Process the integrity attribute of the element.  A result of false
    * must prevent the resource from loading.
    */
   static nsresult VerifyIntegrity(const SRIMetadata& aMetadata,
-                                  nsIUnicharStreamLoader* aLoader,
-                                  const nsAString& aString,
+                                  nsIChannel* aChannel,
+                                  const nsACString& aBytes,
                                   const nsACString& aSourceFileURI,
                                   nsIConsoleReportCollector* aReporter);
 };
 
 // The SRICheckDataVerifier can be used in 2 different mode:
 //
 // 1. The streaming mode involves reading bytes from an input, and to use
 //    the |Update| function to stream new bytes, and to use the |Verify|
--- a/dom/xbl/nsXBLResourceLoader.cpp
+++ b/dom/xbl/nsXBLResourceLoader.cpp
@@ -146,17 +146,17 @@ nsXBLResourceLoader::LoadResources(nsICo
           {
             rv = StyleSheetLoaded(sheet, false, NS_OK);
             NS_ASSERTION(NS_SUCCEEDED(rv), "Processing the style sheet failed!!!");
           }
         }
       }
       else
       {
-        rv = cssLoader->LoadSheet(url, false, docPrincipal, EmptyCString(), this);
+        rv = cssLoader->LoadSheet(url, false, docPrincipal, nullptr, this);
         if (NS_SUCCEEDED(rv))
           ++mPendingSheets;
       }
     }
   }
 
   mInLoadResourcesFunc = false;
 
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -3733,20 +3733,18 @@ XULDocument::AddPrototypeSheets()
     nsresult rv;
 
     const nsCOMArray<nsIURI>& sheets = mCurrentPrototype->GetStyleSheetReferences();
 
     for (int32_t i = 0; i < sheets.Count(); i++) {
         nsCOMPtr<nsIURI> uri = sheets[i];
 
         RefPtr<StyleSheet> incompleteSheet;
-        rv = CSSLoader()->LoadSheet(uri,
-                                    mCurrentPrototype->DocumentPrincipal(),
-                                    EmptyCString(), this,
-                                    &incompleteSheet);
+        rv = CSSLoader()->LoadSheet(
+          uri, mCurrentPrototype->DocumentPrincipal(), this, &incompleteSheet);
 
         // XXXldb We need to prevent bogus sheets from being held in the
         // prototype's list, but until then, don't propagate the failure
         // from LoadSheet (and thus exit the loop).
         if (NS_SUCCEEDED(rv)) {
             ++mPendingSheets;
             if (!mOverlaySheets.AppendElement(incompleteSheet)) {
                 return NS_ERROR_OUT_OF_MEMORY;
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -2834,18 +2834,18 @@ HTMLEditor::ReplaceStyleSheet(const nsAS
   }
   nsCOMPtr<nsIPresShell> ps = GetPresShell();
   NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
 
   nsCOMPtr<nsIURI> uaURI;
   nsresult rv = NS_NewURI(getter_AddRefs(uaURI), aURL);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  return ps->GetDocument()->CSSLoader()->
-    LoadSheet(uaURI, false, nullptr, EmptyCString(), this);
+  return ps->GetDocument()->CSSLoader()->LoadSheet(
+    uaURI, false, nullptr, nullptr, this);
 }
 
 NS_IMETHODIMP
 HTMLEditor::RemoveStyleSheet(const nsAString& aURL)
 {
   RefPtr<StyleSheet> sheet = GetStyleSheetForURL(aURL);
   NS_ENSURE_TRUE(sheet, NS_ERROR_UNEXPECTED);
 
--- a/intl/Encoding.h
+++ b/intl/Encoding.h
@@ -84,16 +84,24 @@ mozilla_encoding_decode_to_nscstring_wit
 
 nsresult
 mozilla_encoding_decode_to_nscstring_without_bom_handling(
   mozilla::Encoding const* encoding,
   nsACString const* src,
   nsACString* dst);
 
 nsresult
+mozilla_encoding_decode_from_slice_to_nscstring_without_bom_handling(
+  mozilla::Encoding const* encoding,
+  uint8_t const* src,
+  size_t src_len,
+  nsACString* dst,
+  size_t already_validated);
+
+nsresult
 mozilla_encoding_decode_to_nscstring_without_bom_handling_and_without_replacement(
   mozilla::Encoding const* encoding,
   nsACString const* src,
   nsACString* dst);
 
 nsresult
 mozilla_encoding_encode_from_nscstring(mozilla::Encoding const** encoding,
                                        nsACString const* src,
@@ -548,16 +556,51 @@ public:
       return mozilla_encoding_decode_to_nscstring_without_bom_handling_and_without_replacement(
         this, &temp, out);
     }
     return mozilla_encoding_decode_to_nscstring_without_bom_handling_and_without_replacement(
       this, bytes, out);
   }
 
   /**
+   * Decode complete input to `nsACString` _without BOM handling_ and
+   * with malformed sequences replaced with the REPLACEMENT CHARACTER when
+   * the entire input is available as a single buffer (i.e. the end of the
+   * buffer marks the end of the stream) _asserting that a number of bytes
+   * from the start are already known to be valid UTF-8_.
+   *
+   * The use case for this method is avoiding copying when dealing with
+   * input that has a UTF-8 BOM. _When in doubt, do not use this method._
+   *
+   * When invoked on `UTF_8`, this method implements the (non-streaming
+   * version of) the _UTF-8 decode without BOM_
+   * (https://encoding.spec.whatwg.org/#utf-8-decode-without-bom) spec concept.
+   *
+   * Returns `NS_ERROR_OUT_OF_MEMORY` upon OOM, `NS_OK_HAD_REPLACEMENTS`
+   * if there were malformed sequences (that were replaced with the
+   * REPLACEMENT CHARACTER) and `NS_OK` otherwise.
+   *
+   * _Note:_ It is wrong to use this when the input buffer represents only
+   * a segment of the input instead of the whole input. Use
+   * `NewDecoderWithoutBOMHandling()` when decoding segmented input.
+   *
+   * # Safety
+   *
+   * The first `aAlreadyValidated` bytes of `aBytes` _must_ be valid UTF-8.
+   * `aBytes` _must not_ alias the buffer (if any) of `aOut`.
+   */
+  inline nsresult DecodeWithoutBOMHandling(Span<const uint8_t> aBytes,
+                                           nsACString& aOut,
+                                           size_t aAlreadyValidated) const
+  {
+    return mozilla_encoding_decode_from_slice_to_nscstring_without_bom_handling(
+      this, aBytes.Elements(), aBytes.Length(), &aOut, aAlreadyValidated);
+  }
+
+  /**
    * Decode complete input to `nsAString` _without BOM handling_ and
    * _with malformed sequences treated as fatal_ when the entire input is
    * available as a single buffer (i.e. the end of the buffer marks the end
    * of the stream).
    *
    * When invoked on `UTF_8`, this method implements the (non-streaming
    * version of) the _UTF-8 decode without BOM or fail_
    * (https://encoding.spec.whatwg.org/#utf-8-decode-without-bom-or-fail)
--- a/intl/encoding_glue/src/lib.rs
+++ b/intl/encoding_glue/src/lib.rs
@@ -416,16 +416,21 @@ pub fn decode_to_nscstring_without_bom_h
         if dst.fallible_assign(src).is_err() {
             return NS_ERROR_OUT_OF_MEMORY;
         }
         return NS_OK;
     }
     decode_from_slice_to_nscstring_without_bom_handling(encoding, src, dst, valid_up_to)
 }
 
+#[no_mangle]
+pub unsafe extern "C" fn mozilla_encoding_decode_from_slice_to_nscstring_without_bom_handling(encoding: *const Encoding, src: *const u8, src_len: usize, dst: *mut nsACString, already_validated: usize) -> nsresult {
+    decode_from_slice_to_nscstring_without_bom_handling(&*encoding, slice::from_raw_parts(src, src_len), &mut *dst, already_validated)
+}
+
 fn decode_from_slice_to_nscstring_without_bom_handling(encoding: &'static Encoding,
                                                        src: &[u8],
                                                        dst: &mut nsACString,
                                                        already_validated: usize)
                                                        -> nsresult {
     let bytes = src;
     let mut decoder = encoding.new_decoder_without_bom_handling();
     let rounded_without_replacement =
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -48,31 +48,35 @@
 #include "mozilla/dom/ShadowRoot.h"
 #include "mozilla/dom/URL.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"
 
 #ifdef MOZ_XUL
 #include "nsXULPrototypeCache.h"
 #endif
 
 #include "nsIDOMStyleSheet.h"
 #include "nsError.h"
 
 #include "nsIContentSecurityPolicy.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
@@ -92,172 +96,16 @@ using namespace mozilla::dom;
  *                     this class handles listening for the stream
  *                     loader completion and also handles charset
  *                     determination.
  */
 
 namespace mozilla {
 namespace css {
 
-/*********************************************
- * Data needed to properly load a stylesheet *
- *********************************************/
-
-static_assert(eAuthorSheetFeatures == 0 &&
-              eUserSheetFeatures == 1 &&
-              eAgentSheetFeatures == 2,
-              "sheet parsing mode constants won't fit "
-              "in SheetLoadData::mParsingMode");
-
-class SheetLoadData final : public nsIRunnable,
-                            public nsIUnicharStreamLoaderObserver,
-                            public nsIThreadObserver
-{
-protected:
-  virtual ~SheetLoadData(void);
-
-public:
-  // Data for loading a sheet linked from a document
-  SheetLoadData(Loader* aLoader,
-                const nsAString& aTitle,
-                nsIURI* aURI,
-                StyleSheet* aSheet,
-                nsIStyleSheetLinkingElement* aOwningElement,
-                bool aIsAlternate,
-                nsICSSLoaderObserver* aObserver,
-                nsIPrincipal* aLoaderPrincipal,
-                nsINode* aRequestingNode);
-
-  // Data for loading a sheet linked from an @import rule
-  SheetLoadData(Loader* aLoader,
-                nsIURI* aURI,
-                StyleSheet* aSheet,
-                SheetLoadData* aParentData,
-                nsICSSLoaderObserver* aObserver,
-                nsIPrincipal* aLoaderPrincipal,
-                nsINode* aRequestingNode);
-
-  // Data for loading a non-document sheet
-  SheetLoadData(Loader* aLoader,
-                nsIURI* aURI,
-                StyleSheet* aSheet,
-                bool aSyncLoad,
-                bool aUseSystemPrincipal,
-                const nsCString& aCharset,
-                nsICSSLoaderObserver* aObserver,
-                nsIPrincipal* aLoaderPrincipal,
-                nsINode* aRequestingNode);
-
-  already_AddRefed<nsIURI> GetReferrerURI();
-
-  void ScheduleLoadEventIfNeeded(nsresult aStatus);
-
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIRUNNABLE
-  NS_DECL_NSITHREADOBSERVER
-  NS_DECL_NSIUNICHARSTREAMLOADEROBSERVER
-
-  // Hold a ref to the CSSLoader so we can call back to it to let it
-  // know the load finished
-  RefPtr<Loader>           mLoader;
-
-  // Title needed to pull datas out of the pending datas table when
-  // the preferred title is changed
-  nsString                   mTitle;
-
-  // Charset we decided to use for the sheet
-  nsCString                  mCharset;
-
-  // URI we're loading.  Null for inline sheets
-  nsCOMPtr<nsIURI>           mURI;
-
-  // Should be 1 for non-inline sheets.
-  uint32_t                   mLineNumber;
-
-  // The sheet we're loading data for
-  RefPtr<StyleSheet> mSheet;
-
-  // Linked list of datas for the same URI as us
-  SheetLoadData*             mNext;  // strong ref
-
-  // Load data for the sheet that @import-ed us if we were @import-ed
-  // during the parse
-  RefPtr<SheetLoadData>    mParentData;
-
-  // Number of sheets we @import-ed that are still loading
-  uint32_t                   mPendingChildren;
-
-  // mSyncLoad is true when the load needs to be synchronous -- right
-  // now only for LoadSheetSync and children of sync loads.
-  bool                       mSyncLoad : 1;
-
-  // mIsNonDocumentSheet is true if the load was triggered by LoadSheetSync or
-  // LoadSheet or an @import from such a sheet.  Non-document sheet loads can
-  // proceed even if we have no document.
-  bool                       mIsNonDocumentSheet : 1;
-
-  // mIsLoading is true from the moment we are placed in the loader's
-  // "loading datas" table (right after the async channel is opened)
-  // to the moment we are removed from said table (due to the load
-  // completing or being cancelled).
-  bool                       mIsLoading : 1;
-
-  // mIsCancelled is set to true when a sheet load is stopped by
-  // Stop() or StopLoadingSheet() (which was removed in Bug 556446).
-  // SheetLoadData::OnStreamComplete() checks this to avoid parsing
-  // sheets that have been cancelled and such.
-  bool                       mIsCancelled : 1;
-
-  // mMustNotify is true if the load data is being loaded async and
-  // the original function call that started the load has returned.
-  // This applies only to observer notifications; load/error events
-  // are fired for any SheetLoadData that has a non-null
-  // mOwningElement.
-  bool                       mMustNotify : 1;
-
-  // mWasAlternate is true if the sheet was an alternate when the load data was
-  // created.
-  bool                       mWasAlternate : 1;
-
-  // mUseSystemPrincipal is true if the system principal should be used for
-  // this sheet, no matter what the channel principal is.  Only true for sync
-  // loads.
-  bool                       mUseSystemPrincipal : 1;
-
-  // If true, this SheetLoadData is being used as a way to handle
-  // async observer notification for an already-complete sheet.
-  bool                       mSheetAlreadyComplete : 1;
-
-  // This is the element that imported the sheet.  Needed to get the
-  // charset set on it and to fire load/error events.
-  nsCOMPtr<nsIStyleSheetLinkingElement> mOwningElement;
-
-  // The observer that wishes to be notified of load completion
-  nsCOMPtr<nsICSSLoaderObserver>        mObserver;
-
-  // The principal that identifies who started loading us.
-  nsCOMPtr<nsIPrincipal>                mLoaderPrincipal;
-
-  // The node that identifies who started loading us.
-  nsCOMPtr<nsINode>                     mRequestingNode;
-
-  // The charset to use if the transport and sheet don't indicate one.
-  // May be empty.  Must be empty if mOwningElement is non-null.
-  nsCString                             mCharsetHint;
-
-  // The status our load ended up with; this determines whether we
-  // should fire error events or load events.  This gets initialized
-  // by ScheduleLoadEventIfNeeded, and is only used after that has
-  // been called.
-  MOZ_INIT_OUTSIDE_CTOR nsresult        mStatus;
-
-private:
-  void FireLoadEvent(nsIThreadInternal* aThread);
-};
-
 #include "mozilla/Logging.h"
 
 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)
@@ -296,65 +144,69 @@ SheetLoadData::SheetLoadData(Loader* aLo
                              const nsAString& aTitle,
                              nsIURI* aURI,
                              StyleSheet* aSheet,
                              nsIStyleSheetLinkingElement* aOwningElement,
                              bool aIsAlternate,
                              nsICSSLoaderObserver* aObserver,
                              nsIPrincipal* aLoaderPrincipal,
                              nsINode* aRequestingNode)
-  : mLoader(aLoader),
-    mTitle(aTitle),
-    mURI(aURI),
-    mLineNumber(1),
-    mSheet(aSheet),
-    mNext(nullptr),
-    mPendingChildren(0),
-    mSyncLoad(false),
-    mIsNonDocumentSheet(false),
-    mIsLoading(false),
-    mIsCancelled(false),
-    mMustNotify(false),
-    mWasAlternate(aIsAlternate),
-    mUseSystemPrincipal(false),
-    mSheetAlreadyComplete(false),
-    mOwningElement(aOwningElement),
-    mObserver(aObserver),
-    mLoaderPrincipal(aLoaderPrincipal),
-    mRequestingNode(aRequestingNode)
+  : mLoader(aLoader)
+  , mTitle(aTitle)
+  , mEncoding(nullptr)
+  , mURI(aURI)
+  , mLineNumber(1)
+  , mSheet(aSheet)
+  , mNext(nullptr)
+  , mPendingChildren(0)
+  , mSyncLoad(false)
+  , mIsNonDocumentSheet(false)
+  , mIsLoading(false)
+  , mIsCancelled(false)
+  , mMustNotify(false)
+  , mWasAlternate(aIsAlternate)
+  , mUseSystemPrincipal(false)
+  , mSheetAlreadyComplete(false)
+  , mOwningElement(aOwningElement)
+  , mObserver(aObserver)
+  , mLoaderPrincipal(aLoaderPrincipal)
+  , mRequestingNode(aRequestingNode)
+  , mPreloadEncoding(nullptr)
 {
   NS_PRECONDITION(mLoader, "Must have a loader!");
 }
 
 SheetLoadData::SheetLoadData(Loader* aLoader,
                              nsIURI* aURI,
                              StyleSheet* aSheet,
                              SheetLoadData* aParentData,
                              nsICSSLoaderObserver* aObserver,
                              nsIPrincipal* aLoaderPrincipal,
                              nsINode* aRequestingNode)
-  : mLoader(aLoader),
-    mURI(aURI),
-    mLineNumber(1),
-    mSheet(aSheet),
-    mNext(nullptr),
-    mParentData(aParentData),
-    mPendingChildren(0),
-    mSyncLoad(false),
-    mIsNonDocumentSheet(false),
-    mIsLoading(false),
-    mIsCancelled(false),
-    mMustNotify(false),
-    mWasAlternate(false),
-    mUseSystemPrincipal(false),
-    mSheetAlreadyComplete(false),
-    mOwningElement(nullptr),
-    mObserver(aObserver),
-    mLoaderPrincipal(aLoaderPrincipal),
-    mRequestingNode(aRequestingNode)
+  : mLoader(aLoader)
+  , mEncoding(nullptr)
+  , mURI(aURI)
+  , mLineNumber(1)
+  , mSheet(aSheet)
+  , mNext(nullptr)
+  , mParentData(aParentData)
+  , mPendingChildren(0)
+  , mSyncLoad(false)
+  , mIsNonDocumentSheet(false)
+  , mIsLoading(false)
+  , mIsCancelled(false)
+  , mMustNotify(false)
+  , mWasAlternate(false)
+  , mUseSystemPrincipal(false)
+  , mSheetAlreadyComplete(false)
+  , mOwningElement(nullptr)
+  , mObserver(aObserver)
+  , mLoaderPrincipal(aLoaderPrincipal)
+  , mRequestingNode(aRequestingNode)
+  , mPreloadEncoding(nullptr)
 {
   NS_PRECONDITION(mLoader, "Must have a loader!");
   if (mParentData) {
     mSyncLoad = mParentData->mSyncLoad;
     mIsNonDocumentSheet = mParentData->mIsNonDocumentSheet;
     mUseSystemPrincipal = mParentData->mUseSystemPrincipal;
     ++(mParentData->mPendingChildren);
   }
@@ -363,39 +215,40 @@ SheetLoadData::SheetLoadData(Loader* aLo
                    "Shouldn't use system principal for async loads");
 }
 
 SheetLoadData::SheetLoadData(Loader* aLoader,
                              nsIURI* aURI,
                              StyleSheet* aSheet,
                              bool aSyncLoad,
                              bool aUseSystemPrincipal,
-                             const nsCString& aCharset,
+                             const Encoding* aPreloadEncoding,
                              nsICSSLoaderObserver* aObserver,
                              nsIPrincipal* aLoaderPrincipal,
                              nsINode* aRequestingNode)
-  : mLoader(aLoader),
-    mURI(aURI),
-    mLineNumber(1),
-    mSheet(aSheet),
-    mNext(nullptr),
-    mPendingChildren(0),
-    mSyncLoad(aSyncLoad),
-    mIsNonDocumentSheet(true),
-    mIsLoading(false),
-    mIsCancelled(false),
-    mMustNotify(false),
-    mWasAlternate(false),
-    mUseSystemPrincipal(aUseSystemPrincipal),
-    mSheetAlreadyComplete(false),
-    mOwningElement(nullptr),
-    mObserver(aObserver),
-    mLoaderPrincipal(aLoaderPrincipal),
-    mRequestingNode(aRequestingNode),
-    mCharsetHint(aCharset)
+  : mLoader(aLoader)
+  , mEncoding(nullptr)
+  , mURI(aURI)
+  , mLineNumber(1)
+  , mSheet(aSheet)
+  , mNext(nullptr)
+  , mPendingChildren(0)
+  , mSyncLoad(aSyncLoad)
+  , mIsNonDocumentSheet(true)
+  , mIsLoading(false)
+  , mIsCancelled(false)
+  , mMustNotify(false)
+  , mWasAlternate(false)
+  , mUseSystemPrincipal(aUseSystemPrincipal)
+  , mSheetAlreadyComplete(false)
+  , mOwningElement(nullptr)
+  , mObserver(aObserver)
+  , mLoaderPrincipal(aLoaderPrincipal)
+  , mRequestingNode(aRequestingNode)
+  , mPreloadEncoding(aPreloadEncoding)
 {
   NS_PRECONDITION(mLoader, "Must have a loader!");
   NS_POSTCONDITION(!mUseSystemPrincipal || mSyncLoad,
                    "Shouldn't use system principal for async loads");
 }
 
 SheetLoadData::~SheetLoadData()
 {
@@ -640,158 +493,151 @@ static bool GetCharsetFromData(const cha
     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;
+}
+
+/*
+ * Encoding decision for the old style system
+ */
 NS_IMETHODIMP
 SheetLoadData::OnDetermineCharset(nsIUnicharStreamLoader* aLoader,
                                   nsISupports* aContext,
                                   nsACString const& aSegment,
                                   nsACString& aCharset)
 {
-  NS_PRECONDITION(!mOwningElement || mCharsetHint.IsEmpty(),
-                  "Can't have element _and_ charset hint");
-
-  LOG_URI("SheetLoadData::OnDetermineCharset for '%s'", mURI);
-
-  // The precedence is (per CSS3 Syntax 2012-11-08 ED):
-  // BOM
-  // Channel
-  // @charset rule
-  // charset attribute on the referrer
-  // encoding of the referrer
-  // UTF-8
-
-  aCharset.Truncate();
-
   const Encoding* encoding;
   size_t bomLength;
   Tie(encoding, bomLength) = Encoding::ForBOM(aSegment);
   Unused << bomLength;
-  if (encoding) {
-    encoding->Name(aCharset);
-    // aCharset is now either "UTF-16BE", "UTF-16BE" or "UTF-8"
-    // which will swallow the BOM.
-    mCharset.Assign(aCharset);
-    LOG(("  Setting from BOM to: %s", PromiseFlatCString(aCharset).get()));
-    return NS_OK;
-  }
-
-  nsCOMPtr<nsIChannel> channel;
-  nsAutoCString specified;
-  aLoader->GetChannel(getter_AddRefs(channel));
-  if (channel) {
-    channel->GetContentCharset(specified);
-    encoding = Encoding::ForLabel(specified);
-    if (encoding) {
-      encoding->Name(aCharset);
-      mCharset.Assign(aCharset);
-      LOG(("  Setting from HTTP to: %s", PromiseFlatCString(aCharset).get()));
-      return NS_OK;
-    }
-  }
-
-  if (GetCharsetFromData(aSegment.BeginReading(),
-                         aSegment.Length(),
-                         specified)) {
-    encoding = Encoding::ForLabel(specified);
-    if (encoding) {
-      encoding->Name(aCharset);
-      if (encoding == UTF_16BE_ENCODING ||
-          encoding == UTF_16LE_ENCODING) {
-        // Be consistent with HTML <meta> handling in face of impossibility.
-        // When the @charset rule itself evidently was not UTF-16-encoded,
-        // it saying UTF-16 has to be a lie.
-        aCharset.AssignLiteral("UTF-8");
-      }
-      mCharset.Assign(aCharset);
-      LOG(("  Setting from @charset rule to: %s",
-          PromiseFlatCString(aCharset).get()));
-      return NS_OK;
-    }
+  if (!encoding) {
+    nsCOMPtr<nsIChannel> channel;
+    aLoader->GetChannel(getter_AddRefs(channel));
+    encoding = DetermineNonBOMEncoding(aSegment, channel);
   }
 
-  // Now try the charset on the <link> or processing instruction
-  // that loaded us
-  if (mOwningElement) {
-    nsAutoString specified16;
-    mOwningElement->GetCharset(specified16);
-    encoding = Encoding::ForLabel(specified16);
-    if (encoding) {
-      encoding->Name(aCharset);
-      mCharset.Assign(aCharset);
-      LOG(("  Setting from charset attribute to: %s",
-          PromiseFlatCString(aCharset).get()));
-      return NS_OK;
-    }
-  }
-
-  // In the preload case, the value of the charset attribute on <link> comes
-  // in via mCharsetHint instead.
-  encoding = Encoding::ForLabel(mCharsetHint);
-  if (encoding) {
-    encoding->Name(aCharset);
-    mCharset.Assign(aCharset);
-      LOG(("  Setting from charset attribute (preload case) to: %s",
-          PromiseFlatCString(aCharset).get()));
-    return NS_OK;
-  }
-
-  // Try charset from the parent stylesheet.
-  if (mParentData) {
-    aCharset = mParentData->mCharset;
-    if (!aCharset.IsEmpty()) {
-      mCharset.Assign(aCharset);
-      LOG(("  Setting from parent sheet to: %s",
-          PromiseFlatCString(aCharset).get()));
-      return NS_OK;
-    }
-  }
-
-  if (mLoader->mDocument) {
-    // no useful data on charset.  Try the document charset.
-    auto encoding = mLoader->mDocument->GetDocumentCharacterSet();
-    encoding->Name(aCharset);
-    mCharset.Assign(aCharset);
-    LOG(("  Setting from document to: %s", PromiseFlatCString(aCharset).get()));
-    return NS_OK;
-  }
-
-  aCharset.AssignLiteral("UTF-8");
-  mCharset = aCharset;
-  LOG(("  Setting from default to: %s", PromiseFlatCString(aCharset).get()));
+  encoding->Name(aCharset);
+  mEncoding = encoding;
   return NS_OK;
 }
 
 already_AddRefed<nsIURI>
 SheetLoadData::GetReferrerURI()
 {
   nsCOMPtr<nsIURI> uri;
   if (mParentData)
     uri = mParentData->mSheet->GetSheetURI();
   if (!uri && mLoader->mDocument)
     uri = mLoader->mDocument->GetDocumentURI();
   return uri.forget();
 }
 
 /*
- * 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.
+ * Load completion for the old style system.
  */
 NS_IMETHODIMP
 SheetLoadData::OnStreamComplete(nsIUnicharStreamLoader* aLoader,
                                 nsISupports* aContext,
                                 nsresult aStatus,
                                 const nsAString& aBuffer)
 {
+  nsCOMPtr<nsIChannel> channel;
+  aLoader->GetChannel(getter_AddRefs(channel));
+  nsCString bytes;
+  aLoader->GetRawBuffer(bytes);
+
+  nsresult rv = VerifySheetReadyToParse(aStatus, bytes, channel);
+  if (rv != NS_OK_PARSE_SHEET) {
+    return rv;
+  }
+
+  bool completed;
+  rv = mLoader->ParseSheet(aBuffer, Span<const uint8_t>(), this, completed);
+  NS_ASSERTION(completed || !mSyncLoad, "sync load did not complete");
+  return rv;
+}
+
+/*
+ * 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& aBytes,
+                                       nsIChannel* aChannel)
+{
   LOG(("SheetLoadData::OnStreamComplete"));
   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;
@@ -821,83 +667,79 @@ SheetLoadData::OnStreamComplete(nsIUnich
           doc->AddBlockedTrackingNode(content);
         }
       }
     }
     mLoader->SheetComplete(this, aStatus);
     return NS_OK;
   }
 
-  nsCOMPtr<nsIChannel> channel;
-  nsresult result = aLoader->GetChannel(getter_AddRefs(channel));
-  if (NS_FAILED(result)) {
-    LOG_WARN(("  No channel from loader"));
-    mLoader->SheetComplete(this, result);
+  if (!aChannel) {
+    mLoader->SheetComplete(this, NS_OK);
     return NS_OK;
   }
 
   nsCOMPtr<nsIURI> originalURI;
-  channel->GetOriginalURI(getter_AddRefs(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(channel, getter_AddRefs(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();
-  result = NS_ERROR_NOT_AVAILABLE;
+  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(channel, getter_AddRefs(principal));
+      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 it's an HTTP channel, we want to make sure this is not an
   // error document we got.
-  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+  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;
-  if (channel) {
-    channel->GetContentType(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) ||
@@ -944,17 +786,17 @@ SheetLoadData::OnStreamComplete(nsIUnich
       mLoader->SheetComplete(this, NS_ERROR_NOT_AVAILABLE);
       return NS_OK;
     }
   }
 
   SRIMetadata sriMetadata;
   mSheet->GetIntegrity(sriMetadata);
   if (sriMetadata.IsEmpty()) {
-    nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
+    nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
     if (loadInfo && loadInfo->GetEnforceSRI()) {
       LOG(("  Load was blocked by SRI"));
       MOZ_LOG(gSriPRLog, mozilla::LogLevel::Debug,
               ("css::Loader::OnStreamComplete, required SRI not found"));
       mLoader->SheetComplete(this, NS_ERROR_SRI_CORRUPT);
       // log the failed load to web console
       nsCOMPtr<nsIContentSecurityPolicy> csp;
       loadInfo->LoadingPrincipal()->GetCsp(getter_AddRefs(csp));
@@ -967,21 +809,21 @@ SheetLoadData::OnStreamComplete(nsIUnich
         0, EmptyString(), EmptyString());
       return NS_OK;
     }
   } else {
     nsAutoCString sourceUri;
     if (mLoader->mDocument && mLoader->mDocument->GetDocumentURI()) {
       mLoader->mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
     }
-    nsresult rv = SRICheck::VerifyIntegrity(sriMetadata, aLoader, aBuffer,
-                                            sourceUri, mLoader->mReporter);
+    nsresult rv = SRICheck::VerifyIntegrity(
+      sriMetadata, aChannel, aBytes, sourceUri, mLoader->mReporter);
 
     nsCOMPtr<nsILoadGroup> loadGroup;
-    channel->GetLoadGroup(getter_AddRefs(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"));
@@ -990,21 +832,17 @@ SheetLoadData::OnStreamComplete(nsIUnich
       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);
-
-  bool completed;
-  result = mLoader->ParseSheet(aBuffer, this, completed);
-  NS_ASSERTION(completed || !mSyncLoad, "sync load did not complete");
-  return result;
+  return NS_OK_PARSE_SHEET;
 }
 
 bool
 Loader::IsAlternate(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
@@ -1464,22 +1302,29 @@ Loader::LoadSheet(SheetLoadData* aLoadDa
     LOG(("  Synchronous load"));
     NS_ASSERTION(!aLoadData->mObserver, "Observer for a sync load?");
     NS_ASSERTION(aSheetState == eSheetNeedsParser,
                  "Sync loads can't reuse existing async loads");
 
     // Create a nsIUnicharStreamLoader instance to which we will feed
     // the data from the sync load.  Do this before creating the
     // channel to make error recovery simpler.
-    nsCOMPtr<nsIUnicharStreamLoader> streamLoader;
-    rv = NS_NewUnicharStreamLoader(getter_AddRefs(streamLoader), aLoadData);
-    if (NS_FAILED(rv)) {
-      LOG_ERROR(("  Failed to create stream loader for sync load"));
-      SheetComplete(aLoadData, rv);
-      return rv;
+    nsCOMPtr<nsIStreamListener> streamLoader;
+    if (aLoadData->mSheet->IsGecko()) {
+      nsCOMPtr<nsIUnicharStreamLoader> unicharStreamLoader;
+      rv = NS_NewUnicharStreamLoader(getter_AddRefs(unicharStreamLoader),
+                                     aLoadData);
+      streamLoader = unicharStreamLoader;
+      if (NS_FAILED(rv)) {
+        LOG_ERROR(("  Failed to create stream loader for sync load"));
+        SheetComplete(aLoadData, rv);
+        return rv;
+      }
+    } else {
+      streamLoader = new StreamLoader(aLoadData);
     }
 
     if (mDocument) {
       mozilla::net::PredictorLearn(aLoadData->mURI, mDocument->GetDocumentURI(),
                                    nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
                                    mDocument);
     }
 
@@ -1702,25 +1547,32 @@ Loader::LoadSheet(SheetLoadData* aLoadDa
 
   // 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<nsIUnicharStreamLoader> streamLoader;
-  rv = NS_NewUnicharStreamLoader(getter_AddRefs(streamLoader), aLoadData);
-  if (NS_FAILED(rv)) {
+  nsCOMPtr<nsIStreamListener> streamLoader;
+  if (aLoadData->mSheet->IsGecko()) {
+    nsCOMPtr<nsIUnicharStreamLoader> unicharStreamLoader;
+    rv =
+      NS_NewUnicharStreamLoader(getter_AddRefs(unicharStreamLoader), aLoadData);
+    streamLoader = unicharStreamLoader;
+    if (NS_FAILED(rv)) {
 #ifdef DEBUG
-    mSyncCallback = false;
+      mSyncCallback = false;
 #endif
-    LOG_ERROR(("  Failed to create stream loader"));
-    SheetComplete(aLoadData, rv);
-    return rv;
+      LOG_ERROR(("  Failed to create stream loader"));
+      SheetComplete(aLoadData, rv);
+      return rv;
+    }
+  } else {
+    streamLoader = new StreamLoader(aLoadData);
   }
 
   if (mDocument) {
     mozilla::net::PredictorLearn(aLoadData->mURI, mDocument->GetDocumentURI(),
                                  nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
                                  mDocument);
   }
 
@@ -1744,17 +1596,18 @@ Loader::LoadSheet(SheetLoadData* aLoadDa
 
 /**
  * ParseSheet handles parsing the data stream.  The main idea here is
  * to push the current load data onto the parse stack before letting
  * the CSS parser at the data stream.  That lets us handle @import
  * correctly.
  */
 nsresult
-Loader::ParseSheet(const nsAString& aInput,
+Loader::ParseSheet(const nsAString& aUTF16,
+                   Span<const uint8_t> aUTF8,
                    SheetLoadData* aLoadData,
                    bool& aCompleted)
 {
   LOG(("css::Loader::ParseSheet"));
   NS_PRECONDITION(aLoadData, "Must have load data");
   NS_PRECONDITION(aLoadData->mSheet, "Must have sheet to parse into");
 
   aCompleted = false;
@@ -1763,26 +1616,30 @@ Loader::ParseSheet(const nsAString& aInp
   mParsingDatas.AppendElement(aLoadData);
   nsIURI* sheetURI = aLoadData->mSheet->GetSheetURI();
   nsIURI* baseURI = aLoadData->mSheet->GetBaseURI();
 
   nsresult rv;
 
   if (aLoadData->mSheet->IsGecko()) {
     nsCSSParser parser(this, aLoadData->mSheet->AsGecko());
-    rv = parser.ParseSheet(aInput, sheetURI, baseURI,
+    rv = parser.ParseSheet(aUTF16,
+                           sheetURI,
+                           baseURI,
                            aLoadData->mSheet->Principal(),
                            aLoadData->mLineNumber);
   } else {
-    rv =
-      aLoadData->mSheet->AsServo()->ParseSheet(this,
-                                               aInput, sheetURI, baseURI,
-                                               aLoadData->mSheet->Principal(),
-                                               aLoadData->mLineNumber,
-                                               GetCompatibilityMode());
+    rv = aLoadData->mSheet->AsServo()->ParseSheet(
+      this,
+      aUTF8.IsEmpty() ? NS_ConvertUTF16toUTF8(aUTF16) : aUTF8,
+      sheetURI,
+      baseURI,
+      aLoadData->mSheet->Principal(),
+      aLoadData->mLineNumber,
+      GetCompatibilityMode());
   }
 
   mParsingDatas.RemoveElementAt(mParsingDatas.Length() - 1);
 
   if (NS_FAILED(rv)) {
     LOG_ERROR(("  Low-level error in parser!"));
     SheetComplete(aLoadData, rv);
     return rv;
@@ -2031,17 +1888,17 @@ Loader::LoadInlineStyle(nsIContent* aEle
                                           aObserver, nullptr, static_cast<nsINode*>(aElement));
 
   // We never actually load this, so just set its principal directly
   sheet->SetPrincipal(aElement->NodePrincipal());
 
   NS_ADDREF(data);
   data->mLineNumber = aLineNumber;
   // Parse completion releases the load data
-  rv = ParseSheet(aBuffer, data, *aCompleted);
+  rv = ParseSheet(aBuffer, Span<const uint8_t>(), data, *aCompleted);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // If aCompleted is true, |data| may well be deleted by now.
   if (!*aCompleted) {
     data->mMustNotify = true;
   }
   return rv;
 }
@@ -2307,75 +2164,92 @@ Loader::LoadChildSheet(StyleSheet* aPare
 nsresult
 Loader::LoadSheetSync(nsIURI* aURL,
                       SheetParsingMode aParsingMode,
                       bool aUseSystemPrincipal,
                       RefPtr<StyleSheet>* aSheet)
 {
   LOG(("css::Loader::LoadSheetSync"));
   return InternalLoadNonDocumentSheet(aURL,
-                                      false, aParsingMode, aUseSystemPrincipal,
-                                      nullptr, EmptyCString(),
-                                      aSheet, nullptr);
+                                      false,
+                                      aParsingMode,
+                                      aUseSystemPrincipal,
+                                      nullptr,
+                                      nullptr,
+                                      aSheet,
+                                      nullptr);
 }
 
 nsresult
 Loader::LoadSheet(nsIURI* aURL,
                   SheetParsingMode aParsingMode,
                   bool aUseSystemPrincipal,
                   nsICSSLoaderObserver* aObserver,
                   RefPtr<StyleSheet>* aSheet)
 {
   LOG(("css::Loader::LoadSheet(aURL, aParsingMode, aUseSystemPrincipal, aObserver, aSheet)"));
   return InternalLoadNonDocumentSheet(aURL,
-                                      false, aParsingMode, aUseSystemPrincipal,
-                                      nullptr, EmptyCString(),
-                                      aSheet, aObserver);
+                                      false,
+                                      aParsingMode,
+                                      aUseSystemPrincipal,
+                                      nullptr,
+                                      nullptr,
+                                      aSheet,
+                                      aObserver);
 }
 
 nsresult
 Loader::LoadSheet(nsIURI* aURL,
                   nsIPrincipal* aOriginPrincipal,
-                  const nsCString& aCharset,
                   nsICSSLoaderObserver* aObserver,
                   RefPtr<StyleSheet>* aSheet)
 {
   LOG(("css::Loader::LoadSheet(aURL, aObserver, aSheet) api call"));
   NS_PRECONDITION(aSheet, "aSheet is null");
   return InternalLoadNonDocumentSheet(aURL,
-                                      false, eAuthorSheetFeatures, false,
-                                      aOriginPrincipal, aCharset,
-                                      aSheet, aObserver);
+                                      false,
+                                      eAuthorSheetFeatures,
+                                      false,
+                                      aOriginPrincipal,
+                                      nullptr,
+                                      aSheet,
+                                      aObserver);
 }
 
 nsresult
 Loader::LoadSheet(nsIURI* aURL,
                   bool aIsPreload,
                   nsIPrincipal* aOriginPrincipal,
-                  const nsCString& aCharset,
+                  const Encoding* aPreloadEncoding,
                   nsICSSLoaderObserver* aObserver,
                   CORSMode aCORSMode,
                   ReferrerPolicy aReferrerPolicy,
                   const nsAString& aIntegrity)
 {
   LOG(("css::Loader::LoadSheet(aURL, aObserver) api call"));
   return InternalLoadNonDocumentSheet(aURL,
-                                      aIsPreload, eAuthorSheetFeatures, false,
-                                      aOriginPrincipal, aCharset,
-                                      nullptr, aObserver,
-                                      aCORSMode, aReferrerPolicy, aIntegrity);
+                                      aIsPreload,
+                                      eAuthorSheetFeatures,
+                                      false,
+                                      aOriginPrincipal,
+                                      aPreloadEncoding,
+                                      nullptr,
+                                      aObserver,
+                                      aCORSMode,
+                                      aReferrerPolicy,
+                                      aIntegrity);
 }
 
 nsresult
 Loader::InternalLoadNonDocumentSheet(nsIURI* aURL,
                                      bool aIsPreload,
                                      SheetParsingMode aParsingMode,
                                      bool aUseSystemPrincipal,
                                      nsIPrincipal* aOriginPrincipal,
-                                     const nsCString& aCharset,
+                                     const Encoding* aPreloadEncoding,
                                      RefPtr<StyleSheet>* aSheet,
                                      nsICSSLoaderObserver* aObserver,
                                      CORSMode aCORSMode,
                                      ReferrerPolicy aReferrerPolicy,
                                      const nsAString& aIntegrity)
 {
   NS_PRECONDITION(aURL, "Must have a URI to load");
   NS_PRECONDITION(aSheet || aObserver, "Sheet and observer can't both be null");
@@ -2416,20 +2290,25 @@ Loader::InternalLoadNonDocumentSheet(nsI
       rv = PostLoadEvent(aURL, sheet, aObserver, false, nullptr);
     }
     if (aSheet) {
       sheet.swap(*aSheet);
     }
     return rv;
   }
 
-  SheetLoadData* data =
-    new SheetLoadData(this, aURL, sheet, syncLoad,
-                      aUseSystemPrincipal, aCharset, aObserver,
-                      aOriginPrincipal, mDocument);
+  SheetLoadData* data = new SheetLoadData(this,
+                                          aURL,
+                                          sheet,
+                                          syncLoad,
+                                          aUseSystemPrincipal,
+                                          aPreloadEncoding,
+                                          aObserver,
+                                          aOriginPrincipal,
+                                          mDocument);
 
   NS_ADDREF(data);
   rv = LoadSheet(data, state, aIsPreload);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (aSheet) {
     sheet.swap(*aSheet);
   }
--- a/layout/style/Loader.h
+++ b/layout/style/Loader.h
@@ -370,40 +370,34 @@ public:
    * sheet is loaded and marked complete.  This method can be used to load
    * sheets not associated with a document.  This method cannot be used to
    * load user or agent sheets.
    *
    * @param aURL the URL of the sheet to load
    * @param aOriginPrincipal the principal to use for security checks.  This
    *                         can be null to indicate that these checks should
    *                         be skipped.
-   * @param aCharset the encoding to use for converting the sheet data
-   *        from bytes to Unicode.  May be empty to indicate that the
-   *        charset of the CSSLoader's document should be used.  This
-   *        is only used if neither the network transport nor the
-   *        sheet itself indicate an encoding.
    * @param aObserver the observer to notify when the load completes.
    *                  Must not be null.
    * @param [out] aSheet the sheet to load. Note that the sheet may well
    *              not be loaded by the time this method returns.
    */
   nsresult LoadSheet(nsIURI* aURL,
                      nsIPrincipal* aOriginPrincipal,
-                     const nsCString& aCharset,
                      nsICSSLoaderObserver* aObserver,
                      RefPtr<StyleSheet>* aSheet);
 
   /**
    * Same as above, to be used when the caller doesn't care about the
    * not-yet-loaded sheet.
    */
   nsresult LoadSheet(nsIURI* aURL,
                      bool aIsPreload,
                      nsIPrincipal* aOriginPrincipal,
-                     const nsCString& aCharset,
+                     const Encoding* aPreloadEncoding,
                      nsICSSLoaderObserver* aObserver,
                      CORSMode aCORSMode = CORS_NONE,
                      ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Unset,
                      const nsAString& aIntegrity = EmptyString());
 
   /**
    * Stop loading all sheets.  All nsICSSLoaderObservers involved will be
    * notified with NS_BINDING_ABORTED as the status, possibly synchronously.
@@ -472,16 +466,17 @@ public:
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
   // Marks all the sheets at the given URI obsolete, and removes them from the
   // cache.
   nsresult ObsoleteSheet(nsIURI* aURI);
 
 private:
   friend class SheetLoadData;
+  friend class StreamLoader;
 
   nsresult CheckContentPolicy(nsIPrincipal* aSourcePrincipal,
                               nsIURI* aTargetURI,
                               nsISupports* aContext,
                               bool aIsPreload);
 
   // For inline style, the aURI param is null, but the aLinkingContent
   // must be non-null then.  The loader principal must never be null
@@ -515,27 +510,28 @@ private:
   nsresult InsertSheetInDoc(StyleSheet* aSheet,
                             nsIContent* aLinkingContent,
                             nsIDocument* aDocument);
 
   nsresult InsertChildSheet(StyleSheet* aSheet,
                             StyleSheet* aParentSheet,
                             ImportRule* aGeckoParentRule);
 
-  nsresult InternalLoadNonDocumentSheet(nsIURI* aURL,
-                                        bool aIsPreload,
-                                        SheetParsingMode aParsingMode,
-                                        bool aUseSystemPrincipal,
-                                        nsIPrincipal* aOriginPrincipal,
-                                        const nsCString& aCharset,
-                                        RefPtr<StyleSheet>* aSheet,
-                                        nsICSSLoaderObserver* aObserver,
-                                        CORSMode aCORSMode = CORS_NONE,
-                                        ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Unset,
-                                        const nsAString& aIntegrity = EmptyString());
+  nsresult InternalLoadNonDocumentSheet(
+    nsIURI* aURL,
+    bool aIsPreload,
+    SheetParsingMode aParsingMode,
+    bool aUseSystemPrincipal,
+    nsIPrincipal* aOriginPrincipal,
+    const Encoding* aPreloadEncoding,
+    RefPtr<StyleSheet>* aSheet,
+    nsICSSLoaderObserver* aObserver,
+    CORSMode aCORSMode = CORS_NONE,
+    ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Unset,
+    const nsAString& aIntegrity = EmptyString());
 
   // Post a load event for aObserver to be notified about aSheet.  The
   // notification will be sent with status NS_OK unless the load event is
   // canceled at some point (in which case it will be sent with
   // NS_BINDING_ABORTED).  aWasAlternate indicates the state when the load was
   // initiated, not the state at some later time.  aURI should be the URI the
   // sheet was loaded from (may be null for inline sheets).  aElement is the
   // owning element for this sheet.
@@ -552,21 +548,23 @@ private:
   void HandleLoadEvent(SheetLoadData* aEvent);
 
   // Note: LoadSheet is responsible for releasing aLoadData and setting the
   // sheet to complete on failure.
   nsresult LoadSheet(SheetLoadData* aLoadData,
                      StyleSheetState aSheetState,
                      bool aIsPreLoad);
 
-  // Parse the stylesheet in aLoadData.  The sheet data comes from aInput.
-  // Set aCompleted to true if the parse finished, false otherwise (e.g. if the
+  // Parse the stylesheet in aLoadData. The sheet data comes from aUTF16 if
+  // UTF-16 and from aUTF8 if UTF-8.
+  // Sets aCompleted to true if the parse finished, false otherwise (e.g. if the
   // sheet had an @import).  If aCompleted is true when this returns, then
   // ParseSheet also called SheetComplete on aLoadData.
-  nsresult ParseSheet(const nsAString& aInput,
+  nsresult ParseSheet(const nsAString& aUTF16,
+                      Span<const uint8_t> aUTF8,
                       SheetLoadData* aLoadData,
                       bool& aCompleted);
 
   // The load of the sheet in aLoadData is done, one way or another.  Do final
   // cleanup, including releasing aLoadData.
   void SheetComplete(SheetLoadData* aLoadData, nsresult aStatus);
 
   // The guts of SheetComplete.  This may be called recursively on parent datas
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -33,20 +33,22 @@ SERVO_BINDING_FUNC(Servo_Element_HasPseu
 SERVO_BINDING_FUNC(Servo_Element_GetPseudoComputedValues,
                    ServoStyleContextStrong,
                    RawGeckoElementBorrowed node, size_t index)
 SERVO_BINDING_FUNC(Servo_Element_IsDisplayNone,
                    bool,
                    RawGeckoElementBorrowed element)
 
 // Styleset and Stylesheet management
-SERVO_BINDING_FUNC(Servo_StyleSheet_FromUTF8Bytes, RawServoStyleSheetContentsStrong,
+SERVO_BINDING_FUNC(Servo_StyleSheet_FromUTF8Bytes,
+                   RawServoStyleSheetContentsStrong,
                    mozilla::css::Loader* loader,
                    mozilla::ServoStyleSheet* gecko_stylesheet,
-                   const nsACString* data,
+                   const uint8_t* data,
+                   size_t data_len,
                    mozilla::css::SheetParsingMode parsing_mode,
                    RawGeckoURLExtraData* extra_data,
                    uint32_t line_number_offset,
                    nsCompatibility quirks_mode,
                    mozilla::css::LoaderReusableStyleSheets* reusable_sheets)
 SERVO_BINDING_FUNC(Servo_StyleSheet_Empty, RawServoStyleSheetContentsStrong,
                    mozilla::css::SheetParsingMode parsing_mode)
 SERVO_BINDING_FUNC(Servo_StyleSheet_HasRules, bool,
--- a/layout/style/ServoStyleSheet.cpp
+++ b/layout/style/ServoStyleSheet.cpp
@@ -190,34 +190,38 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 bool
 ServoStyleSheet::HasRules() const
 {
   return Servo_StyleSheet_HasRules(Inner()->mContents);
 }
 
 nsresult
 ServoStyleSheet::ParseSheet(css::Loader* aLoader,
-                            const nsAString& aInput,
+                            Span<const uint8_t> aInput,
                             nsIURI* aSheetURI,
                             nsIURI* aBaseURI,
                             nsIPrincipal* aSheetPrincipal,
                             uint32_t aLineNumber,
                             nsCompatibility aCompatMode,
                             css::LoaderReusableStyleSheets* aReusableSheets)
 {
   MOZ_ASSERT(!mMedia || mMedia->IsServo());
   RefPtr<URLExtraData> extraData =
     new URLExtraData(aBaseURI, aSheetURI, aSheetPrincipal);
 
-  NS_ConvertUTF16toUTF8 input(aInput);
-  Inner()->mContents =
-    Servo_StyleSheet_FromUTF8Bytes(
-        aLoader, this, &input, mParsingMode, extraData,
-        aLineNumber, aCompatMode, aReusableSheets
-    ).Consume();
+  Inner()->mContents = Servo_StyleSheet_FromUTF8Bytes(aLoader,
+                                                      this,
+                                                      aInput.Elements(),
+                                                      aInput.Length(),
+                                                      mParsingMode,
+                                                      extraData,
+                                                      aLineNumber,
+                                                      aCompatMode,
+                                                      aReusableSheets)
+                         .Consume();
 
   Inner()->mURLData = extraData.forget();
   return NS_OK;
 }
 
 nsresult
 ServoStyleSheet::ReparseSheet(const nsAString& aInput)
 {
@@ -286,19 +290,24 @@ ServoStyleSheet::ReparseSheet(const nsAS
         // If detached, don't process any more rules.
         break;
       }
     }
   }
 
   DropRuleList();
 
-  nsresult rv = ParseSheet(loader, aInput, mInner->mSheetURI, mInner->mBaseURI,
-                           mInner->mPrincipal, lineNumber,
-                           eCompatibility_FullStandards, &reusableSheets);
+  nsresult rv = ParseSheet(loader,
+                           NS_ConvertUTF16toUTF8(aInput),
+                           mInner->mSheetURI,
+                           mInner->mBaseURI,
+                           mInner->mPrincipal,
+                           lineNumber,
+                           eCompatibility_FullStandards,
+                           &reusableSheets);
   DidDirty();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Notify mDocument that all our new rules are added.
   if (mDocument) {
     // Get the rule list (which will need to be regenerated after ParseSheet).
     ServoCSSRuleList* ruleList = GetCssRulesInternal();
     MOZ_ASSERT(ruleList);
--- a/layout/style/ServoStyleSheet.h
+++ b/layout/style/ServoStyleSheet.h
@@ -79,24 +79,25 @@ public:
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServoStyleSheet, StyleSheet)
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_SERVO_STYLE_SHEET_IMPL_CID)
 
   bool HasRules() const;
 
-  MOZ_MUST_USE nsresult ParseSheet(css::Loader* aLoader,
-                                   const nsAString& aInput,
-                                   nsIURI* aSheetURI,
-                                   nsIURI* aBaseURI,
-                                   nsIPrincipal* aSheetPrincipal,
-                                   uint32_t aLineNumber,
-                                   nsCompatibility aCompatMode,
-                                   css::LoaderReusableStyleSheets* aReusableSheets = nullptr);
+  MOZ_MUST_USE nsresult
+  ParseSheet(css::Loader* aLoader,
+             Span<const uint8_t> aInput,
+             nsIURI* aSheetURI,
+             nsIURI* aBaseURI,
+             nsIPrincipal* aSheetPrincipal,
+             uint32_t aLineNumber,
+             nsCompatibility aCompatMode,
+             css::LoaderReusableStyleSheets* aReusableSheets = nullptr);
 
   nsresult ReparseSheet(const nsAString& aInput);
 
   const RawServoStyleSheetContents* RawContents() const {
     return Inner()->mContents;
   }
 
   void SetContentsForImport(const RawServoStyleSheetContents* aContents) {
new file mode 100644
--- /dev/null
+++ b/layout/style/SheetLoadData.h
@@ -0,0 +1,183 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: ft=cpp tw=78 sw=2 et ts=2
+ *
+ * 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/. */
+
+#ifndef mozilla_css_SheetLoadData_h
+#define mozilla_css_SheetLoadData_h
+
+#include "nsIUnicharStreamLoader.h"
+#include "nsIThreadInternal.h"
+
+namespace mozilla {
+namespace css {
+
+/*********************************************
+ * Data needed to properly load a stylesheet *
+ *********************************************/
+
+static_assert(eAuthorSheetFeatures == 0 && eUserSheetFeatures == 1 &&
+                eAgentSheetFeatures == 2,
+              "sheet parsing mode constants won't fit "
+              "in SheetLoadData::mParsingMode");
+
+class SheetLoadData final
+  : public nsIRunnable
+  , public nsIUnicharStreamLoaderObserver
+  , public nsIThreadObserver
+{
+protected:
+  virtual ~SheetLoadData(void);
+
+public:
+  // Data for loading a sheet linked from a document
+  SheetLoadData(Loader* aLoader,
+                const nsAString& aTitle,
+                nsIURI* aURI,
+                StyleSheet* aSheet,
+                nsIStyleSheetLinkingElement* aOwningElement,
+                bool aIsAlternate,
+                nsICSSLoaderObserver* aObserver,
+                nsIPrincipal* aLoaderPrincipal,
+                nsINode* aRequestingNode);
+
+  // Data for loading a sheet linked from an @import rule
+  SheetLoadData(Loader* aLoader,
+                nsIURI* aURI,
+                StyleSheet* aSheet,
+                SheetLoadData* aParentData,
+                nsICSSLoaderObserver* aObserver,
+                nsIPrincipal* aLoaderPrincipal,
+                nsINode* aRequestingNode);
+
+  // Data for loading a non-document sheet
+  SheetLoadData(Loader* aLoader,
+                nsIURI* aURI,
+                StyleSheet* aSheet,
+                bool aSyncLoad,
+                bool aUseSystemPrincipal,
+                const Encoding* aPreloadEncoding,
+                nsICSSLoaderObserver* aObserver,
+                nsIPrincipal* aLoaderPrincipal,
+                nsINode* aRequestingNode);
+
+  already_AddRefed<nsIURI> GetReferrerURI();
+
+  void ScheduleLoadEventIfNeeded(nsresult aStatus);
+
+  NotNull<const Encoding*> DetermineNonBOMEncoding(nsACString const& aSegment,
+                                                   nsIChannel* aChannel);
+
+  nsresult VerifySheetReadyToParse(nsresult aStatus,
+                                   const nsACString& aBytes,
+                                   nsIChannel* aChannel);
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIRUNNABLE
+  NS_DECL_NSITHREADOBSERVER
+  NS_DECL_NSIUNICHARSTREAMLOADEROBSERVER
+
+  // Hold a ref to the CSSLoader so we can call back to it to let it
+  // know the load finished
+  RefPtr<Loader> mLoader;
+
+  // Title needed to pull datas out of the pending datas table when
+  // the preferred title is changed
+  nsString mTitle;
+
+  // The encoding we decided to use for the sheet
+  const Encoding* mEncoding;
+
+  // URI we're loading.  Null for inline sheets
+  nsCOMPtr<nsIURI> mURI;
+
+  // Should be 1 for non-inline sheets.
+  uint32_t mLineNumber;
+
+  // The sheet we're loading data for
+  RefPtr<StyleSheet> mSheet;
+
+  // Linked list of datas for the same URI as us
+  SheetLoadData* mNext; // strong ref
+
+  // Load data for the sheet that @import-ed us if we were @import-ed
+  // during the parse
+  RefPtr<SheetLoadData> mParentData;
+
+  // Number of sheets we @import-ed that are still loading
+  uint32_t mPendingChildren;
+
+  // mSyncLoad is true when the load needs to be synchronous -- right
+  // now only for LoadSheetSync and children of sync loads.
+  bool mSyncLoad : 1;
+
+  // mIsNonDocumentSheet is true if the load was triggered by LoadSheetSync or
+  // LoadSheet or an @import from such a sheet.  Non-document sheet loads can
+  // proceed even if we have no document.
+  bool mIsNonDocumentSheet : 1;
+
+  // mIsLoading is true from the moment we are placed in the loader's
+  // "loading datas" table (right after the async channel is opened)
+  // to the moment we are removed from said table (due to the load
+  // completing or being cancelled).
+  bool mIsLoading : 1;
+
+  // mIsCancelled is set to true when a sheet load is stopped by
+  // Stop() or StopLoadingSheet() (which was removed in Bug 556446).
+  // SheetLoadData::OnStreamComplete() checks this to avoid parsing
+  // sheets that have been cancelled and such.
+  bool mIsCancelled : 1;
+
+  // mMustNotify is true if the load data is being loaded async and
+  // the original function call that started the load has returned.
+  // This applies only to observer notifications; load/error events
+  // are fired for any SheetLoadData that has a non-null
+  // mOwningElement.
+  bool mMustNotify : 1;
+
+  // mWasAlternate is true if the sheet was an alternate when the load data was
+  // created.
+  bool mWasAlternate : 1;
+
+  // mUseSystemPrincipal is true if the system principal should be used for
+  // this sheet, no matter what the channel principal is.  Only true for sync
+  // loads.
+  bool mUseSystemPrincipal : 1;
+
+  // If true, this SheetLoadData is being used as a way to handle
+  // async observer notification for an already-complete sheet.
+  bool mSheetAlreadyComplete : 1;
+
+  // This is the element that imported the sheet.  Needed to get the
+  // charset set on it and to fire load/error events.
+  nsCOMPtr<nsIStyleSheetLinkingElement> mOwningElement;
+
+  // The observer that wishes to be notified of load completion
+  nsCOMPtr<nsICSSLoaderObserver> mObserver;
+
+  // The principal that identifies who started loading us.
+  nsCOMPtr<nsIPrincipal> mLoaderPrincipal;
+
+  // The node that identifies who started loading us.
+  nsCOMPtr<nsINode> mRequestingNode;
+
+  // The encoding to use for preloading Must be empty if mOwningElement
+  // is non-null.
+  const Encoding* mPreloadEncoding;
+
+  // The status our load ended up with; this determines whether we
+  // should fire error events or load events.  This gets initialized
+  // by ScheduleLoadEventIfNeeded, and is only used after that has
+  // been called.
+  MOZ_INIT_OUTSIDE_CTOR nsresult mStatus;
+
+private:
+  void FireLoadEvent(nsIThreadInternal* aThread);
+};
+
+} // namespace css
+} // namespace mozilla
+
+#endif // mozilla_css_SheetLoadData_h
new file mode 100644
--- /dev/null
+++ b/layout/style/StreamLoader.cpp
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/css/StreamLoader.h"
+
+#include "mozilla/IntegerTypeTraits.h"
+#include "mozilla/Encoding.h"
+#include "nsIChannel.h"
+#include "nsIInputStream.h"
+
+using namespace mozilla;
+
+namespace mozilla {
+namespace css {
+
+StreamLoader::StreamLoader(mozilla::css::SheetLoadData* aSheetLoadData)
+  : mSheetLoadData(aSheetLoadData)
+  , mStatus(NS_OK)
+{
+  MOZ_ASSERT(!aSheetLoadData->mSheet->IsGecko());
+}
+
+StreamLoader::~StreamLoader()
+{
+}
+
+NS_IMPL_ISUPPORTS(StreamLoader, nsIStreamListener)
+
+/* nsIRequestObserver implementation */
+NS_IMETHODIMP
+StreamLoader::OnStartRequest(nsIRequest* aRequest, nsISupports*)
+{
+  // It's kinda bad to let Web content send a number that results
+  // in a potentially large allocation directly, but efficiency of
+  // compression bombs is so great that it doesn't make much sense
+  // to require a site to send one before going ahead and allocating.
+  nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+  if (channel) {
+    int64_t length;
+    nsresult rv = channel->GetContentLength(&length);
+    if (NS_SUCCEEDED(rv) && length > 0) {
+      if (length > MaxValue<nsACString::size_type>::value) {
+        return (mStatus = NS_ERROR_OUT_OF_MEMORY);
+      }
+      if (!mBytes.SetCapacity(length, mozilla::fallible_t())) {
+        return (mStatus = NS_ERROR_OUT_OF_MEMORY);
+      }
+    }
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+StreamLoader::OnStopRequest(nsIRequest* aRequest,
+                            nsISupports* aContext,
+                            nsresult aStatus)
+{
+  // Decoded data
+  nsCString utf8String;
+  // How many bytes of decoded data to skip (3 when skipping UTF-8 BOM needed,
+  // 0 otherwise)
+  size_t skip = 0;
+
+  const Encoding* encoding;
+
+  nsresult rv = NS_OK;
+
+  {
+    // Hold the nsStringBuffer for the bytes from the stack to ensure release
+    // no matter which return branch is taken.
+    nsCString bytes(mBytes);
+    mBytes.Truncate();
+
+    nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+
+    if (NS_FAILED(mStatus)) {
+      mSheetLoadData->VerifySheetReadyToParse(mStatus, EmptyCString(), channel);
+      return mStatus;
+    }
+
+    nsresult rv =
+      mSheetLoadData->VerifySheetReadyToParse(aStatus, bytes, channel);
+    if (rv != NS_OK_PARSE_SHEET) {
+      return rv;
+    }
+
+    rv = NS_OK;
+
+    size_t bomLength;
+    Tie(encoding, bomLength) = Encoding::ForBOM(bytes);
+    if (!encoding) {
+      // No BOM
+      encoding = mSheetLoadData->DetermineNonBOMEncoding(bytes, channel);
+
+      rv = encoding->DecodeWithoutBOMHandling(bytes, utf8String);
+    } else if (encoding == UTF_8_ENCODING) {
+      // UTF-8 BOM; handling this manually because mozilla::Encoding
+      // can't handle this without copying with C++ types and uses
+      // infallible allocation with Rust types (which could avoid
+      // the copy).
+
+      // First, chop off the BOM.
+      auto tail = Span<const uint8_t>(bytes).From(bomLength);
+      size_t upTo = Encoding::UTF8ValidUpTo(tail);
+      if (upTo == tail.Length()) {
+        // No need to copy
+        skip = bomLength;
+        utf8String.Assign(bytes);
+      } else {
+        rv = encoding->DecodeWithoutBOMHandling(tail, utf8String, upTo);
+      }
+    } else {
+      // UTF-16LE or UTF-16BE
+      rv = encoding->DecodeWithBOMRemoval(bytes, utf8String);
+    }
+  } // run destructor for `bytes`
+
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // For reasons I don't understand, factoring the below lines into
+  // a method on SheetLoadData resulted in a linker error. Hence,
+  // accessing fields of mSheetLoadData from here.
+  mSheetLoadData->mEncoding = encoding;
+  bool dummy;
+  return mSheetLoadData->mLoader->ParseSheet(
+    EmptyString(),
+    Span<const uint8_t>(utf8String).From(skip),
+    mSheetLoadData,
+    dummy);
+}
+
+/* nsIStreamListener implementation */
+NS_IMETHODIMP
+StreamLoader::OnDataAvailable(nsIRequest*,
+                              nsISupports*,
+                              nsIInputStream* aInputStream,
+                              uint64_t,
+                              uint32_t aCount)
+{
+  if (NS_FAILED(mStatus)) {
+    return mStatus;
+  }
+  uint32_t dummy;
+  return aInputStream->ReadSegments(WriteSegmentFun, this, aCount, &dummy);
+}
+
+nsresult
+StreamLoader::WriteSegmentFun(nsIInputStream*,
+                              void* aClosure,
+                              const char* aSegment,
+                              uint32_t,
+                              uint32_t aCount,
+                              uint32_t* aWriteCount)
+{
+  StreamLoader* self = static_cast<StreamLoader*>(aClosure);
+  if (NS_FAILED(self->mStatus)) {
+    return self->mStatus;
+  }
+  if (!self->mBytes.Append(aSegment, aCount, mozilla::fallible_t())) {
+    self->mBytes.Truncate();
+    return (self->mStatus = NS_ERROR_OUT_OF_MEMORY);
+  }
+  *aWriteCount = aCount;
+  return NS_OK;
+}
+
+} // namespace css
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/layout/style/StreamLoader.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef mozilla_css_StreamLoader_h
+#define mozilla_css_StreamLoader_h
+
+#include "nsString.h"
+#include "mozilla/css/SheetLoadData.h"
+
+class nsIInputStream;
+
+namespace mozilla {
+namespace css {
+
+class StreamLoader : public nsIStreamListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIREQUESTOBSERVER
+  NS_DECL_NSISTREAMLISTENER
+
+  explicit StreamLoader(mozilla::css::SheetLoadData* aSheetLoadData);
+
+private:
+  virtual ~StreamLoader();
+
+  /**
+   * callback method used for ReadSegments
+   */
+  static nsresult WriteSegmentFun(nsIInputStream*,
+                                  void*,
+                                  const char*,
+                                  uint32_t,
+                                  uint32_t,
+                                  uint32_t*);
+
+  RefPtr<mozilla::css::SheetLoadData> mSheetLoadData;
+  nsCString mBytes;
+  nsresult mStatus;
+};
+
+} // namespace css
+} // namespace mozilla
+
+#endif // mozilla_css_StreamLoader_h
--- a/layout/style/moz.build
+++ b/layout/style/moz.build
@@ -165,17 +165,19 @@ EXPORTS.mozilla.css += [
     'Declaration.h',
     'ErrorReporter.h',
     'GroupRule.h',
     'ImageLoader.h',
     'ImportRule.h',
     'Loader.h',
     'NameSpaceRule.h',
     'Rule.h',
+    'SheetLoadData.h',
     'SheetParsingMode.h',
+    'StreamLoader.h',
     'StyleRule.h',
     'URLMatchingFunction.h',
 ]
 
 UNIFIED_SOURCES += [
     'AnimationCollection.cpp',
     'BindingStyleRule.cpp',
     'CounterStyleManager.cpp',
@@ -261,16 +263,17 @@ UNIFIED_SOURCES += [
     'ServoNamespaceRule.cpp',
     'ServoPageRule.cpp',
     'ServoSpecifiedValues.cpp',
     'ServoStyleContext.cpp',
     'ServoStyleRule.cpp',
     'ServoStyleSet.cpp',
     'ServoStyleSheet.cpp',
     'ServoSupportsRule.cpp',
+    'StreamLoader.cpp',
     'StyleAnimationValue.cpp',
     'StylePrefs.cpp',
     'StyleRule.cpp',
     'StyleSheet.cpp',
     'URLExtraData.cpp',
 ]
 
 # - nsLayoutStylesheetCache.cpp needs to be built separately because it uses
--- a/layout/style/nsLayoutStylesheetCache.cpp
+++ b/layout/style/nsLayoutStylesheetCache.cpp
@@ -902,17 +902,17 @@ nsLayoutStylesheetCache::BuildPreference
   NS_NewURI(getter_AddRefs(uri), "about:PreferenceStyleSheet", nullptr);
   MOZ_ASSERT(uri, "URI creation shouldn't fail");
 
   sheet->SetURIs(uri, uri, uri);
   sheet->SetComplete();
 
   static const uint32_t kPreallocSize = 1024;
 
-  nsString sheetText;
+  nsCString sheetText;
   sheetText.SetCapacity(kPreallocSize);
 
 #define NS_GET_R_G_B(color_) \
   NS_GET_R(color_), NS_GET_G(color_), NS_GET_B(color_)
 
   sheetText.AppendLiteral(
       "@namespace url(http://www.w3.org/1999/xhtml);\n"
       "@namespace svg url(http://www.w3.org/2000/svg);\n");
@@ -985,23 +985,22 @@ nsLayoutStylesheetCache::BuildPreference
         NS_GET_R_G_B(focusBG));
   }
 
   NS_ASSERTION(sheetText.Length() <= kPreallocSize,
                "kPreallocSize should be big enough to build preference style "
                "sheet without reallocation");
 
   if (sheet->IsGecko()) {
-    sheet->AsGecko()->ReparseSheet(sheetText);
+    sheet->AsGecko()->ReparseSheet(NS_ConvertUTF8toUTF16(sheetText));
   } else {
     ServoStyleSheet* servoSheet = sheet->AsServo();
     // NB: The pref sheet never has @import rules.
-    nsresult rv =
-      servoSheet->ParseSheet(nullptr, sheetText, uri, uri, nullptr, 0,
-                             eCompatibility_FullStandards);
+    nsresult rv = servoSheet->ParseSheet(
+      nullptr, sheetText, uri, uri, nullptr, 0, eCompatibility_FullStandards);
     // Parsing the about:PreferenceStyleSheet URI can only fail on OOM. If we
     // are OOM before we parsed any documents we might as well abort.
     MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
   }
 
 #undef NS_GET_R_G_B
 }
 
--- a/layout/style/test/gtest/StyloParsingBench.cpp
+++ b/layout/style/test/gtest/StyloParsingBench.cpp
@@ -5,41 +5,47 @@
 
 #include "gtest/gtest.h"
 #include "gtest/MozGTestBench.h"
 #include "nsString.h"
 #include "ExampleStylesheet.h"
 #include "ServoBindings.h"
 #include "NullPrincipalURI.h"
 #include "nsCSSParser.h"
+#include "mozilla/Encoding.h"
 
 using namespace mozilla;
 using namespace mozilla::css;
 using namespace mozilla::dom;
 using namespace mozilla::net;
 
 #define PARSING_REPETITIONS 20
 #define SETPROPERTY_REPETITIONS (1000 * 1000)
 #define GETPROPERTY_REPETITIONS (1000 * 1000)
 
 #ifdef MOZ_STYLO
 
 static void ServoParsingBench() {
-  NS_NAMED_LITERAL_CSTRING(css_, EXAMPLE_STYLESHEET);
-  const nsACString& css = css_;
-  ASSERT_TRUE(IsUTF8(css));
+  auto css = AsBytes(MakeStringSpan(EXAMPLE_STYLESHEET));
+  ASSERT_EQ(Encoding::UTF8ValidUpTo(css), css.Length());
 
   RefPtr<URLExtraData> data = new URLExtraData(
     NullPrincipalURI::Create(), nullptr, NullPrincipal::Create());
   for (int i = 0; i < PARSING_REPETITIONS; i++) {
     RefPtr<RawServoStyleSheetContents> stylesheet =
-      Servo_StyleSheet_FromUTF8Bytes(
-        nullptr, nullptr, &css, eAuthorSheetFeatures,
-        data, 0, eCompatibility_FullStandards, nullptr
-      ).Consume();
+      Servo_StyleSheet_FromUTF8Bytes(nullptr,
+                                     nullptr,
+                                     css.Elements(),
+                                     css.Length(),
+                                     eAuthorSheetFeatures,
+                                     data,
+                                     0,
+                                     eCompatibility_FullStandards,
+                                     nullptr)
+        .Consume();
   }
 }
 
 MOZ_GTEST_BENCH(Stylo, Servo_StyleSheet_FromUTF8Bytes_Bench, ServoParsingBench);
 
 #endif
 
 
--- a/parser/html/nsHtml5TreeOpExecutor.cpp
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
@@ -979,17 +979,20 @@ nsHtml5TreeOpExecutor::PreloadStyle(cons
 
   mozilla::net::ReferrerPolicy referrerPolicy = mSpeculationReferrerPolicy;
   mozilla::net::ReferrerPolicy styleReferrerPolicy =
     mozilla::net::AttributeReferrerPolicyFromString(aReferrerPolicy);
   if (styleReferrerPolicy != mozilla::net::RP_Unset) {
     referrerPolicy = styleReferrerPolicy;
   }
 
-  mDocument->PreloadStyle(uri, aCharset, aCrossOrigin, referrerPolicy,
+  mDocument->PreloadStyle(uri,
+                          Encoding::ForLabel(aCharset),
+                          aCrossOrigin,
+                          referrerPolicy,
                           aIntegrity);
 }
 
 void
 nsHtml5TreeOpExecutor::PreloadImage(const nsAString& aURL,
                                     const nsAString& aCrossOrigin,
                                     const nsAString& aSrcset,
                                     const nsAString& aSizes,
--- a/xpcom/base/ErrorList.py
+++ b/xpcom/base/ErrorList.py
@@ -248,17 +248,17 @@ with modules["NETWORK"]:
     # These error codes are commonly passed through callback methods to indicate
     # the status of some requested async request.
     #
     # For example, see nsIRequestObserver::onStopRequest.
 
     # The async request completed successfully.
     errors["NS_BINDING_SUCCEEDED"] = errors["NS_OK"]
 
-    # The async request failed for some unknown reason. 
+    # The async request failed for some unknown reason.
     errors["NS_BINDING_FAILED"] = FAILURE(1)
     # The async request failed because it was aborted by some user action.
     errors["NS_BINDING_ABORTED"] = FAILURE(2)
     # The async request has been "redirected" to a different async request.
     # (e.g., an HTTP redirect occurred).
     #
     # This error code is used with load groups to notify the load group observer
     # when a request in the load group is redirected to another request.
@@ -309,17 +309,17 @@ with modules["NETWORK"]:
 
     # The connection is already established.  XXX unused - consider removing.
     errors["NS_ERROR_ALREADY_CONNECTED"] = FAILURE(11)
     # The connection does not exist.  XXX unused - consider removing.
     errors["NS_ERROR_NOT_CONNECTED"] = FAILURE(12)
     # The connection attempt failed, for example, because no server was
     # listening at specified host:port.
     errors["NS_ERROR_CONNECTION_REFUSED"] = FAILURE(13)
-    # The connection was lost due to a timeout error. 
+    # The connection was lost due to a timeout error.
     errors["NS_ERROR_NET_TIMEOUT"] = FAILURE(14)
     # The requested action could not be completed while the networking library
     # is in the offline state.
     errors["NS_ERROR_OFFLINE"] = FAILURE(16)
     # The requested action was prohibited because it would have caused the
     # networking library to establish a connection to an unsafe or otherwise
     # banned port.
     errors["NS_ERROR_PORT_ACCESS_NOT_ALLOWED"] = FAILURE(19)
@@ -334,17 +334,17 @@ with modules["NETWORK"]:
     # HTTP/2 detected invalid TLS configuration
     errors["NS_ERROR_NET_INADEQUATE_SECURITY"] = FAILURE(82)
 
     # XXX really need to better rationalize these error codes.  are consumers of
     # necko really expected to know how to discern the meaning of these??
     # This request is not resumable, but it was tried to resume it, or to
     # request resume-specific data.
     errors["NS_ERROR_NOT_RESUMABLE"] = FAILURE(25)
-    # The request failed as a result of a detected redirection loop. 
+    # The request failed as a result of a detected redirection loop.
     errors["NS_ERROR_REDIRECT_LOOP"] = FAILURE(31)
     # It was attempted to resume the request, but the entity has changed in the
     # meantime.
     errors["NS_ERROR_ENTITY_CHANGED"] = FAILURE(32)
     # The request failed because the content type returned by the server was not
     # a type expected by the channel (for nested channels such as the JAR
     # channel).
     errors["NS_ERROR_UNSAFE_CONTENT_TYPE"] = FAILURE(74)
@@ -469,16 +469,18 @@ with modules["PLUGINS"]:
 
 
 # =======================================================================
 # 8: NS_ERROR_MODULE_LAYOUT
 # =======================================================================
 with modules["LAYOUT"]:
     # Return code for nsITableLayout
     errors["NS_TABLELAYOUT_CELL_NOT_FOUND"] = SUCCESS(0)
+    # Return code for SheetLoadData::VerifySheetReadyToParse
+    errors["NS_OK_PARSE_SHEET"] = SUCCESS(1)
     # Return code for nsFrame::GetNextPrevLineFromeBlockFrame
     errors["NS_POSITION_BEFORE_TABLE"] = SUCCESS(3)
     # Return codes for nsPresState::GetProperty()
     # Returned if the property exists
     errors["NS_STATE_PROPERTY_EXISTS"] = errors["NS_OK"]
     # Returned if the property does not exist
     errors["NS_STATE_PROPERTY_NOT_THERE"] = SUCCESS(5)