Bug 1334735 - Part 1: Move need style/flush flags from document to pres shell. r?bz draft
authorCameron McCormack <cam@mcc.id.au>
Tue, 07 Feb 2017 18:19:35 +0800
changeset 479820 cfc9317005af772987ffdc0e124331a9edbe17c7
parent 479651 af8a2573d0f1e9cc6f2ba0ab67d7a702a197f177
child 479821 5b542ee78cd33e581f5ca61bec0725886d1d0a40
push id44370
push userbmo:cam@mcc.id.au
push dateTue, 07 Feb 2017 10:20:07 +0000
reviewersbz
bugs1334735
milestone54.0a1
Bug 1334735 - Part 1: Move need style/flush flags from document to pres shell. r?bz MozReview-Commit-ID: VgDOiWnF4A
dom/animation/EffectCompositor.cpp
dom/base/nsDocument.cpp
dom/base/nsDocument.h
dom/base/nsIDocument.h
dom/smil/nsSMILAnimationController.cpp
dom/xbl/nsBindingManager.cpp
layout/base/PresShell.cpp
layout/base/RestyleManagerBase.cpp
layout/base/moz.build
layout/base/nsCSSFrameConstructor.cpp
layout/base/nsCSSFrameConstructor.h
layout/base/nsIPresShell.h
layout/base/nsIPresShellInlines.h
layout/base/nsPresContext.cpp
layout/style/nsAnimationManager.cpp
view/nsViewManager.cpp
--- a/dom/animation/EffectCompositor.cpp
+++ b/dom/animation/EffectCompositor.cpp
@@ -17,16 +17,17 @@
 #include "mozilla/LayerAnimationInfo.h"
 #include "mozilla/RestyleManagerHandle.h"
 #include "mozilla/RestyleManagerHandleInlines.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "nsComputedDOMStyle.h" // nsComputedDOMStyle::GetPresShellForContent
 #include "nsCSSPropertyIDSet.h"
 #include "nsCSSProps.h"
 #include "nsIPresShell.h"
+#include "nsIPresShellInlines.h"
 #include "nsLayoutUtils.h"
 #include "nsRuleNode.h" // For nsRuleNode::ComputePropertiesOverridingAnimation
 #include "nsRuleProcessorData.h" // For ElementRuleProcessorData etc.
 #include "nsTArray.h"
 #include <bitset>
 #include <initializer_list>
 
 using mozilla::dom::Animation;
