Bug 1354989 - Avoid pivoting via UTF-16 when loading CSS in the Stylo mode. draft
authorHenri Sivonen <hsivonen@hsivonen.fi>
Tue, 29 Aug 2017 16:01:42 +0300
changeset 657577 86f1679f4084e17394f05f46918cbf417a244033
parent 655883 f3a4f4402577200e8c3ab5706ff9cb4224c67d33
child 729462 a950b8ee2d090628212a6e3b33af150cd6c1d001
push id77560
push userbmo:hsivonen@hsivonen.fi
push dateFri, 01 Sep 2017 16:49:21 +0000
bugs1354989
milestone57.0a1
Bug 1354989 - Avoid pivoting via UTF-16 when loading CSS in the Stylo mode. 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
@@ -9979,30 +9979,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
@@ -2837,18 +2837,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)