Bug 1386840: Defer loading and don't block rendering for non-matching stylesheets. r?bz,heycam draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Tue, 24 Apr 2018 19:17:33 +0200
changeset 787281 6b1f9d371f191a2ece4aa91913bfb10ea4a48569
parent 787280 c64cda1612c6b0c778043e1ba80409a031b8732d
push id107707
push userbmo:emilio@crisal.io
push dateTue, 24 Apr 2018 18:07:12 +0000
reviewersbz, heycam
bugs1386840
milestone61.0a1
Bug 1386840: Defer loading and don't block rendering for non-matching stylesheets. r?bz,heycam MozReview-Commit-ID: 24UJZDooGmn
dom/base/nsContentSink.cpp
dom/base/nsIStyleSheetLinkingElement.h
dom/xbl/nsXBLResourceLoader.cpp
dom/xml/nsXMLContentSink.cpp
dom/xslt/xslt/txMozillaXMLOutput.cpp
dom/xul/XULDocument.cpp
editor/libeditor/HTMLEditor.cpp
layout/style/FontFaceSet.cpp
layout/style/Loader.cpp
layout/style/Loader.h
layout/style/PreloadedStyleSheet.cpp
layout/style/SheetLoadData.h
--- a/dom/base/nsContentSink.cpp
+++ b/dom/base/nsContentSink.cpp
@@ -229,22 +229,22 @@ nsContentSink::Init(nsIDocument* aDoc,
     FavorPerformanceHint(!mDynamicLowerValue, 0);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsContentSink::StyleSheetLoaded(StyleSheet* aSheet,
-                                bool aWasAlternate,
+                                bool aWasDeferred,
                                 nsresult aStatus)
 {
-  NS_ASSERTION(!mRunsToCompletion, "How come a fragment parser observed sheets?");
-  if (!aWasAlternate) {
-    NS_ASSERTION(mPendingSheetCount > 0, "How'd that happen?");
+  MOZ_ASSERT(!mRunsToCompletion, "How come a fragment parser observed sheets?");
+  if (!aWasDeferred) {
+    MOZ_ASSERT(mPendingSheetCount > 0, "How'd that happen?");
     --mPendingSheetCount;
 
     if (mPendingSheetCount == 0 &&
         (mDeferredLayoutStart || mDeferredFlushTags)) {
       if (mDeferredFlushTags) {
         FlushTags();
       }
       if (mDeferredLayoutStart) {
--- a/dom/base/nsIStyleSheetLinkingElement.h
+++ b/dom/base/nsIStyleSheetLinkingElement.h
@@ -33,45 +33,54 @@ public:
   };
 
   enum class Completed
   {
     Yes,
     No,
   };
 
+  enum class MediaMatched
+  {
+    Yes,
+    No,
+  };
+
   struct Update
   {
   private:
     bool mWillNotify;
     bool mIsAlternate;
+    bool mMediaMatched;
 
   public:
     Update()
       : mWillNotify(false)
       , mIsAlternate(false)
+      , mMediaMatched(false)
     { }
 
-    Update(Completed aCompleted, IsAlternate aIsAlternate)
+    Update(Completed aCompleted, IsAlternate aIsAlternate, MediaMatched aMediaMatched)
       : mWillNotify(aCompleted == Completed::No)
       , mIsAlternate(aIsAlternate == IsAlternate::Yes)
+      , mMediaMatched(aMediaMatched == MediaMatched::Yes)
     { }
 
     bool WillNotify() const
     {
       return mWillNotify;
     }
 
     bool ShouldBlock() const
     {
       if (!mWillNotify) {
         return false;
       }
 
-      return !mIsAlternate;
+      return !mIsAlternate && mMediaMatched;
     }
   };
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISTYLESHEETLINKINGELEMENT_IID)
 
   /**
    * Used to make the association between a style sheet and
    * the element that linked it to the document.
--- a/dom/xbl/nsXBLResourceLoader.cpp
+++ b/dom/xbl/nsXBLResourceLoader.cpp
@@ -159,17 +159,17 @@ nsXBLResourceLoader::LoadResources(nsICo
   mResourceList = nullptr;
 
   return mPendingSheets == 0;
 }
 
 // nsICSSLoaderObserver
 NS_IMETHODIMP
 nsXBLResourceLoader::StyleSheetLoaded(StyleSheet* aSheet,
-                                      bool aWasAlternate,
+                                      bool aWasDeferred,
                                       nsresult aStatus)
 {
   if (!mResources) {
     // Our resources got destroyed -- just bail out
     return NS_OK;
   }
 
   mResources->AppendStyleSheet(aSheet);
--- a/dom/xml/nsXMLContentSink.cpp
+++ b/dom/xml/nsXMLContentSink.cpp
@@ -420,21 +420,21 @@ nsXMLContentSink::OnTransformDone(nsresu
 
   DropParserAndPerfHint();
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXMLContentSink::StyleSheetLoaded(StyleSheet* aSheet,
-                                   bool aWasAlternate,
+                                   bool aWasDeferred,
                                    nsresult aStatus)
 {
   if (!mPrettyPrinting) {
-    return nsContentSink::StyleSheetLoaded(aSheet, aWasAlternate, aStatus);
+    return nsContentSink::StyleSheetLoaded(aSheet, aWasDeferred, aStatus);
   }
 
   if (!mDocument->CSSLoader()->HasPendingLoads()) {
     mDocument->CSSLoader()->RemoveObserver(this);
     StartLayout(false);
     ScrollToRef();
   }
 
--- a/dom/xslt/xslt/txMozillaXMLOutput.cpp
+++ b/dom/xslt/xslt/txMozillaXMLOutput.cpp
@@ -969,28 +969,28 @@ txTransformNotifier::ScriptEvaluated(nsr
         SignalTransformEnd();
     }
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 txTransformNotifier::StyleSheetLoaded(StyleSheet* aSheet,
-                                      bool aWasAlternate,
+                                      bool aWasDeferred,
                                       nsresult aStatus)
 {
     if (mPendingStylesheetCount == 0) {
         // We weren't waiting on this stylesheet anyway.  This can happen if
         // SignalTransformEnd got called with an error aResult.  See
         // http://bugzilla.mozilla.org/show_bug.cgi?id=215465.
         return NS_OK;
     }
 
     // We're never waiting for alternate stylesheets
-    if (!aWasAlternate) {
+    if (!aWasDeferred) {
         --mPendingStylesheetCount;
         SignalTransformEnd();
     }
 
     return NS_OK;
 }
 
 void
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -2874,24 +2874,23 @@ XULDocument::DoneWalking()
         }
     }
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 XULDocument::StyleSheetLoaded(StyleSheet* aSheet,
-                              bool aWasAlternate,
+                              bool aWasDeferred,
                               nsresult aStatus)
 {
-    if (!aWasAlternate) {
+    if (!aWasDeferred) {
         // Don't care about when alternate sheets finish loading
-
-        NS_ASSERTION(mPendingSheets > 0,
-            "Unexpected StyleSheetLoaded notification");
+        MOZ_ASSERT(mPendingSheets > 0,
+                   "Unexpected StyleSheetLoaded notification");
 
         --mPendingSheets;
 
         if (!mStillWalking && mPendingSheets == 0) {
             return DoneWalking();
         }
     }
 
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -3435,17 +3435,17 @@ HTMLEditor::DebugUnitTests(int32_t* outN
   return NS_OK;
 #else
   return NS_ERROR_NOT_IMPLEMENTED;
 #endif
 }
 
 NS_IMETHODIMP
 HTMLEditor::StyleSheetLoaded(StyleSheet* aSheet,
-                             bool aWasAlternate,
+                             bool aWasDeferred,
                              nsresult aStatus)
 {
   AutoPlaceholderBatch batchIt(this);
 
   if (!mLastStyleSheetURL.IsEmpty()) {
     RemoveStyleSheetWithTransaction(mLastStyleSheetURL);
   }
 
--- a/layout/style/FontFaceSet.cpp
+++ b/layout/style/FontFaceSet.cpp
@@ -1819,17 +1819,17 @@ FontFaceSet::PrefEnabled()
   }
   return enabled;
 }
 
 // nsICSSLoaderObserver
 
 NS_IMETHODIMP
 FontFaceSet::StyleSheetLoaded(StyleSheet* aSheet,
-                              bool aWasAlternate,
+                              bool aWasDeferred,
                               nsresult aStatus)
 {
   CheckLoadingFinished();
   return NS_OK;
 }
 
 void
 FontFaceSet::FlushUserFontSet()
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -140,16 +140,17 @@ NS_IMPL_ISUPPORTS(SheetLoadData, nsIRunn
                   nsIThreadObserver)
 
 SheetLoadData::SheetLoadData(Loader* aLoader,
                              const nsAString& aTitle,
                              nsIURI* aURI,
                              StyleSheet* aSheet,
                              nsIStyleSheetLinkingElement* aOwningElement,
                              bool aIsAlternate,
+                             bool aMediaMatches,
                              nsICSSLoaderObserver* aObserver,
                              nsIPrincipal* aLoaderPrincipal,
                              nsINode* aRequestingNode)
   : mLoader(aLoader)
   , mTitle(aTitle)
   , mEncoding(nullptr)
   , mURI(aURI)
   , mLineNumber(1)
@@ -158,16 +159,17 @@ SheetLoadData::SheetLoadData(Loader* aLo
   , mPendingChildren(0)
   , mSyncLoad(false)
   , mIsNonDocumentSheet(false)
   , mIsLoading(false)
   , mIsBeingParsed(false)
   , mIsCancelled(false)
   , mMustNotify(false)
   , mWasAlternate(aIsAlternate)
+  , mMediaMatched(aMediaMatches)
   , mUseSystemPrincipal(false)
   , mSheetAlreadyComplete(false)
   , mIsCrossOriginNoCORS(false)
   , mBlockResourceTiming(false)
   , mLoadFailed(false)
   , mOwningElement(aOwningElement)
   , mObserver(aObserver)
   , mLoaderPrincipal(aLoaderPrincipal)
@@ -412,17 +414,17 @@ Loader::~Loader()
 void
 Loader::DropDocumentReference(void)
 {
   mDocument = nullptr;
   // Flush out pending datas just so we don't leak by accident.  These
   // loads should short-circuit through the mDocument check in
   // LoadSheet and just end up in SheetComplete immediately
   if (mSheets) {
-    StartAlternateLoads();
+    StartDeferredLoads();
   }
 }
 
 nsresult
 Loader::SetPreferredSheet(const nsAString& aTitle)
 {
 #ifdef DEBUG
   if (mDocument) {
@@ -1112,22 +1114,42 @@ Loader::CreateSheet(nsIURI* aURI,
 
   NS_ASSERTION(*aSheet, "We should have a sheet by now!");
   NS_ASSERTION(aSheetState != eSheetStateUnknown, "Have to set a state!");
   LOG(("  State: %s", gStateStrings[aSheetState]));
 
   return NS_OK;
 }
 
+static Loader::MediaMatched
+MediaListMatches(const MediaList* aMediaList, const nsIDocument* aDocument)
+{
+  if (!aMediaList || !aDocument) {
+    return Loader::MediaMatched::Yes;
+  }
+
+  nsPresContext* pc = aDocument->GetPresContext();
+  if (!pc) {
+    // Conservatively assume a match.
+    return Loader::MediaMatched::Yes;
+  }
+
+  if (aMediaList->Matches(pc)) {
+    return Loader::MediaMatched::Yes;
+  }
+
+  return Loader::MediaMatched::No;
+}
+
 /**
  * PrepareSheet() handles setting the media and title on the sheet, as
  * well as setting the enabled state based on the title and whether
  * the sheet had "alternate" in its rel.
  */
-void
+Loader::MediaMatched
 Loader::PrepareSheet(StyleSheet* aSheet,
                      const nsAString& aTitle,
                      const nsAString& aMediaString,
                      MediaList* aMediaList,
                      IsAlternate aIsAlternate)
 {
   NS_PRECONDITION(aSheet, "Must have a sheet!");
 
@@ -1138,16 +1160,17 @@ Loader::PrepareSheet(StyleSheet* aSheet,
                  "must not provide both aMediaString and aMediaList");
     mediaList = MediaList::Create(aMediaString);
   }
 
   aSheet->SetMedia(mediaList);
 
   aSheet->SetTitle(aTitle);
   aSheet->SetEnabled(aIsAlternate == IsAlternate::No);
+  return MediaListMatches(mediaList, mDocument);
 }
 
 /**
  * InsertSheetInDoc handles ordering of sheets in the document.  Here
  * we have two types of sheets -- those with linking elements and
  * those without.  The latter are loaded by Link: headers.
  * The following constraints are observed:
  * 1) Any sheet with a linking element comes after all sheets without
@@ -1400,17 +1423,17 @@ Loader::LoadSheet(SheetLoadData* aLoadDa
 
   if (existingData) {
     LOG(("  Glomming on to existing load"));
     SheetLoadData* data = existingData;
     while (data->mNext) {
       data = data->mNext;
     }
     data->mNext = aLoadData; // transfer ownership
-    if (aSheetState == eSheetPending && !aLoadData->mWasAlternate) {
+    if (aSheetState == eSheetPending && !aLoadData->ShouldDefer()) {
       // Kick the load off; someone cares about it right away
 
 #ifdef DEBUG
       SheetLoadData* removedData;
       NS_ASSERTION(mSheets->mPendingDatas.Get(&key, &removedData) &&
                    removedData == existingData,
                    "Bad pending table.");
 #endif
@@ -1495,17 +1518,17 @@ Loader::LoadSheet(SheetLoadData* aLoadDa
 #ifdef DEBUG
     mSyncCallback = false;
 #endif
     LOG_ERROR(("  Failed to create channel"));
     SheetComplete(aLoadData, rv);
     return rv;
   }
 
-  if (!aLoadData->mWasAlternate) {
+  if (!aLoadData->ShouldDefer()) {
     nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
     if (cos) {
       cos->AddClassFlags(nsIClassOfService::Leader);
     }
   }
 
   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
   if (httpChannel) {
@@ -1712,33 +1735,33 @@ Loader::SheetComplete(SheetLoadData* aLo
   for (uint32_t i = 0; i < count; ++i) {
     --mDatasToNotifyOn;
 
     SheetLoadData* data = datasToNotify[i];
     NS_ASSERTION(data && data->mMustNotify, "How did this data get here?");
     if (data->mObserver) {
       LOG(("  Notifying observer %p for data %p.  wasAlternate: %d",
            data->mObserver.get(), data, data->mWasAlternate));
-      data->mObserver->StyleSheetLoaded(data->mSheet, data->mWasAlternate,
+      data->mObserver->StyleSheetLoaded(data->mSheet, data->ShouldDefer(),
                                         aStatus);
     }
 
     nsTObserverArray<nsCOMPtr<nsICSSLoaderObserver> >::ForwardIterator iter(mObservers);
     nsCOMPtr<nsICSSLoaderObserver> obs;
     while (iter.HasMore()) {
       obs = iter.GetNext();
       LOG(("  Notifying global observer %p for data %p.  wasAlternate: %d",
            obs.get(), data, data->mWasAlternate));
       obs->StyleSheetLoaded(data->mSheet, data->mWasAlternate, aStatus);
     }
   }
 
   if (mSheets->mLoadingDatas.Count() == 0 && mSheets->mPendingDatas.Count() > 0) {
-    LOG(("  No more loading sheets; starting alternates"));
-    StartAlternateLoads();
+    LOG(("  No more loading sheets; starting deferred loads"));
+    StartDeferredLoads();
   }
 }
 
 void
 Loader::DoSheetComplete(SheetLoadData* aLoadData, LoadDataArray& aDatasToNotify)
 {
   LOG(("css::Loader::DoSheetComplete"));
   NS_PRECONDITION(aLoadData, "Must have a load data!");
@@ -1910,17 +1933,17 @@ Loader::LoadInlineStyle(nsIContent* aEle
   if (NS_FAILED(rv)) {
     return Err(rv);
   }
   NS_ASSERTION(state == eSheetNeedsParser,
                "Inline sheets should not be cached");
 
   LOG(("  Sheet is alternate: %d", static_cast<int>(isAlternate)));
 
-  PrepareSheet(sheet, aTitle, aMedia, nullptr, isAlternate);
+  auto matched = PrepareSheet(sheet, aTitle, aMedia, nullptr, isAlternate);
 
   if (aElement->HasFlag(NODE_IS_IN_SHADOW_TREE)) {
     ShadowRoot* containingShadow = aElement->GetContainingShadow();
     MOZ_ASSERT(containingShadow);
     containingShadow->InsertSheet(sheet, aElement);
   } else {
     rv = InsertSheetInDoc(sheet, aElement, mDocument);
     if (NS_FAILED(rv)) {
@@ -1935,16 +1958,17 @@ Loader::LoadInlineStyle(nsIContent* aEle
     // stylesheet. So treat this as principal inheritance, and downgrade if
     // necessary.
     principal = BasePrincipal::Cast(aTriggeringPrincipal)->PrincipalToInherit();
   }
 
   SheetLoadData* data = new SheetLoadData(this, aTitle, nullptr, sheet,
                                           owningElement,
                                           isAlternate == IsAlternate::Yes,
+                                          matched == MediaMatched::Yes,
                                           aObserver, nullptr,
                                           static_cast<nsINode*>(aElement));
 
   // We never actually load this, so just set its principal directly
   sheet->SetPrincipal(principal);
 
   NS_ADDREF(data);
   data->mLineNumber = aLineNumber;
@@ -1960,17 +1984,17 @@ Loader::LoadInlineStyle(nsIContent* aEle
   if (NS_FAILED(rv)) {
     return Err(rv);
   }
 
   // If completed is true, |data| may well be deleted by now.
   if (!completed) {
     data->mMustNotify = true;
   }
-  return LoadSheetResult { completed ? Completed::Yes : Completed::No, isAlternate };
+  return LoadSheetResult { completed ? Completed::Yes : Completed::No, isAlternate, matched };
 }
 
 Result<Loader::LoadSheetResult, nsresult>
 Loader::LoadStyleLink(nsIContent* aElement,
                       nsIURI* aURL,
                       nsIPrincipal* aTriggeringPrincipal,
                       const nsAString& aTitle,
                       const nsAString& aMedia,
@@ -2033,71 +2057,82 @@ Loader::LoadStyleLink(nsIContent* aEleme
                    aHasAlternateRel, aTitle, state, &isAlternate,
                    &sheet);
   if (NS_FAILED(rv)) {
     return Err(rv);
   }
 
   LOG(("  Sheet is alternate: %d", static_cast<int>(isAlternate)));
 
-  PrepareSheet(sheet, aTitle, aMedia, nullptr, isAlternate);
+  auto matched = PrepareSheet(sheet, aTitle, aMedia, nullptr, isAlternate);
 
   // FIXME(emilio, bug 1410578): Shadow DOM should be handled here too.
   rv = InsertSheetInDoc(sheet, aElement, mDocument);
   if (NS_FAILED(rv)) {
     return Err(rv);
   }
 
   nsCOMPtr<nsIStyleSheetLinkingElement> owningElement(do_QueryInterface(aElement));
 
   if (state == eSheetComplete) {
     LOG(("  Sheet already complete: 0x%p", sheet.get()));
     if (aObserver || !mObservers.IsEmpty() || owningElement) {
-      rv = PostLoadEvent(aURL, sheet, aObserver, isAlternate, owningElement);
+      rv = PostLoadEvent(aURL,
+                         sheet,
+                         aObserver,
+                         isAlternate,
+                         matched,
+                         owningElement);
       if (NS_FAILED(rv)) {
         return Err(rv);
       }
     }
 
     // The load hasn't been completed yet, will be done in PostLoadEvent.
-    return LoadSheetResult { Completed::No, isAlternate };
+    return LoadSheetResult { Completed::No, isAlternate, matched };
   }
 
   // Now we need to actually load it
   nsCOMPtr<nsINode> requestingNode = do_QueryInterface(context);
   SheetLoadData* data = new SheetLoadData(this, aTitle, aURL, sheet,
                                           owningElement,
                                           isAlternate == IsAlternate::Yes,
+                                          matched == MediaMatched::Yes,
                                           aObserver, principal, requestingNode);
   NS_ADDREF(data);
 
-  // If we have to parse and it's an alternate non-inline, defer it
+  auto result = LoadSheetResult { Completed::No, isAlternate, matched };
+
+  MOZ_ASSERT(!result.ShouldBlock() == data->ShouldDefer(),
+             "These should better match!");
+
+  // If we have to parse and it's a non-blocking non-inline sheet, defer it.
   if (aURL &&
       state == eSheetNeedsParser &&
       mSheets->mLoadingDatas.Count() != 0 &&
-      isAlternate == IsAlternate::Yes) {
+      !result.ShouldBlock()) {
     LOG(("  Deferring alternate sheet load"));
     URIPrincipalReferrerPolicyAndCORSModeHashKey key(data->mURI,
                                                      data->mLoaderPrincipal,
                                                      data->mSheet->GetCORSMode(),
                                                      data->mSheet->GetReferrerPolicy());
     mSheets->mPendingDatas.Put(&key, data);
 
     data->mMustNotify = true;
-    return LoadSheetResult { Completed::No, isAlternate };
+    return result;
   }
 
   // Load completion will free the data
   rv = LoadSheet(data, state, false);
   if (NS_FAILED(rv)) {
     return Err(rv);
   }
 
   data->mMustNotify = true;
-  return LoadSheetResult { Completed::No, isAlternate };
+  return result;
 }
 
 static bool
 HaveAncestorDataWithURI(SheetLoadData *aData, nsIURI *aURI)
 {
   if (!aData->mURI) {
     // Inline style; this won't have any ancestors
     MOZ_ASSERT(!aData->mParentData,
@@ -2369,17 +2404,22 @@ Loader::InternalLoadNonDocumentSheet(nsI
                    false, empty, state, &isAlternate, &sheet);
   NS_ENSURE_SUCCESS(rv, rv);
 
   PrepareSheet(sheet, empty, empty, nullptr, isAlternate);
 
   if (state == eSheetComplete) {
     LOG(("  Sheet already complete"));
     if (aObserver || !mObservers.IsEmpty()) {
-      rv = PostLoadEvent(aURL, sheet, aObserver, IsAlternate::No, nullptr);
+      rv = PostLoadEvent(aURL,
+                         sheet,
+                         aObserver,
+                         IsAlternate::No,
+                         MediaMatched::Yes,
+                         nullptr);
     }
     if (aSheet) {
       sheet.swap(*aSheet);
     }
     return rv;
   }
 
   SheetLoadData* data = new SheetLoadData(this,
@@ -2406,29 +2446,31 @@ Loader::InternalLoadNonDocumentSheet(nsI
   return rv;
 }
 
 nsresult
 Loader::PostLoadEvent(nsIURI* aURI,
                       StyleSheet* aSheet,
                       nsICSSLoaderObserver* aObserver,
                       IsAlternate aWasAlternate,
+                      MediaMatched aMediaMatched,
                       nsIStyleSheetLinkingElement* aElement)
 {
   LOG(("css::Loader::PostLoadEvent"));
   NS_PRECONDITION(aSheet, "Must have sheet");
   NS_PRECONDITION(aObserver || !mObservers.IsEmpty() || aElement,
                   "Must have observer or element");
 
   RefPtr<SheetLoadData> evt =
     new SheetLoadData(this, EmptyString(), // title doesn't matter here
                       aURI,
                       aSheet,
                       aElement,
                       aWasAlternate == IsAlternate::Yes,
+                      aMediaMatched == MediaMatched::Yes,
                       aObserver,
                       nullptr,
                       mDocument);
 
   if (!mPostedEvents.AppendElement(evt)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
@@ -2568,17 +2610,17 @@ Loader::AddObserver(nsICSSLoaderObserver
 
 void
 Loader::RemoveObserver(nsICSSLoaderObserver* aObserver)
 {
   mObservers.RemoveElement(aObserver);
 }
 
 void
-Loader::StartAlternateLoads()
+Loader::StartDeferredLoads()
 {
   NS_PRECONDITION(mSheets, "Don't call me!");
   LoadDataArray arr(mSheets->mPendingDatas.Count());
   for (auto iter = mSheets->mPendingDatas.Iter(); !iter.Done(); iter.Next()) {
     arr.AppendElement(iter.Data());
     iter.Remove();
   }
 
--- a/layout/style/Loader.h
+++ b/layout/style/Loader.h
@@ -187,16 +187,17 @@ enum StyleSheetState {
   eSheetComplete
 };
 
 class Loader final {
   typedef mozilla::net::ReferrerPolicy ReferrerPolicy;
 
 public:
   typedef nsIStyleSheetLinkingElement::IsAlternate IsAlternate;
+  typedef nsIStyleSheetLinkingElement::MediaMatched MediaMatched;
   typedef nsIStyleSheetLinkingElement::Completed Completed;
   typedef nsIStyleSheetLinkingElement::Update LoadSheetResult;
 
   Loader();
   // aDocGroup is used for dispatching SheetLoadData in PostLoadEvent(). It
   // can be null if you want to use this constructor, and there's no
   // document when the Loader is constructed.
   explicit Loader(mozilla::dom::DocGroup*);
@@ -508,21 +509,21 @@ private:
                        StyleSheetState& aSheetState,
                        IsAlternate* aIsAlternate,
                        RefPtr<StyleSheet>* aSheet);
 
   // Pass in either a media string or the MediaList from the CSSParser.  Don't
   // pass both.
   //
   // This method will set the sheet's enabled state based on aIsAlternate
-  void PrepareSheet(StyleSheet* aSheet,
-                    const nsAString& aTitle,
-                    const nsAString& aMediaString,
-                    dom::MediaList* aMediaList,
-                    IsAlternate);
+  MediaMatched PrepareSheet(StyleSheet* aSheet,
+                            const nsAString& aTitle,
+                            const nsAString& aMediaString,
+                            dom::MediaList* aMediaList,
+                            IsAlternate);
 
   nsresult InsertSheetInDoc(StyleSheet* aSheet,
                             nsIContent* aLinkingContent,
                             nsIDocument* aDocument);
 
   nsresult InsertChildSheet(StyleSheet* aSheet,
                             StyleSheet* aParentSheet);
 
@@ -545,20 +546,21 @@ private:
   // 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.
   nsresult PostLoadEvent(nsIURI* aURI,
                          StyleSheet* aSheet,
                          nsICSSLoaderObserver* aObserver,
                          IsAlternate aWasAlternate,
+                         MediaMatched aMediaMatched,
                          nsIStyleSheetLinkingElement* aElement);
 
   // Start the loads of all the sheets in mPendingDatas
-  void StartAlternateLoads();
+  void StartDeferredLoads();
 
   // Handle an event posted by PostLoadEvent
   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,
--- a/layout/style/PreloadedStyleSheet.cpp
+++ b/layout/style/PreloadedStyleSheet.cpp
@@ -88,17 +88,17 @@ PreloadedStyleSheet::Preload()
   return GetSheet(&sheet);
 }
 
 NS_IMPL_ISUPPORTS(PreloadedStyleSheet::StylesheetPreloadObserver,
                   nsICSSLoaderObserver)
 
 NS_IMETHODIMP
 PreloadedStyleSheet::StylesheetPreloadObserver::StyleSheetLoaded(
-  StyleSheet* aSheet, bool aWasAlternate, nsresult aStatus)
+  StyleSheet* aSheet, bool aWasDeferred, nsresult aStatus)
 {
   MOZ_DIAGNOSTIC_ASSERT(!mPreloadedSheet->mLoaded);
   mPreloadedSheet->mLoaded = true;
 
   if (NS_FAILED(aStatus)) {
     mPromise->MaybeReject(aStatus);
   } else {
     mPromise->MaybeResolve(mPreloadedSheet);
--- a/layout/style/SheetLoadData.h
+++ b/layout/style/SheetLoadData.h
@@ -45,16 +45,17 @@ protected:
 public:
   // Data for loading a sheet linked from a document
   SheetLoadData(Loader* aLoader,
                 const nsAString& aTitle,
                 nsIURI* aURI,
                 StyleSheet* aSheet,
                 nsIStyleSheetLinkingElement* aOwningElement,
                 bool aIsAlternate,
+                bool aMediaMatches,
                 nsICSSLoaderObserver* aObserver,
                 nsIPrincipal* aLoaderPrincipal,
                 nsINode* aRequestingNode);
 
   // Data for loading a sheet linked from an @import rule
   SheetLoadData(Loader* aLoader,
                 nsIURI* aURI,
                 StyleSheet* aSheet,
@@ -152,16 +153,20 @@ public:
   // 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;
 
+  // mMediaMatched is true if the sheet matched its medialist when the load data
+  // was created.
+  bool mMediaMatched : 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;
@@ -194,16 +199,21 @@ public:
 
   // 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;
 
+  bool ShouldDefer() const
+  {
+    return mWasAlternate || !mMediaMatched;
+  }
+
 private:
   void FireLoadEvent(nsIThreadInternal* aThread);
 };
 
 typedef nsMainThreadPtrHolder<SheetLoadData> SheetLoadDataHolder;
 
 } // namespace css
 } // namespace mozilla