@@ -258,17 +259,17 @@ EffectCompositor::RequestRestyle(dom::El
 
   auto& elementsToRestyle = mElementsToRestyle[aCascadeLevel];
   PseudoElementHashEntry::KeyType key = { aElement, aPseudoType };
 
   if (aRestyleType == RestyleType::Throttled) {
     if (!elementsToRestyle.Contains(key)) {
       elementsToRestyle.Put(key, false);
     }
-    mPresContext->Document()->SetNeedStyleFlush();
+    mPresContext->PresShell()->SetNeedStyleFlush();
   } else {
     // Get() returns 0 if the element is not found. It will also return
     // false if the element is found but does not have a pending restyle.
     bool hasPendingRestyle = elementsToRestyle.Get(key);
     if (!hasPendingRestyle) {
       PostRestyleForAnimation(aElement, aPseudoType, aCascadeLevel);
     }
     elementsToRestyle.Put(key, true);
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -249,16 +249,17 @@
 #include "nsISupportsPrimitives.h"
 #include "mozilla/StyleSetHandle.h"
 #include "mozilla/StyleSetHandleInlines.h"
 #include "mozilla/StyleSheet.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "mozilla/dom/SVGSVGElement.h"
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/TabGroup.h"
+#include "nsIPresShell.h"
 
 #include "mozilla/DocLoadingTimelineMarker.h"
 
 #include "nsISpeculativeConnect.h"
 
 #include "mozilla/MediaManager.h"
 #ifdef MOZ_WEBRTC
 #include "IPeerConnection.h"
@@ -1308,18 +1309,16 @@ nsIDocument::nsIDocument()
     mAllowDNSPrefetch(true),
     mIsStaticDocument(false),
     mCreatingStaticClone(false),
     mInUnlinkOrDeletion(false),
     mHasHadScriptHandlingObject(false),
     mIsBeingUsedAsImage(false),
     mIsSyntheticDocument(false),
     mHasLinksToUpdate(false),
-    mNeedLayoutFlush(false),
-    mNeedStyleFlush(false),
     mMayHaveDOMMutationObservers(false),
     mMayHaveAnimationObservers(false),
     mHasMixedActiveContentLoaded(false),
     mHasMixedActiveContentBlocked(false),
     mHasMixedDisplayContentLoaded(false),
     mHasMixedDisplayContentBlocked(false),
     mHasMixedContentObjectSubrequest(false),
     mHasCSP(false),
@@ -1387,17 +1386,16 @@ nsDocument::nsDocument(const char* aCont
   , mHeaderData(nullptr)
   , mIsGoingAway(false)
   , mInDestructor(false)
   , mMayHaveTitleElement(false)
   , mHasWarnedAboutBoxObjects(false)
   , mDelayFrameLoaderInitialization(false)
   , mSynchronousDOMContentLoaded(false)
   , mInXBLUpdate(false)
-  , mInFlush(false)
   , mParserAborted(false)
   , mCurrentOrientationAngle(0)
   , mCurrentOrientationType(OrientationType::Portrait_primary)
   , mSSApplicableStateNotificationPending(false)
   , mReportedUseCounters(false)
   , mStyleSetFilled(false)
   , mPendingFullscreenRequests(0)
   , mXMLDeclarationBits(0)
@@ -7935,37 +7933,22 @@ nsDocument::FlushPendingNotifications(Fl
   // correct size to determine the correct style.
   if (mParentDocument && IsSafeToFlush()) {
     FlushType parentType = aType;
     if (aType >= FlushType::Style)
       parentType = std::max(FlushType::Layout, aType);
     mParentDocument->FlushPendingNotifications(parentType);
   }
 
-  // We can optimize away getting our presshell and calling
-  // FlushPendingNotifications on it if we don't need a flush of the sort we're
-  // looking at.  The one exception is if mInFlush is true, because in that
-  // case we might have set mNeedStyleFlush and mNeedLayoutFlush to false
-  // already but the presshell hasn't actually done the corresponding work yet.
-  // So if mInFlush and reentering this code, we need to flush the presshell.
-  if (mNeedStyleFlush ||
-      (mNeedLayoutFlush && aType >= FlushType::InterruptibleLayout) ||
-      aType >= FlushType::Display ||
-      mInFlush) {
-    nsCOMPtr<nsIPresShell> shell = GetShell();
-    if (shell) {
-      mNeedStyleFlush = false;
-      mNeedLayoutFlush = mNeedLayoutFlush && (aType < FlushType::InterruptibleLayout);
-      // mInFlush is a bitfield, so can't us AutoRestore here.  But we
-      // need to keep track of multi-level reentry correctly, so need
-      // to restore the old mInFlush value.
-      bool oldInFlush = mInFlush;
-      mInFlush = true;
-      shell->FlushPendingNotifications(aType);
-      mInFlush = oldInFlush;
+  // Call nsIPresShell::NeedFlush (inline, non-virtual) to check whether we
+  // really need to flush the shell (virtual, and needs a strong reference).
+  if (nsIPresShell* shell = GetShell()) {
+    if (shell->NeedFlush(aType)) {
+      nsCOMPtr<nsIPresShell> presShell = shell;
+      presShell->FlushPendingNotifications(aType);
     }
   }
 }
 
 static bool
 Copy(nsIDocument* aDocument, void* aData)
 {
   nsTArray<nsCOMPtr<nsIDocument> >* resources =
@@ -12884,17 +12867,19 @@ nsIDocument::RebuildUserFontSet()
   if (!mGetUserFontSetCalled) {
     // We want to lazily build the user font set the first time it's
     // requested (so we don't force creation of rule cascades too
     // early), so don't do anything now.
     return;
   }
 
   mFontFaceSetDirty = true;
-  SetNeedStyleFlush();
+  if (nsIPresShell* shell = GetShell()) {
+    shell->SetNeedStyleFlush();
+  }
 
   // Somebody has already asked for the user font set, so we need to
   // post an event to rebuild it.  Setting the user font set to be dirty
   // and lazily rebuilding it isn't sufficient, since it is only the act
   // of rebuilding it that will trigger the style change reflow that
   // calls GetUserFontSet.  (This reflow causes rebuilding of text runs,
   // which starts font loads, whose completion causes another style
   // change reflow).
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -1440,20 +1440,16 @@ public:
   bool mHasWarnedAboutBoxObjects:1;
 
   bool mDelayFrameLoaderInitialization:1;
 
   bool mSynchronousDOMContentLoaded:1;
 
   bool mInXBLUpdate:1;
 
-  // Whether we're currently under a FlushPendingNotifications call to
-  // our presshell.  This is used to handle flush reentry correctly.
-  bool mInFlush:1;
-
   // Parser aborted. True if the parser of this document was forcibly
   // terminated instead of letting it finish at its own pace.
   bool mParserAborted:1;
 
   friend class nsCallRequestFullScreen;
 
   // ScreenOrientation "pending promise" as described by
   // http://www.w3.org/TR/screen-orientation/
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2472,30 +2472,16 @@ public:
                      bool asError = false,
                      const char16_t **aParams = nullptr,
                      uint32_t aParamsLength = 0) const;
 
   virtual void PostVisibilityUpdateEvent() = 0;
 
   bool IsSyntheticDocument() const { return mIsSyntheticDocument; }
 
-  void SetNeedLayoutFlush() {
-    mNeedLayoutFlush = true;
-    if (mDisplayDocument) {
-      mDisplayDocument->SetNeedLayoutFlush();
-    }
-  }
-
-  void SetNeedStyleFlush() {
-    mNeedStyleFlush = true;
-    if (mDisplayDocument) {
-      mDisplayDocument->SetNeedStyleFlush();
-    }
-  }
-
   // Note: nsIDocument is a sub-class of nsINode, which has a
   // SizeOfExcludingThis function.  However, because nsIDocument objects can
   // only appear at the top of the DOM tree, we have a specialized measurement
   // function which returns multiple sizes.
   virtual void DocAddSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const;
   // DocAddSizeOfIncludingThis doesn't need to be overridden by sub-classes
   // because nsIDocument inherits from nsINode;  see the comment above the
   // declaration of nsINode::SizeOfIncludingThis.
@@ -3119,22 +3105,16 @@ protected:
 
   // True is this document is synthetic : stand alone image, video, audio
   // file, etc.
   bool mIsSyntheticDocument : 1;
 
   // True if this document has links whose state needs updating
   bool mHasLinksToUpdate : 1;
 
-  // True if a layout flush might not be a no-op
-  bool mNeedLayoutFlush : 1;
-
-  // True if a style flush might not be a no-op
-  bool mNeedStyleFlush : 1;
-
   // True if a DOMMutationObserver is perhaps attached to a node in the document.
   bool mMayHaveDOMMutationObservers : 1;
 
   // True if an nsIAnimationObserver is perhaps attached to a node in the document.
   bool mMayHaveAnimationObservers : 1;
 
   // True if a document has loaded Mixed Active Script (see nsMixedContentBlocker.cpp)
   bool mHasMixedActiveContentLoaded : 1;
--- a/dom/smil/nsSMILAnimationController.cpp
+++ b/dom/smil/nsSMILAnimationController.cpp
@@ -11,16 +11,17 @@
 #include "nsITimer.h"
 #include "mozilla/dom/Element.h"
 #include "nsIDocument.h"
 #include "mozilla/dom/SVGAnimationElement.h"
 #include "nsSMILTimedElement.h"
 #include <algorithm>
 #include "mozilla/AutoRestore.h"
 #include "RestyleTracker.h"
+#include "nsIPresShell.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 //----------------------------------------------------------------------
 // nsSMILAnimationController implementation
 
 //----------------------------------------------------------------------
@@ -788,10 +789,12 @@ nsSMILAnimationController::GetRefreshDri
 
   nsPresContext* context = shell->GetPresContext();
   return context ? context->RefreshDriver() : nullptr;
 }
 
 void
 nsSMILAnimationController::FlagDocumentNeedsFlush()
 {
-  mDocument->SetNeedStyleFlush();
+  if (nsIPresShell* shell = mDocument->GetShell()) {
+    shell->SetNeedStyleFlush();
+  }
 }
--- a/dom/xbl/nsBindingManager.cpp
+++ b/dom/xbl/nsBindingManager.cpp
@@ -15,16 +15,17 @@
 #include "nsIChannel.h"
 #include "nsXPIDLString.h"
 #include "plstr.h"
 #include "nsIContent.h"
 #include "nsIDOMElement.h"
 #include "nsIDocument.h"
 #include "nsContentUtils.h"
 #include "nsIPresShell.h"
+#include "nsIPresShellInlines.h"
 #include "nsIXMLContentSink.h"
 #include "nsContentCID.h"
 #include "mozilla/dom/XMLDocument.h"
 #include "nsIStreamListener.h"
 #include "ChildIterator.h"
 #include "nsITimer.h"
 
 #include "nsXBLBinding.h"
@@ -333,17 +334,19 @@ nsBindingManager::AddToAttachedQueue(nsX
 
   // If we're in the middle of processing our queue already, don't
   // bother posting the event.
   if (!mProcessingAttachedStack && !mProcessAttachedQueueEvent) {
     PostProcessAttachedQueueEvent();
   }
 
   // Make sure that flushes will flush out the new items as needed.
-  mDocument->SetNeedStyleFlush();
+  if (nsIPresShell* shell = mDocument->GetShell()) {
+    shell->SetNeedStyleFlush();
+  }
 
   return NS_OK;
 
 }
 
 void
 nsBindingManager::PostProcessAttachedQueueEvent()
 {
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -791,26 +791,29 @@ nsIPresShell::nsIPresShell()
     , mIsThemeSupportDisabled(false)
     , mIsActive(false)
     , mFrozen(false)
     , mIsFirstPaint(false)
     , mObservesMutationsForPrint(false)
     , mReflowScheduled(false)
     , mSuppressInterruptibleReflows(false)
     , mScrollPositionClampingScrollPortSizeSet(false)
+    , mNeedLayoutFlush(true)
+    , mNeedStyleFlush(true)
     , mPresShellId(0)
     , mFontSizeInflationEmPerLine(0)
     , mFontSizeInflationMinTwips(0)
     , mFontSizeInflationLineThreshold(0)
     , mFontSizeInflationForceEnabled(false)
     , mFontSizeInflationDisabledInMasterProcess(false)
     , mFontSizeInflationEnabled(false)
     , mPaintingIsFrozen(false)
     , mFontSizeInflationEnabledIsDirty(false)
     , mIsNeverPainting(false)
+    , mInFlush(false)
   {}
 
 PresShell::PresShell()
   : mCaretEnabled(false)
 #ifdef DEBUG
   , mInVerifyReflow(false)
   , mCurrentReflowRoot(nullptr)
   , mUpdateCount(0)
@@ -2022,17 +2025,17 @@ PresShell::ResizeReflowIgnoreOverride(ns
                                                      this, 15,
                                                      nsITimer::TYPE_ONE_SHOT);
       }
     } else {
       RefPtr<nsRunnableMethod<PresShell> > resizeEvent =
         NewRunnableMethod(this, &PresShell::FireResizeEvent);
       if (NS_SUCCEEDED(NS_DispatchToCurrentThread(resizeEvent))) {
         mResizeEvent = resizeEvent;
-        mDocument->SetNeedStyleFlush();
+        SetNeedStyleFlush();
       }
     }
   }
 
   return NS_OK; //XXX this needs to be real. MMP
 }
 
 void
@@ -2768,17 +2771,17 @@ PresShell::FrameNeedsReflow(nsIFrame *aF
     // up the tree until we reach either a frame that's already dirty or
     // a reflow root.
     nsIFrame *f = subtreeRoot;
     for (;;) {
       if (FRAME_IS_REFLOW_ROOT(f) || !f->GetParent()) {
         // we've hit a reflow root or the root frame
         if (!wasDirty) {
           mDirtyRoots.AppendElement(f);
-          mDocument->SetNeedLayoutFlush();
+          SetNeedLayoutFlush();
         }
 #ifdef DEBUG
         else {
           VerifyHasDirtyRootAncestor(f);
         }
 #endif
 
         break;
@@ -3495,17 +3498,19 @@ PresShell::ScrollContentIntoView(nsICont
   data->mContentScrollHAxis = aHorizontal;
   data->mContentToScrollToFlags = aFlags;
   if (NS_FAILED(mContentToScrollTo->SetProperty(nsGkAtoms::scrolling, data,
                                                 nsINode::DeleteProperty<PresShell::ScrollIntoViewData>))) {
     mContentToScrollTo = nullptr;
   }
 
   // Flush layout and attempt to scroll in the process.
-  composedDoc->SetNeedLayoutFlush();
+  if (nsIPresShell* shell = composedDoc->GetShell()) {
+    shell->SetNeedLayoutFlush();
+  }
   composedDoc->FlushPendingNotifications(FlushType::InterruptibleLayout);
 
   // If mContentToScrollTo is non-null, that means we interrupted the reflow
   // (or suppressed it altogether because we're suppressing interruptible
   // flushes right now) and won't necessarily get the position correct, but do
   // a best-effort scroll here.  The other option would be to do this inside
   // FlushPendingNotifications, but I'm not sure the repeated scrolling that
   // could trigger if reflows keep getting interrupted would be more desirable
@@ -3722,19 +3727,17 @@ PresShell::ScheduleViewManagerFlush(Pain
     }
     return;
   }
 
   nsPresContext* presContext = GetPresContext();
   if (presContext) {
     presContext->RefreshDriver()->ScheduleViewManagerFlush();
   }
-  if (mDocument) {
-    mDocument->SetNeedLayoutFlush();
-  }
+  SetNeedLayoutFlush();
 }
 
 bool
 FlushLayoutRecursive(nsIDocument* aDocument,
                      void* aData = nullptr)
 {
   MOZ_ASSERT(!aData);
   nsCOMPtr<nsIDocument> kungFuDeathGrip(aDocument);
@@ -4062,17 +4065,17 @@ PresShell::FlushPendingNotifications(Flu
 }
 
 void
 PresShell::FlushPendingNotifications(mozilla::ChangesToFlush aFlush)
 {
   /**
    * VERY IMPORTANT: If you add some sort of new flushing to this
    * method, make sure to add the relevant SetNeedLayoutFlush or
-   * SetNeedStyleFlush calls on the document.
+   * SetNeedStyleFlush calls on the shell.
    */
   FlushType flushType = aFlush.mFlushType;
 
 #ifdef MOZ_GECKO_PROFILER
   static const EnumeratedArray<FlushType,
                                FlushType::Count,
                                const char*> flushTypeNames = {
     "",
@@ -4096,16 +4099,28 @@ PresShell::FlushPendingNotifications(moz
     NS_ASSERTION(!accService->IsProcessingRefreshDriverNotification(),
                  "Flush during accessible tree update!");
   }
 #endif
 #endif
 
   NS_ASSERTION(flushType >= FlushType::Frames, "Why did we get called?");
 
+  // Record that we are in a flush, so that our optimization in
+  // nsDocument::FlushPendingNotifications doesn't skip any re-entrant
+  // calls to us.  Otherwise, we might miss some needed flushes, since
+  // we clear mNeedStyleFlush / mNeedLayoutFlush here at the top of
+  // the function but we might not have done the work yet.
+  AutoRestore<bool> guard(mInFlush);
+  mInFlush = true;
+
+  mNeedStyleFlush = false;
+  mNeedLayoutFlush =
+    mNeedLayoutFlush && (flushType < FlushType::InterruptibleLayout);
+
   bool isSafeToFlush = IsSafeToFlush();
 
   // If layout could possibly trigger scripts, then it's only safe to flush if
   // it's safe to run script.
   bool hasHadScriptObject;
   if (mDocument->GetScriptHandlingObject(hasHadScriptObject) ||
       hasHadScriptObject) {
     isSafeToFlush = isSafeToFlush && nsContentUtils::IsSafeToRunScript();
@@ -4219,27 +4234,27 @@ PresShell::FlushPendingNotifications(moz
     if (flushType >= FlushType::Layout) {
       if (!mIsDestroying) {
         viewManager->UpdateWidgetGeometry();
       }
     }
   }
 
   if (!didStyleFlush && flushType >= FlushType::Style && !mIsDestroying) {
-    mDocument->SetNeedStyleFlush();
+    SetNeedStyleFlush();
   }
 
   if (!didLayoutFlush && !mIsDestroying &&
       (flushType >=
        (mSuppressInterruptibleReflows ? FlushType::Layout
                                       : FlushType::InterruptibleLayout))) {
     // We suppressed this flush due to mSuppressInterruptibleReflows or
-    // !isSafeToFlush, but the document thinks it doesn't
-    // need to flush anymore.  Let it know what's really going on.
-    mDocument->SetNeedLayoutFlush();
+    // !isSafeToFlush, but now we think we don't need to flush any more.
+    // Record what's really going on.
+    SetNeedLayoutFlush();
   }
 }
 
 void
 PresShell::CharacterDataChanged(nsIDocument *aDocument,
                                 nsIContent*  aContent,
                                 CharacterDataChangeInfo* aInfo)
 {
@@ -9289,17 +9304,17 @@ PresShell::DoReflow(nsIFrame* target, bo
         if (f == target) {
           break;
         }
       }
     }
 
     NS_ASSERTION(NS_SUBTREE_DIRTY(target), "Why is the target not dirty?");
     mDirtyRoots.AppendElement(target);
-    mDocument->SetNeedLayoutFlush();
+    SetNeedLayoutFlush();
 
     // Clear mFramesToDirty after we've done the NS_SUBTREE_DIRTY(target)
     // assertion so that if it fails it's easier to see what's going on.
 #ifdef NOISY_INTERRUPTIBLE_REFLOW
     printf("mFramesToDirty.Count() == %u\n", mFramesToDirty.Count());
 #endif /* NOISY_INTERRUPTIBLE_REFLOW */
     mFramesToDirty.Clear();
 
@@ -9426,18 +9441,18 @@ PresShell::ProcessReflowCommands(bool aI
 
       // If any new reflow commands were enqueued during the reflow, schedule
       // another reflow event to process them.  Note that we want to do this
       // after DidDoReflow(), since that method can change whether there are
       // dirty roots around by flushing, and there's no point in posting a
       // reflow event just to have the flush revoke it.
       if (!mDirtyRoots.IsEmpty()) {
         MaybeScheduleReflow();
-        // And tell our document that we might need flushing
-        mDocument->SetNeedLayoutFlush();
+        // And record that we might need flushing
+        SetNeedLayoutFlush();
       }
     }
   }
 
   if (!mIsDestroying && mShouldUnsuppressPainting &&
       mDirtyRoots.IsEmpty()) {
     // We only unlock if we're out of reflows.  It's pointless
     // to unlock if reflows are still pending, since reflows
--- a/layout/base/RestyleManagerBase.cpp
+++ b/layout/base/RestyleManagerBase.cpp
@@ -2,16 +2,17 @@
 /* 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/. */
 
 #include "mozilla/RestyleManagerBase.h"
 #include "mozilla/StyleSetHandleInlines.h"
 #include "nsIFrame.h"
+#include "nsIPresShellInlines.h"
 
 namespace mozilla {
 
 RestyleManagerBase::RestyleManagerBase(nsPresContext* aPresContext)
   : mPresContext(aPresContext)
   , mRestyleGeneration(1)
   , mHoverGeneration(0)
   , mObservingRefreshDriver(false)
@@ -213,17 +214,17 @@ RestyleManagerBase::PostRestyleEventInte
   if (!ObservingRefreshDriver() && !inRefresh) {
     SetObservingRefreshDriver(PresContext()->RefreshDriver()->
         AddStyleFlushObserver(presShell));
   }
 
   // Unconditionally flag our document as needing a flush.  The other
   // option here would be a dedicated boolean to track whether we need
   // to do so (set here and unset in ProcessPendingRestyles).
-  presShell->GetDocument()->SetNeedStyleFlush();
+  presShell->SetNeedStyleFlush();
 }
 
 /**
  * Frame construction helpers follow.
  */
 #ifdef DEBUG
 static bool gInApplyRenderingChangeToTree = false;
 #endif
--- a/layout/base/moz.build
+++ b/layout/base/moz.build
@@ -45,16 +45,17 @@ EXPORTS += [
     'nsFrameManager.h',
     'nsFrameManagerBase.h',
     'nsFrameTraversal.h',
     'nsIFrameTraversal.h',
     'nsILayoutDebugger.h',
     'nsILayoutHistoryState.h',
     'nsIPercentBSizeObserver.h',
     'nsIPresShell.h',
+    'nsIPresShellInlines.h',
     'nsIReflowCallback.h',
     'nsLayoutUtils.h',
     'nsPresArena.h',
     'nsPresArenaObjectList.h',
     'nsPresContext.h',
     'nsPresState.h',
     'nsRefreshDriver.h',
     'nsStyleChangeList.h',
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -116,16 +116,17 @@
 #include "nsMathMLParts.h"
 #include "mozilla/dom/SVGTests.h"
 #include "nsSVGUtils.h"
 
 #include "nsRefreshDriver.h"
 #include "nsRuleProcessorData.h"
 #include "nsTextNode.h"
 #include "ActiveLayerTracker.h"
+#include "nsIPresShellInlines.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 // An alias for convenience.
 static const nsIFrame::ChildListID kPrincipalList = nsIFrame::kPrincipalList;
 
 nsIFrame*
@@ -12914,8 +12915,28 @@ Iterator::DeleteItemsTo(const Iterator& 
     NS_ASSERTION(!IsDone(), "Ran off end of list?");
     FrameConstructionItem* item = mCurrent;
     Next();
     item->remove();
     mList.AdjustCountsForItem(item, -1);
     delete item;
   } while (*this != aEnd);
 }
+
+void
+nsCSSFrameConstructor::QuotesDirty()
+{
+  NS_PRECONDITION(mUpdateCount != 0, "Instant quote updates are bad news");
+  mQuotesDirty = true;
+  if (nsIPresShell* shell = mDocument->GetShell()) {
+    shell->SetNeedLayoutFlush();
+  }
+}
+
+void
+nsCSSFrameConstructor::CountersDirty()
+{
+  NS_PRECONDITION(mUpdateCount != 0, "Instant counter updates are bad news");
+  mCountersDirty = true;
+  if (nsIPresShell* shell = mDocument->GetShell()) {
+    shell->SetNeedLayoutFlush();
+  }
+}
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -18,16 +18,17 @@
 
 #include "nsCOMPtr.h"
 #include "nsILayoutHistoryState.h"
 #include "nsQuoteList.h"
 #include "nsCounterManager.h"
 #include "nsIAnonymousContentCreator.h"
 #include "nsFrameManager.h"
 #include "ScrollbarStyles.h"
+#include "nsIPresShellInlines.h"
 
 struct nsFrameItems;
 class nsStyleContext;
 struct nsStyleDisplay;
 struct nsGenConInitializer;
 
 class nsContainerFrame;
 class nsFirstLineFrame;
@@ -2056,27 +2057,18 @@ private:
   // see if aContent and aSibling are legitimate siblings due to restrictions
   // imposed by table columns
   // XXXbz this code is generally wrong, since the frame for aContent
   // may be constructed based on tag, not based on aDisplay!
   bool IsValidSibling(nsIFrame*              aSibling,
                       nsIContent*            aContent,
                       mozilla::StyleDisplay& aDisplay);
 
-  void QuotesDirty() {
-    NS_PRECONDITION(mUpdateCount != 0, "Instant quote updates are bad news");
-    mQuotesDirty = true;
-    mDocument->SetNeedLayoutFlush();
-  }
-
-  void CountersDirty() {
-    NS_PRECONDITION(mUpdateCount != 0, "Instant counter updates are bad news");
-    mCountersDirty = true;
-    mDocument->SetNeedLayoutFlush();
-  }
+  void QuotesDirty();
+  void CountersDirty();
 
   /**
    * Add the pair (aContent, aStyleContext) to the undisplayed items
    * in aList as needed.  This method enforces the invariant that all
    * style contexts in the undisplayed content map must be non-pseudo
    * contexts and also handles unbinding undisplayed generated content
    * as needed.
    */
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -581,16 +581,43 @@ public:
    * nsIDocument::FlushPendingNotifications.
    *
    * @param aType the type of notifications to flush
    */
   virtual void FlushPendingNotifications(mozilla::FlushType aType) = 0;
   virtual void FlushPendingNotifications(mozilla::ChangesToFlush aType) = 0;
 
   /**
+   * Whether we might need a flush for the given flush type.  If this
+   * function returns false, we definitely don't need to flush.
+   *
+   * @param aFlushType The flush type to check.  This must be
+   *   >= FlushType::Style.
+   */
+  bool NeedFlush(mozilla::FlushType aType) const
+  {
+    // We check mInFlush to handle re-entrant calls to FlushPendingNotifications
+    // by reporting that we always need a flush in that case.  Otherwise,
+    // we could end up missing needed flushes, since we clear mNeedStyleFlush
+    // and mNeedLayoutFlush at the top of FlushPendingNotifications.
+    //
+    // XXXheycam Could we just clear those flags after the work has been
+    // done instead?
+    MOZ_ASSERT(aType >= mozilla::FlushType::Style);
+    return mNeedStyleFlush ||
+           (mNeedLayoutFlush &&
+            aType >= mozilla::FlushType::InterruptibleLayout) ||
+           aType >= mozilla::FlushType::Display ||
+           mInFlush;
+  }
+
+  inline void SetNeedStyleFlush();
+  inline void SetNeedLayoutFlush();
+
+  /**
    * Callbacks will be called even if reflow itself fails for
    * some reason.
    */
   virtual nsresult PostReflowCallback(nsIReflowCallback* aCallback) = 0;
   virtual void CancelReflowCallback(nsIReflowCallback* aCallback) = 0;
 
   virtual void ClearFrameRefs(nsIFrame* aFrame) = 0;
 
@@ -1800,16 +1827,22 @@ protected:
 
   // If true, we have a reflow scheduled. Guaranteed to be false if
   // mReflowContinueTimer is non-null.
   bool                      mReflowScheduled : 1;
 
   bool                      mSuppressInterruptibleReflows : 1;
   bool                      mScrollPositionClampingScrollPortSizeSet : 1;
 
+  // True if a layout flush might not be a no-op
+  bool mNeedLayoutFlush : 1;
+
+  // True if a style flush might not be a no-op
+  bool mNeedStyleFlush : 1;
+
   uint32_t                  mPresShellId;
 
   // List of subtrees rooted at style scope roots that need to be restyled.
   // When a change to a scoped style sheet is made, we add the style scope
   // root to this array rather than setting mStylesHaveChanged = true, since
   // we know we don't need to restyle the whole document.  However, if in the
   // same update block we have already had other changes that require
   // the whole document to be restyled (i.e., mStylesHaveChanged is already
@@ -1830,13 +1863,17 @@ protected:
 
   // Dirty bit indicating that mFontSizeInflationEnabled needs to be recomputed.
   bool mFontSizeInflationEnabledIsDirty;
 
   // If a document belongs to an invisible DocShell, this flag must be set
   // to true, so we can avoid any paint calls for widget related to this
   // presshell.
   bool mIsNeverPainting;
+
+  // Whether we're currently under a FlushPendingNotifications.
+  // This is used to handle flush reentry correctly.
+  bool mInFlush;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIPresShell, NS_IPRESSHELL_IID)
 
 #endif /* nsIPresShell_h___ */
new file mode 100644
--- /dev/null
+++ b/layout/base/nsIPresShellInlines.h
@@ -0,0 +1,28 @@
+#ifndef nsIPresShellInlines_h
+#define nsIPresShellInlines_h
+
+#include "nsIDocument.h"
+
+void
+nsIPresShell::SetNeedLayoutFlush()
+{
+  mNeedLayoutFlush = true;
+  if (nsIDocument* doc = mDocument->GetDisplayDocument()) {
+    if (nsIPresShell* shell = doc->GetShell()) {
+      shell->mNeedLayoutFlush = true;
+    }
+  }
+}
+
+void
+nsIPresShell::SetNeedStyleFlush()
+{
+  mNeedStyleFlush = true;
+  if (nsIDocument* doc = mDocument->GetDisplayDocument()) {
+    if (nsIPresShell* shell = doc->GetShell()) {
+      shell->mNeedStyleFlush = true;
+    }
+  }
+}
+
+#endif // nsIPresShellInlines_h
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -2064,22 +2064,22 @@ nsPresContext::MediaFeatureValuesChanged
 }
 
 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) {
+  if (!mPendingMediaFeatureValuesChanged && mShell) {
     nsCOMPtr<nsIRunnable> ev =
       NewRunnableMethod(this, &nsPresContext::HandleMediaFeatureValuesChangedEvent);
     if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev))) {
       mPendingMediaFeatureValuesChanged = true;
-      mDocument->SetNeedStyleFlush();
+      mShell->SetNeedStyleFlush();
     }
   }
 }
 
 void
 nsPresContext::HandleMediaFeatureValuesChangedEvent()
 {
   // Null-check mShell in case the shell has been destroyed (and the
@@ -2253,17 +2253,19 @@ void
 nsPresContext::RebuildCounterStyles()
 {
   if (mCounterStyleManager->IsInitial()) {
     // Still in its initial state, no need to reset.
     return;
   }
 
   mCounterStylesDirty = true;
-  mDocument->SetNeedStyleFlush();
+  if (mShell) {
+    mShell->SetNeedStyleFlush();
+  }
   if (!mPostedFlushCounterStyles) {
     nsCOMPtr<nsIRunnable> ev =
       NewRunnableMethod(this, &nsPresContext::HandleRebuildCounterStyles);
     if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev))) {
       mPostedFlushCounterStyles = true;
     }
   }
 }
