Bug 1435939: Make media feature changes always async. r?bz draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Tue, 06 Feb 2018 12:51:32 +0100
changeset 751621 c64759a3b1b909d9a221ebaf30f323c4277db0e7
parent 751620 b686a330cdbb75a35f04bcbeaa1089275e96d8b8
child 751622 ceee35775a5df57e9d548fa017488807f9ddf604
push id98022
push userbmo:emilio@crisal.io
push dateTue, 06 Feb 2018 19:03:46 +0000
reviewersbz
bugs1435939, 1434474, 1413143
milestone60.0a1
Bug 1435939: Make media feature changes always async. r?bz Much in the spirit of bug 1434474. We right now call MediaFeatureChanges sync or async pretty randomly. This has caused bugs in the past like bug 1413143. Unify media feature changes, and only post them async, and flush them from FlushPendingNotifications. This also fixes a pre-existing problem where style wasn't flushed correctly from getComputedStyle when there were pending media feature values. MozReview-Commit-ID: H9S1M8fk5H4
docshell/base/nsDocShell.cpp
layout/base/nsPresContext.cpp
layout/base/nsPresContext.h
layout/style/MediaFeatureChange.h
layout/style/moz.build
layout/style/nsComputedDOMStyle.cpp
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -20,16 +20,17 @@
 #include "mozilla/AutoRestore.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/Casting.h"
 #include "mozilla/Encoding.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/HTMLEditor.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/Logging.h"
+#include "mozilla/MediaFeatureChange.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ResultExtensions.h"
 #include "mozilla/Services.h"
 #include "mozilla/StartupTimeline.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Unused.h"
 
 #include "mozilla/dom/ClientChannelHelper.h"
@@ -4174,17 +4175,18 @@ nsDocShell::GetWindow()
 NS_IMETHODIMP
 nsDocShell::SetDeviceSizeIsPageSize(bool aValue)
 {
   if (mDeviceSizeIsPageSize != aValue) {
     mDeviceSizeIsPageSize = aValue;
     RefPtr<nsPresContext> presContext;
     GetPresContext(getter_AddRefs(presContext));
     if (presContext) {
-      presContext->MediaFeatureValuesChanged(nsRestyleHint(0));
+      presContext->MediaFeatureValuesChanged({
+        MediaFeatureChangeReason::DeviceSizeIsPageSizeChange });
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::GetDeviceSizeIsPageSize(bool* aValue)
 {
@@ -14417,14 +14419,15 @@ nsDocShell::SetDisplayMode(uint32_t aDis
     return NS_ERROR_INVALID_ARG;
   }
 
   if (aDisplayMode != mDisplayMode) {
     mDisplayMode = aDisplayMode;
 
     RefPtr<nsPresContext> presContext;
     if (NS_SUCCEEDED(GetPresContext(getter_AddRefs(presContext)))) {
-      presContext->MediaFeatureValuesChangedAllDocuments(nsRestyleHint(0));
-    }
-  }
-
-  return NS_OK;
-}
+      presContext->MediaFeatureValuesChangedAllDocuments({
+        MediaFeatureChangeReason::DisplayModeChange });
+    }
+  }
+
+  return NS_OK;
+}
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -301,23 +301,21 @@ nsPresContext::nsPresContext(nsIDocument
     mCanPaginatedScroll(false),
     mDoScaledTwips(true),
     mIsRootPaginatedDocument(false),
     mPrefBidiDirection(false),
     mPrefScrollbarSide(0),
     mPendingSysColorChanged(false),
     mPendingThemeChanged(false),
     mPendingUIResolutionChanged(false),
-    mPendingMediaFeatureValuesChanged(false),
     mPrefChangePendingNeedsReflow(false),
     mIsEmulatingMedia(false),
     mIsGlyph(false),
     mUsesRootEMUnits(false),
     mUsesExChUnits(false),
-    mPendingViewportChange(false),
     mCounterStylesDirty(true),
     mFontFeatureValuesDirty(true),
     mSuppressResizeReflow(false),
     mIsVisual(false),
     mFireAfterPaintEvents(false),
     mIsChrome(false),
     mIsChromeOriginImage(false),
     mPaintFlashing(false),
@@ -736,17 +734,21 @@ nsPresContext::AppUnitsPerDevPixelChange
   InvalidatePaintedLayers();
 
   if (mDeviceContext) {
     mDeviceContext->FlushFontCache();
   }
 
   if (HasCachedStyleData()) {
     // All cached style data must be recomputed.
-    MediaFeatureValuesChanged(eRestyle_ForceDescendants, NS_STYLE_HINT_REFLOW);
+    MediaFeatureValuesChanged({
+      eRestyle_ForceDescendants,
+      NS_STYLE_HINT_REFLOW,
+      MediaFeatureChangeReason::ResolutionChange
+    });
   }
 
   mCurAppUnitsPerDevPixel = AppUnitsPerDevPixel();
 }
 
 void
 nsPresContext::PreferenceChanged(const char* aPrefName)
 {
@@ -1407,18 +1409,21 @@ nsPresContext::UpdateEffectiveTextZoom()
   mEffectiveTextZoom = newZoom;
 
   // In case of servo, stylist.device might have already generated the default
   // computed values with the previous effective text zoom value even if the
   // pres shell has not initialized yet.
   if (mDocument->IsStyledByServo() || HasCachedStyleData()) {
     // Media queries could have changed, since we changed the meaning
     // of 'em' units in them.
-    MediaFeatureValuesChanged(eRestyle_ForceDescendants,
-                              NS_STYLE_HINT_REFLOW);
+    MediaFeatureValuesChanged({
+      eRestyle_ForceDescendants,
+      NS_STYLE_HINT_REFLOW,
+      MediaFeatureChangeReason::ZoomChange
+    });
   }
 }
 
 float
 nsPresContext::GetDeviceFullZoom()
 {
   return mDeviceContext->GetFullZoom();
 }
