Bug 1435939: Make media feature changes always async. r=bz
authorEmilio Cobos Álvarez <emilio@crisal.io>
Tue, 06 Feb 2018 12:51:32 +0100
changeset 402789 12ca97022384d5461f597088f6941c85c213197d
parent 402788 a18cd8419b3f3a131a8536798f2472a78bd1f728
child 402790 66535b923cac1661d8e40a4570bb18127db8987b
push id99659
push useraciure@mozilla.com
push dateWed, 07 Feb 2018 22:33:57 +0000
treeherdermozilla-inbound@5ceb1098fef3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1435939, 1434474, 1413143
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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)
 {
@@ -14418,14 +14420,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,19 @@ 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({
+          mozilla::MediaFeatureChangeReason::ViewportChange
+        });
       }
     }
   }
 
   bool ShouldFireResizeEvent() const {
     return !mLastResizeEventVisibleArea.IsEqualEdges(mVisibleArea);
   }
 
@@ -613,18 +621,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 +1461,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 +1516,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,81 @@
+/* -*- 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/Attributes.h"
+#include "mozilla/TypedEnumBits.h"
+
+namespace mozilla {
+
+enum class MediaFeatureChangeReason
+{
+  // The viewport size the document has used has changed.
+  //
+  // This affects size media queries like min-width.
+  ViewportChange = 1 << 0,
+  // The effective text zoom has changed. This affects the meaning of em units,
+  // and thus affects any media query that uses a Length.
+  ZoomChange = 1 << 1,
+  // The base min font size has changed. This can affect the meaning of em
+  // units, if the previous default font-size has changed, and also zoom.
+  MinFontSizeChange = 1 << 2,
+  // The resolution has changed. This can affect device-pixel-ratio media
+  // queries, for example.
+  ResolutionChange = 1 << 3,
+  // The medium has changed.
+  MediumChange = 1 << 4,
+  // The size-mode has changed.
+  SizeModeChange = 1 << 5,
+  // A system metric or multiple have changed. This affects all the media
+  // features that expose the presence of a system metric directly.
+  SystemMetricsChange = 1 << 6,
+  // The fact of whether the device size is the page size has changed, thus
+  // resolution media queries can change.
+  DeviceSizeIsPageSizeChange = 1 << 7,
+  // display-mode changed on the document, thus the display-mode media queries
+  // may have changed.
+  DisplayModeChange = 1 << 8,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(MediaFeatureChangeReason)
+
+struct MediaFeatureChange
+{
+  nsRestyleHint mRestyleHint;
+  nsChangeHint mChangeHint;
+  MediaFeatureChangeReason mReason;
+
+  MOZ_IMPLICIT 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,51 @@ 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. For
+  // example, if you change the zoom factor and then call getComputedStyle, you
+  // should be able to observe the style with the new media queries.
+  //
+  // TODO(emilio): Does this need to also check the user font set? (it affects
+  // ch / ex units).
+  if (presContext->HasPendingMediaQueryUpdates()) {
+    // So gotta flush.
+    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();