--- a/layout/style/nsAnimationManager.cpp
+++ b/layout/style/nsAnimationManager.cpp
@@ -450,17 +450,17 @@ nsAnimationManager::UpdateAnimations(nsS
     newAnimations[newAnimIdx]->CancelFromStyle();
   }
 
   // We don't actually dispatch the pending events now.  We'll either
   // dispatch them the next time we get a refresh driver notification
   // or the next time somebody calls
   // nsPresShell::FlushPendingNotifications.
   if (mEventDispatcher.HasQueuedEvents()) {
-    mPresContext->Document()->SetNeedStyleFlush();
+    mPresContext->PresShell()->SetNeedStyleFlush();
   }
 }
 
 void
 nsAnimationManager::StopAnimationsForElement(
   mozilla::dom::Element* aElement,
   mozilla::CSSPseudoElementType aPseudoType)
 {
--- a/view/nsViewManager.cpp
+++ b/view/nsViewManager.cpp
@@ -224,20 +224,19 @@ nsViewManager::SetWindowDimensions(nscoo
         // request a resize reflow (which would correct it). See bug 617076.
         mDelayedResize = nsSize(aWidth, aHeight);
         FlushDelayedResize(false);
       }
       mDelayedResize.SizeTo(NSCOORD_NONE, NSCOORD_NONE);
       DoSetWindowDimensions(aWidth, aHeight);
     } else {
       mDelayedResize.SizeTo(aWidth, aHeight);
-      if (mPresShell && mPresShell->GetDocument()) {
-        nsIDocument* doc = mPresShell->GetDocument();
-        doc->SetNeedStyleFlush();
-        doc->SetNeedLayoutFlush();
+      if (mPresShell) {
+        mPresShell->SetNeedStyleFlush();
+        mPresShell->SetNeedLayoutFlush();
       }
     }
   }
 }
 
 void
 nsViewManager::FlushDelayedResize(bool aDoReflow)
 {