@@ -1456,17 +1461,17 @@ nsPresContext::SetOverrideDPPX(float aDP
 {
   // SetOverrideDPPX is called during navigations, including history
   // traversals.  In that case, it's typically called with our current value,
   // and we don't need to actually do anything.
   if (aDPPX != mOverrideDPPX) {
     mOverrideDPPX = aDPPX;
 
     if (HasCachedStyleData()) {
-      MediaFeatureValuesChanged(nsRestyleHint(0), nsChangeHint(0));
+      MediaFeatureValuesChanged({ MediaFeatureChangeReason::ResolutionChange });
     }
   }
 }
 
 gfxSize
 nsPresContext::ScreenSizeInchesForFontInflation(bool* aChanged)
 {
   if (aChanged) {
@@ -1915,17 +1920,21 @@ nsPresContext::RefreshSystemMetrics()
   nsMediaFeatures::FreeSystemMetrics();
 
   // Changes to system metrics can change media queries on them.
   //
   // Changes in theme can change system colors (whose changes are
   // properly reflected in computed style data), system fonts (whose
   // changes are not), and -moz-appearance (whose changes likewise are
   // not), so we need to recascade for the first, and reflow for the rest.
-  MediaFeatureValuesChanged(eRestyle_ForceDescendants, NS_STYLE_HINT_REFLOW);
+  MediaFeatureValuesChanged({
+    eRestyle_ForceDescendants,
+    NS_STYLE_HINT_REFLOW,
+    MediaFeatureChangeReason::SystemMetricsChange,
+  });
 }
 
 void
 nsPresContext::UIResolutionChanged()
 {
   if (!mPendingUIResolutionChanged) {
     nsCOMPtr<nsIRunnable> ev =
       NewRunnableMethod("nsPresContext::UIResolutionChangedInternal",
@@ -2015,26 +2024,26 @@ nsPresContext::EmulateMedium(const nsASt
   nsAtom* previousMedium = Medium();
   mIsEmulatingMedia = true;
 
   nsAutoString mediaType;
   nsContentUtils::ASCIIToLower(aMediaType, mediaType);
 
   mMediaEmulated = NS_Atomize(mediaType);
   if (mMediaEmulated != previousMedium && mShell) {
-    MediaFeatureValuesChanged(nsRestyleHint(0), nsChangeHint(0));
+    MediaFeatureValuesChanged({ MediaFeatureChangeReason::MediumChange });
   }
 }
 
 void nsPresContext::StopEmulatingMedium()
 {
   nsAtom* previousMedium = Medium();
   mIsEmulatingMedia = false;
   if (Medium() != previousMedium) {
-    MediaFeatureValuesChanged(nsRestyleHint(0), nsChangeHint(0));
+    MediaFeatureValuesChanged({ MediaFeatureChangeReason::MediumChange });
   }
 }
 
 void
 nsPresContext::ForceCacheLang(nsAtom *aLanguage)
 {
   // force it to be cached
   GetDefaultFont(kPresContext_DefaultVariableFont_ID, aLanguage);
@@ -2062,16 +2071,18 @@ void
 nsPresContext::RebuildAllStyleData(nsChangeHint aExtraHint,
                                    nsRestyleHint aRestyleHint)
 {
   if (!mShell) {
     // We must have been torn down. Nothing to do here.
     return;
   }
 
+  // FIXME(emilio): Why is it safe to reset mUsesRootEMUnits / mUsesEXChUnits
+  // here if there's no restyle hint? That looks pretty bogus.
   mUsesRootEMUnits = false;
   mUsesExChUnits = false;
   if (mShell->StyleSet()->IsGecko()) {
 #ifdef MOZ_OLD_STYLE
     mShell->StyleSet()->AsGecko()->SetUsesViewportUnits(false);
 #else
     MOZ_CRASH("old style system disabled");
 #endif
@@ -2103,60 +2114,61 @@ nsPresContext::PostRebuildAllStyleDataEv
 
 struct MediaFeatureHints
 {
   nsRestyleHint restyleHint;
   nsChangeHint changeHint;
 };
 
 static bool
-MediaFeatureValuesChangedAllDocumentsCallback(nsIDocument* aDocument, void* aHints)
+MediaFeatureValuesChangedAllDocumentsCallback(nsIDocument* aDocument, void* aChange)
 {
-  MediaFeatureHints* hints = static_cast<MediaFeatureHints*>(aHints);
+  auto* change = static_cast<const MediaFeatureChange*>(aChange);
   if (nsIPresShell* shell = aDocument->GetShell()) {
     if (nsPresContext* pc = shell->GetPresContext()) {
-      pc->MediaFeatureValuesChangedAllDocuments(hints->restyleHint,
-                                                hints->changeHint);
+      pc->MediaFeatureValuesChangedAllDocuments(*change);
     }
   }
   return true;
 }
 
 void
-nsPresContext::MediaFeatureValuesChangedAllDocuments(nsRestyleHint aRestyleHint,
-                                                     nsChangeHint aChangeHint)
+nsPresContext::MediaFeatureValuesChangedAllDocuments(
+    const MediaFeatureChange& aChange)
 {
-    MediaFeatureValuesChanged(aRestyleHint, aChangeHint);
-    MediaFeatureHints hints = {
-      aRestyleHint,
-      aChangeHint
-    };
-
-    mDocument->EnumerateSubDocuments(MediaFeatureValuesChangedAllDocumentsCallback,
-                                     &hints);
+    MediaFeatureValuesChanged(aChange);
+    mDocument->EnumerateSubDocuments(
+      MediaFeatureValuesChangedAllDocumentsCallback,
+      const_cast<MediaFeatureChange*>(&aChange));
 }
 
 void
-nsPresContext::MediaFeatureValuesChanged(nsRestyleHint aRestyleHint,
-                                         nsChangeHint aChangeHint)
+nsPresContext::FlushPendingMediaFeatureValuesChanged()
 {
-  mPendingMediaFeatureValuesChanged = false;
+  if (!mPendingMediaFeatureValuesChange) {
+    return;
+  }
+
+  MediaFeatureChange change = *mPendingMediaFeatureValuesChange;
+  mPendingMediaFeatureValuesChange.reset();
+
+  const bool viewportChanged =
+    bool(change.mReason & MediaFeatureChangeReason::ViewportChange);
+
 
   // MediumFeaturesChanged updates the applied rules, so it always gets called.
   if (mShell) {
-    aRestyleHint |= mShell->
-      StyleSet()->MediumFeaturesChanged(mPendingViewportChange);
+    change.mRestyleHint |=
+      mShell->StyleSet()->MediumFeaturesChanged(viewportChanged);
   }
 
-  if (aRestyleHint || aChangeHint) {
-    RebuildAllStyleData(aChangeHint, aRestyleHint);
+  if (change.mRestyleHint || change.mChangeHint) {
+    RebuildAllStyleData(change.mChangeHint, change.mRestyleHint);
   }
 
-  mPendingViewportChange = false;
-
   if (!mShell || !mShell->DidInitialize()) {
     return;
   }
 
   if (mDocument->IsBeingUsedAsImage()) {
     MOZ_ASSERT(mDocument->MediaQueryLists().isEmpty());
     return;
   }
@@ -2167,81 +2179,56 @@ nsPresContext::MediaFeatureValuesChanged
 
   // Media query list listeners should be notified from a queued task
   // (in HTML5 terms), although we also want to notify them on certain
   // flushes.  (We're already running off an event.)
   //
   // Note that we do this after the new style from media queries in
   // style sheets has been computed.
 
-  if (!mDocument->MediaQueryLists().isEmpty()) {
-    // We build a list of all the notifications we're going to send
-    // before we send any of them.
-
-    // Copy pointers to all the lists into a new array, in case one of our
-    // notifications modifies the list.
-    nsTArray<RefPtr<mozilla::dom::MediaQueryList>> localMediaQueryLists;
-    for (auto* mql : mDocument->MediaQueryLists()) {
-      localMediaQueryLists.AppendElement(mql);
-    }
-
-    // Now iterate our local array of the lists.
-    for (const auto& mql : localMediaQueryLists) {
-      nsAutoMicroTask mt;
-      mql->MaybeNotify();
-    }
+  if (mDocument->MediaQueryLists().isEmpty()) {
+    return;
   }
-}
-
-void
-nsPresContext::PostMediaFeatureValuesChangedEvent()
-{
-  // FIXME: We should probably replace this event with use of
-  // nsRefreshDriver::AddStyleFlushObserver (except the pres shell would
-  // need to track whether it's been added).
-  if (!mPendingMediaFeatureValuesChanged && mShell) {
-    nsCOMPtr<nsIRunnable> ev =
-      NewRunnableMethod("nsPresContext::HandleMediaFeatureValuesChangedEvent",
-                        this, &nsPresContext::HandleMediaFeatureValuesChangedEvent);
-    nsresult rv =
-      Document()->Dispatch(TaskCategory::Other, ev.forget());
-    if (NS_SUCCEEDED(rv)) {
-      mPendingMediaFeatureValuesChanged = true;
-      mShell->SetNeedStyleFlush();
-    }
+
+  // We build a list of all the notifications we're going to send
+  // before we send any of them.
+
+  // Copy pointers to all the lists into a new array, in case one of our
+  // notifications modifies the list.
+  nsTArray<RefPtr<mozilla::dom::MediaQueryList>> localMediaQueryLists;
+  for (auto* mql : mDocument->MediaQueryLists()) {
+    localMediaQueryLists.AppendElement(mql);
   }
-}
-
-void
-nsPresContext::HandleMediaFeatureValuesChangedEvent()
-{
-  // Null-check mShell in case the shell has been destroyed (and the
-  // event is the only thing holding the pres context alive).
-  if (mPendingMediaFeatureValuesChanged && mShell) {
-    MediaFeatureValuesChanged(nsRestyleHint(0));
+
+  // Now iterate our local array of the lists.
+  for (const auto& mql : localMediaQueryLists) {
+    nsAutoMicroTask mt;
+    mql->MaybeNotify();
   }
 }
 
 static bool
 NotifyTabSizeModeChanged(TabParent* aTab, void* aArg)
 {
   nsSizeMode* sizeMode = static_cast<nsSizeMode*>(aArg);
   aTab->SizeModeChanged(*sizeMode);
   return false;
 }
 
 void
 nsPresContext::SizeModeChanged(nsSizeMode aSizeMode)
 {
-  if (HasCachedStyleData()) {
-    nsContentUtils::CallOnAllRemoteChildren(mDocument->GetWindow(),
-                                            NotifyTabSizeModeChanged,
-                                            &aSizeMode);
-    MediaFeatureValuesChangedAllDocuments(nsRestyleHint(0));
+  if (!HasCachedStyleData()) {
+    return;
   }
+
+  nsContentUtils::CallOnAllRemoteChildren(mDocument->GetWindow(),
+                                          NotifyTabSizeModeChanged,
+                                          &aSizeMode);
+  MediaFeatureValuesChangedAllDocuments({ MediaFeatureChangeReason::SizeModeChange });
 }
 
 nsCompatibility
 nsPresContext::CompatibilityMode() const
 {
   return Document()->GetCompatibilityMode();
 }
 
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -5,23 +5,25 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* a presentation of a document, part 1 */
 
 #ifndef nsPresContext_h___
 #define nsPresContext_h___
 
 #include "mozilla/Attributes.h"
+#include "mozilla/MediaFeatureChange.h"
 #include "mozilla/NotNull.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
 #include "nsColor.h"
 #include "nsCoord.h"
 #include "nsCOMPtr.h"
 #include "nsIPresShell.h"
+#include "nsIPresShellInlines.h"
 #include "nsRect.h"
 #include "nsStringFwd.h"
 #include "nsFont.h"
 #include "gfxFontConstants.h"
 #include "nsAtom.h"
 #include "nsITimer.h"
 #include "nsCRT.h"
 #include "nsIWidgetListener.h"
@@ -219,22 +221,20 @@ public:
       NS_ASSERTION(!mShell || !mShell->GetDocument() ||
                    mShell->GetDocument() == mDocument,
                    "nsPresContext doesn't have the same document as nsPresShell!");
       return mDocument;
   }
 
   mozilla::StyleSetHandle StyleSet() const { return GetPresShell()->StyleSet(); }
 
-#ifdef DEBUG
   bool HasPendingMediaQueryUpdates() const
   {
-    return mPendingMediaFeatureValuesChanged;
+    return !!mPendingMediaFeatureValuesChange;
   }
-#endif
 
   nsFrameManager* FrameManager()
     { return PresShell()->FrameManager(); }
 
   nsCSSFrameConstructor* FrameConstructor()
     { return PresShell()->FrameConstructor(); }
 
   mozilla::AnimationEventDispatcher* AnimationEventDispatcher()
@@ -268,16 +268,17 @@ public:
   void RebuildAllStyleData(nsChangeHint aExtraHint, nsRestyleHint aRestyleHint);
   /**
    * Just like RebuildAllStyleData, except (1) asynchronous and (2) it
    * doesn't rebuild the user font set.
    */
   void PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
                                     nsRestyleHint aRestyleHint);
 
+
   /**
    * Handle changes in the values of media features (used in media
    * queries).
    *
    * There are three sensible values to use for aRestyleHint:
    *  * nsRestyleHint(0) to rebuild style data, with rerunning of
    *    selector matching, only if media features have changed
    *  * eRestyle_ForceDescendants to force rebuilding of style data (but
@@ -288,32 +289,38 @@ public:
    *    RebuildAllStyleData at all.)
    *  * eRestyle_Subtree to force rebuilding of style data with
    *    rerunning of selector matching
    *
    * For aChangeHint, see RestyleManager::RebuildAllStyleData.  (Passing
    * a nonzero aChangeHint forces rebuilding style data even if
    * nsRestyleHint(0) is passed.)
    */
-  void MediaFeatureValuesChanged(nsRestyleHint aRestyleHint,
-                                 nsChangeHint aChangeHint = nsChangeHint(0));
+  void MediaFeatureValuesChanged(const mozilla::MediaFeatureChange& aChange)
+  {
+    if (mShell) {
+      mShell->EnsureStyleFlush();
+    }
+
+    if (!mPendingMediaFeatureValuesChange) {
+      mPendingMediaFeatureValuesChange.emplace(aChange);
+      return;
+    }
+
+    *mPendingMediaFeatureValuesChange |= aChange;
+  }
+
+  void FlushPendingMediaFeatureValuesChanged();
+
   /**
    * Calls MediaFeatureValuesChanged for this pres context and all descendant
    * subdocuments that have a pres context. This should be used for media
    * features that must be updated in all subdocuments e.g. display-mode.
    */
-  void MediaFeatureValuesChangedAllDocuments(nsRestyleHint aRestyleHint,
-                                             nsChangeHint aChangeHint = nsChangeHint(0));
-
-  void PostMediaFeatureValuesChangedEvent();
-  void HandleMediaFeatureValuesChangedEvent();
-  void FlushPendingMediaFeatureValuesChanged() {
-    if (mPendingMediaFeatureValuesChanged)
-      MediaFeatureValuesChanged(nsRestyleHint(0));
-  }
+  void MediaFeatureValuesChangedAllDocuments(const mozilla::MediaFeatureChange&);
 
   /**
    * Updates the size mode on all remote children and recursively notifies this
    * document and all subdocuments (including remote children) that a media
    * feature value has changed.
    */
   void SizeModeChanged(nsSizeMode aSizeMode);
 
@@ -461,18 +468,21 @@ public:
    * Set the currently visible area. The units for r are standard
    * nscoord units (as scaled by the device context).
    */
   void SetVisibleArea(const nsRect& r) {
     if (!r.IsEqualEdges(mVisibleArea)) {
       mVisibleArea = r;
       // Visible area does not affect media queries when paginated.
       if (!IsPaginated() && HasCachedStyleData()) {
-        mPendingViewportChange = true;
-        PostMediaFeatureValuesChangedEvent();
+        MediaFeatureValuesChanged({
+          nsRestyleHint(0),
+          nsChangeHint(0),
+          mozilla::MediaFeatureChangeReason::ViewportChange
+        });
       }
     }
   }
 
   bool ShouldFireResizeEvent() const {
     return !mLastResizeEventVisibleArea.IsEqualEdges(mVisibleArea);
   }
 
@@ -613,18 +623,21 @@ public:
   void SetBaseMinFontSize(int32_t aMinFontSize) {
     if (aMinFontSize == mBaseMinFontSize)
       return;
 
     mBaseMinFontSize = aMinFontSize;
     if (HasCachedStyleData()) {
       // Media queries could have changed, since we changed the meaning
       // of 'em' units in them.
-      MediaFeatureValuesChanged(eRestyle_ForceDescendants,
-                                NS_STYLE_HINT_REFLOW);
+      MediaFeatureValuesChanged({
+        eRestyle_ForceDescendants,
+        NS_STYLE_HINT_REFLOW,
+        mozilla::MediaFeatureChangeReason::MinFontSizeChange
+      });
     }
   }
 
   float GetFullZoom() { return mFullZoom; }
   /**
    * Device full zoom differs from full zoom because it gets the zoom from
    * the device context, which may be using a different zoom due to rounding
    * of app units to device pixels.
@@ -1450,31 +1463,27 @@ protected:
   unsigned              mCanPaginatedScroll : 1;
   unsigned              mDoScaledTwips : 1;
   unsigned              mIsRootPaginatedDocument : 1;
   unsigned              mPrefBidiDirection : 1;
   unsigned              mPrefScrollbarSide : 2;
   unsigned              mPendingSysColorChanged : 1;
   unsigned              mPendingThemeChanged : 1;
   unsigned              mPendingUIResolutionChanged : 1;
-  unsigned              mPendingMediaFeatureValuesChanged : 1;
   unsigned              mPrefChangePendingNeedsReflow : 1;
   unsigned              mIsEmulatingMedia : 1;
 
   // Are we currently drawing an SVG glyph?
   unsigned              mIsGlyph : 1;
 
   // Does the associated document use root-em (rem) units?
   unsigned              mUsesRootEMUnits : 1;
   // Does the associated document use ex or ch units?
   unsigned              mUsesExChUnits : 1;
 
-  // Has there been a change to the viewport's dimensions?
-  unsigned              mPendingViewportChange : 1;
-
   // Is the current mCounterStyleManager valid?
   unsigned              mCounterStylesDirty : 1;
 
   // Is the current mFontFeatureValuesLookup valid?
   unsigned              mFontFeatureValuesDirty : 1;
 
   // resize reflow is suppressed when the only change has been to zoom
   // the document rather than to change the document's dimensions
@@ -1509,16 +1518,17 @@ protected:
   // Should we output debug information about restyling for this document?
   unsigned mRestyleLoggingEnabled : 1;
 #endif
 
 #ifdef DEBUG
   unsigned mInitialized : 1;
 #endif
 
+  mozilla::Maybe<mozilla::MediaFeatureChange> mPendingMediaFeatureValuesChange;
 
 protected:
 
   virtual ~nsPresContext();
 
   nscolor MakeColorPref(const nsString& aColor);
 
   void LastRelease();
new file mode 100644
--- /dev/null
+++ b/layout/style/MediaFeatureChange.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* A struct defining a media feature change. */
+
+#ifndef mozilla_MediaFeatureChange_h__
+#define mozilla_MediaFeatureChange_h__
+
+#include "nsChangeHint.h"
+#include "mozilla/TypedEnumBits.h"
+
+namespace mozilla {
+
+enum class MediaFeatureChangeReason
+{
+  ViewportChange = 1 << 0,
+  ZoomChange = 1 << 1,
+  MinFontSizeChange = 1 << 2,
+  ResolutionChange = 1 << 3,
+  MediumChange = 1 << 4,
+  SizeModeChange = 1 << 5,
+  SystemMetricsChange = 1 << 6,
+  DeviceSizeIsPageSizeChange = 1 << 7,
+  DisplayModeChange = 1 << 8,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(MediaFeatureChangeReason)
+
+struct MediaFeatureChange
+{
+  nsRestyleHint mRestyleHint;
+  nsChangeHint mChangeHint;
+  MediaFeatureChangeReason mReason;
+
+  MediaFeatureChange(MediaFeatureChangeReason aReason)
+    : MediaFeatureChange(nsRestyleHint(0), nsChangeHint(0), aReason)
+  {
+  }
+
+  MediaFeatureChange(nsRestyleHint aRestyleHint,
+                     nsChangeHint aChangeHint,
+                     MediaFeatureChangeReason aReason)
+    : mRestyleHint(aRestyleHint)
+    , mChangeHint(aChangeHint)
+    , mReason(aReason)
+  {
+  }
+
+  inline MediaFeatureChange& operator|=(const MediaFeatureChange& aOther)
+  {
+    mRestyleHint |= aOther.mRestyleHint;
+    mChangeHint |= aOther.mChangeHint;
+    mReason |= aOther.mReason;
+    return *this;
+  }
+};
+
+} // namespace mozilla
+
+#endif
--- a/layout/style/moz.build
+++ b/layout/style/moz.build
@@ -87,16 +87,17 @@ EXPORTS.mozilla += [
     'CachedInheritingStyles.h',
     'CSSEnabledState.h',
     'DeclarationBlock.h',
     'DeclarationBlockInlines.h',
     'DocumentStyleRootIterator.h',
     'GenericSpecifiedValues.h',
     'GenericSpecifiedValuesInlines.h',
     'LayerAnimationInfo.h',
+    'MediaFeatureChange.h',
     'PostTraversalTask.h',
     'PreloadedStyleSheet.h',
     'RuleNodeCacheConditions.h',
     'ServoArcTypeList.h',
     'ServoBindingList.h',
     'ServoBindings.h',
     'ServoBindingTypes.h',
     'ServoCSSParser.h',
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -142,37 +142,49 @@ DocumentNeedsRestyle(
   const nsIDocument* aDocument,
   Element* aElement,
   nsAtom* aPseudo)
 {
   nsIPresShell* shell = aDocument->GetShell();
   if (!shell) {
     return true;
   }
+
+  nsPresContext* presContext = shell->GetPresContext();
+  MOZ_ASSERT(presContext);
+
   // Unfortunately we don't know if the sheet change affects mContent or not, so
   // just assume it will and that we need to flush normally.
   StyleSetHandle styleSet = shell->StyleSet();
   if (styleSet->StyleSheetsHaveChanged()) {
     return true;
   }
 
+  // Pending media query updates can definitely change style on the element, so
+  // gotta flush.
+  //
+  // TODO(emilio): Does this need to also check the user font set? (it affects
+  // ch / ex units).
+  if (presContext->HasPendingMediaQueryUpdates()) {
+    return true;
+  }
+
   // If the pseudo-element is animating, make sure to flush.
   if (aElement->MayHaveAnimations() && aPseudo) {
     if (aPseudo == nsCSSPseudoElements::before) {
       if (EffectSet::GetEffectSet(aElement, CSSPseudoElementType::before)) {
         return true;
       }
     } else if (aPseudo == nsCSSPseudoElements::after) {
       if (EffectSet::GetEffectSet(aElement, CSSPseudoElementType::after)) {
         return true;
       }
     }
   }
 
-  nsPresContext* presContext = shell->GetPresContext();
   if (styleSet->IsServo()) {
     // For Servo, we need to process the restyle-hint-invalidations first, to
     // expand LaterSiblings hint, so that we can look whether ancestors need
     // restyling.
     ServoRestyleManager* restyleManager =
       presContext->RestyleManager()->AsServo();
     restyleManager->ProcessAllPendingAttributeAndStateInvalidations();
 
@@ -935,21 +947,30 @@ nsComputedDOMStyle::GetFlushTarget(nsIDo
   // be an edge case, so we just don't bother to do the optimization in this
   // case.
   //
   // FIXME(emilio): This is likely to want GetComposedDoc() instead of
   // OwnerDoc().
   if (aDocument != mContent->OwnerDoc()) {
     return FlushTarget::Normal;
   }
+
   if (DocumentNeedsRestyle(aDocument, mContent->AsElement(), mPseudo)) {
     return FlushTarget::Normal;
   }
+
   // If parent document is there, also needs to check if there is some change
   // that needs to flush this document (e.g. size change for iframe).
+  //
+  // FIXME(emilio): This doesn't look sound to me, the parent flush may actually
+  // run script or what not in a way that causes the element to need a
+  // restyle...
+  //
+  // nsDocument::FlushPendingNotifications should probably check with this
+  // function after flushing the parent again, or something.
   while (nsIDocument* parentDocument = aDocument->GetParentDocument()) {
     Element* element = parentDocument->FindContentForSubDocument(aDocument);
     if (DocumentNeedsRestyle(parentDocument, element, nullptr)) {
       return FlushTarget::Normal;
     }
     aDocument = parentDocument;
   }
   return FlushTarget::ParentOnly;