Bug 67752. Implement interruptible reflow. r=roc,dbaron
☠☠ backed out by f58de2414f51 ☠ ☠
authorBoris Zbarsky <bzbarsky@mit.edu>
Tue, 21 Apr 2009 19:53:52 -0400
changeset 27590 6a452e522e0775c1993c41085fb2851acd3aaf5b
parent 27589 b4dd3013fea637d93f359c11e294519d545c6f9e
child 27591 5617c22ea9aca301a6515dab91ab9a7ab7bb88dd
child 27600 f58de2414f51ac92548b9b52e2ee85abc9b35456
push id6634
push userbzbarsky@mozilla.com
push dateTue, 21 Apr 2009 23:54:32 +0000
treeherdermozilla-central@6a452e522e07 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc, dbaron
bugs67752
milestone1.9.2a1pre
Bug 67752. Implement interruptible reflow. r=roc,dbaron
content/base/public/mozFlushType.h
content/base/src/nsDocument.cpp
content/events/src/nsEventStateManager.cpp
content/html/content/src/nsGenericHTMLElement.cpp
content/html/document/src/nsHTMLContentSink.cpp
content/xml/document/src/nsXMLContentSink.cpp
layout/base/nsIPresShell.h
layout/base/nsPresContext.cpp
layout/base/nsPresContext.h
layout/base/nsPresShell.cpp
layout/generic/nsAbsoluteContainingBlock.cpp
layout/generic/nsAbsoluteContainingBlock.h
layout/generic/nsBlockFrame.cpp
layout/generic/nsBlockFrame.h
layout/generic/nsColumnSetFrame.cpp
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsLineLayout.cpp
layout/reftests/bugs/67752-1-ref.html
layout/reftests/bugs/67752-1.html
layout/reftests/bugs/67752-2-ref.html
layout/reftests/bugs/67752-2.html
layout/reftests/bugs/reftest.list
layout/svg/base/src/nsSVGForeignObjectFrame.cpp
widget/public/nsIWidget.h
widget/src/cocoa/nsAppShell.mm
widget/src/cocoa/nsChildView.h
widget/src/cocoa/nsChildView.mm
widget/src/cocoa/nsCocoaWindow.h
widget/src/cocoa/nsCocoaWindow.mm
widget/src/gtk2/nsWindow.cpp
widget/src/gtk2/nsWindow.h
widget/src/os2/nsWindow.cpp
widget/src/os2/nsWindow.h
widget/src/windows/nsWindow.cpp
widget/src/windows/nsWindow.h
widget/src/xpwidgets/nsBaseWidget.cpp
widget/src/xpwidgets/nsBaseWidget.h
--- a/content/base/public/mozFlushType.h
+++ b/content/base/public/mozFlushType.h
@@ -43,13 +43,17 @@
  */
 enum mozFlushType {
   Flush_Content          = 1, /* flush the content model construction */
   Flush_ContentAndNotify = 2, /* As above, plus flush the frame model
                                  construction and other nsIMutationObserver
                                  notifications. */
   Flush_Style            = 3, /* As above, plus flush style reresolution */
   Flush_Frames           = Flush_Style,
-  Flush_Layout           = 4, /* As above, plus flush reflow */
-  Flush_Display          = 5  /* As above, plus flush painting */
+  Flush_InterruptibleLayout = 4, /* As above, plus flush reflow,
+                                    but allow it to be interrupted (so
+                                    an incomplete layout may result) */
+  Flush_Layout           = 5, /* As above, but layout must run to
+                                 completion */
+  Flush_Display          = 6  /* As above, plus flush painting */
 };
 
 #endif /* mozFlushType_h___ */
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -6252,18 +6252,18 @@ nsDocument::FlushPendingNotifications(mo
   // like resizes of our frame's widget, which we can't handle while flushing
   // is unsafe.
   // Since media queries mean that a size change of our container can
   // affect style, we need to promote a style flush on ourself to a
   // layout flush on our parent, since we need our container to be the
   // correct size to determine the correct style.
   if (mParentDocument && IsSafeToFlush()) {
     mozFlushType parentType = aType;
-    if (aType == Flush_Style)
-      parentType = Flush_Layout;
+    if (aType >= Flush_Style)
+      parentType = PR_MAX(Flush_Layout, aType);
     mParentDocument->FlushPendingNotifications(parentType);
   }
 
   nsPresShellIterator iter(this);
   nsCOMPtr<nsIPresShell> shell;
   while ((shell = iter.GetNextShell())) {
     shell->FlushPendingNotifications(aType);
   }
--- a/content/events/src/nsEventStateManager.cpp
+++ b/content/events/src/nsEventStateManager.cpp
@@ -5744,17 +5744,17 @@ nsEventStateManager::EnsureDocument(nsIP
 }
 
 void
 nsEventStateManager::FlushPendingEvents(nsPresContext* aPresContext)
 {
   NS_PRECONDITION(nsnull != aPresContext, "nsnull ptr");
   nsIPresShell *shell = aPresContext->GetPresShell();
   if (shell) {
-    shell->FlushPendingNotifications(Flush_Display);
+    shell->FlushPendingNotifications(Flush_InterruptibleLayout);
   }
 }
 
 nsresult
 nsEventStateManager::GetDocSelectionLocation(nsIContent **aStartContent,
                                              nsIContent **aEndContent,
                                              nsIFrame **aStartFrame,
                                              PRUint32* aStartOffset)
--- a/content/html/content/src/nsGenericHTMLElement.cpp
+++ b/content/html/content/src/nsGenericHTMLElement.cpp
@@ -1303,19 +1303,18 @@ nsGenericHTMLElement::GetAttributeMappin
 
 // static
 nsIFormControlFrame*
 nsGenericHTMLElement::GetFormControlFrameFor(nsIContent* aContent,
                                              nsIDocument* aDocument,
                                              PRBool aFlushContent)
 {
   if (aFlushContent) {
-    // Cause a flush of content, so we get up-to-date frame
-    // information
-    aDocument->FlushPendingNotifications(Flush_Layout);
+    // Cause a flush of the frames, so we get up-to-date frame information
+    aDocument->FlushPendingNotifications(Flush_Frames);
   }
   nsIFrame* frame = GetPrimaryFrameFor(aContent, aDocument);
   if (frame) {
     nsIFormControlFrame* form_frame = do_QueryFrame(frame);
     if (form_frame) {
       return form_frame;
     }
 
@@ -2691,17 +2690,20 @@ nsGenericHTMLFormElement::IntrinsicState
   return state;
 }
 
 void
 nsGenericHTMLFormElement::SetFocusAndScrollIntoView(nsPresContext* aPresContext)
 {
   nsIEventStateManager *esm = aPresContext->EventStateManager();
   if (esm->SetContentState(this, NS_EVENT_STATE_FOCUS)) {
-    nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_TRUE);
+    // XXXldb once bug 43114 is fixed, we don't need to flush here.
+    aPresContext->Document()->
+      FlushPendingNotifications(Flush_InterruptibleLayout);
+    nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_FALSE);
     if (formControlFrame) {
       formControlFrame->SetFocus(PR_TRUE, PR_TRUE);
       nsCOMPtr<nsIPresShell> presShell = aPresContext->GetPresShell();
       if (presShell) {
         presShell->ScrollContentIntoView(this, NS_PRESSHELL_SCROLL_IF_NOT_VISIBLE,
                                          NS_PRESSHELL_SCROLL_IF_NOT_VISIBLE);
       }
     }
--- a/content/html/document/src/nsHTMLContentSink.cpp
+++ b/content/html/document/src/nsHTMLContentSink.cpp
@@ -3195,17 +3195,17 @@ HTMLContentSink::FlushPendingNotificatio
   // (since we aren't reentrant)
   if (!mInNotification) {
     if (aType >= Flush_ContentAndNotify) {
       FlushTags();
     }
     else if (mCurrentContext) {
       mCurrentContext->FlushText();
     }
-    if (aType >= Flush_Layout) {
+    if (aType >= Flush_InterruptibleLayout) {
       // Make sure that layout has started so that the reflow flush
       // will actually happen.
       StartLayout(PR_TRUE);
     }
   }
 }
 
 nsresult
--- a/content/xml/document/src/nsXMLContentSink.cpp
+++ b/content/xml/document/src/nsXMLContentSink.cpp
@@ -1648,17 +1648,17 @@ nsXMLContentSink::FlushPendingNotificati
   // (since we aren't reentrant)
   if (!mInNotification) {
     if (aType >= Flush_ContentAndNotify) {
       FlushTags();
     }
     else {
       FlushText(PR_FALSE);
     }
-    if (aType >= Flush_Layout) {
+    if (aType >= Flush_InterruptibleLayout) {
       // Make sure that layout has started so that the reflow flush
       // will actually happen.
       MaybeStartLayout(PR_TRUE);
     }
   }
 }
 
 /**
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -96,20 +96,20 @@ class nsWeakFrame;
 class nsIScrollableFrame;
 class gfxASurface;
 class gfxContext;
 class nsPIDOMEventTarget;
 
 typedef short SelectionType;
 typedef PRUint32 nsFrameState;
 
-// fa7f090d-b19a-4ef8-9552-82992a3b4a83
+// 4fb87dae-8986-429f-b6ba-f040750e3ee8
 #define NS_IPRESSHELL_IID \
-{ 0xfa7f090d, 0xb19a, 0x4ef8, \
-  { 0x95, 0x52, 0x82, 0x99, 0x2a, 0x3b, 0x4a, 0x83 } }
+  { 0x4fb87dae, 0x8986, 0x429f, \
+    { 0xb6, 0xba, 0xf0, 0x40, 0x75, 0x0e, 0x3e, 0xe8 } }
 
 // Constants for ScrollContentIntoView() function
 #define NS_PRESSHELL_SCROLL_TOP      0
 #define NS_PRESSHELL_SCROLL_BOTTOM   100
 #define NS_PRESSHELL_SCROLL_LEFT     0
 #define NS_PRESSHELL_SCROLL_RIGHT    100
 #define NS_PRESSHELL_SCROLL_CENTER   50
 #define NS_PRESSHELL_SCROLL_ANYWHERE -1
@@ -119,16 +119,18 @@ typedef PRUint32 nsFrameState;
 #define VERIFY_REFLOW_ON              0x01
 #define VERIFY_REFLOW_NOISY           0x02
 #define VERIFY_REFLOW_ALL             0x04
 #define VERIFY_REFLOW_DUMP_COMMANDS   0x08
 #define VERIFY_REFLOW_NOISY_RC        0x10
 #define VERIFY_REFLOW_REALLY_NOISY_RC 0x20
 #define VERIFY_REFLOW_DURING_RESIZE_REFLOW  0x40
 
+#undef NOISY_INTERRUPTIBLE_REFLOW
+
 /**
  * Presentation shell interface. Presentation shells are the
  * controlling point for managing the presentation of a document. The
  * presentation shell holds a live reference to the document, the
  * presentation context, the style manager, the style set and the root
  * frame. <p>
  *
  * When this object is Release'd, it will release the document, the
@@ -362,16 +364,29 @@ public:
     eResize,     // don't mark any intrinsic widths dirty
     eTreeChange, // mark intrinsic widths dirty on aFrame and its ancestors
     eStyleChange // Do eTreeChange, plus all of aFrame's descendants
   };
   NS_IMETHOD FrameNeedsReflow(nsIFrame *aFrame,
                               IntrinsicDirty aIntrinsicDirty,
                               nsFrameState aBitToAdd) = 0;
 
+  /**
+   * Tell the presshell that the given frame's reflow was interrupted.  This
+   * will mark as having dirty children a path from the given frame (inclusive)
+   * to the nearest ancestor with a dirty subtree, or to the reflow root
+   * currently being reflowed if no such ancestor exists (inclusive).  This is
+   * to be done immediately after reflow of the current reflow root completes.
+   * This method must only be called during reflow, and the frame it's being
+   * called on must be in the process of being reflowed when it's called.  This
+   * method doesn't mark any intrinsic widths dirty and doesn't add any bits
+   * other than NS_FRAME_HAS_DIRTY_CHILDREN.
+   */
+  NS_IMETHOD_(void) FrameNeedsToContinueReflow(nsIFrame *aFrame) = 0;
+
   NS_IMETHOD CancelAllPendingReflows() = 0;
 
   /**
    * Recreates the frames for a node
    */
   NS_IMETHOD RecreateFramesFor(nsIContent* aContent) = 0;
 
   void PostRecreateFramesFor(nsIContent* aContent);
@@ -456,17 +471,17 @@ public:
    *                  is placed at the point "aVPercent" across the visible area.
    *                  A value of 50 (NS_PRESSHELL_SCROLL_CENTER) centers the frame
    *                  horizontally . A value of NS_PRESSHELL_SCROLL_ANYWHERE means move
    *                  the frame the minimum amount necessary in order for the entire
    *                  frame to be visible horizontally (if possible)
    */
   NS_IMETHOD ScrollContentIntoView(nsIContent* aContent,
                                    PRIntn      aVPercent,
-                                   PRIntn      aHPercent) const = 0;
+                                   PRIntn      aHPercent) = 0;
 
   /**
    * Suppress notification of the frame manager that frames are
    * being destroyed.
    */
   NS_IMETHOD SetIgnoreFrameDestruction(PRBool aIgnore) = 0;
 
   /**
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -85,16 +85,18 @@
 #include "nsRuleNode.h"
 #include "nsEventDispatcher.h"
 #include "gfxUserFontSet.h"
 #include "gfxPlatform.h"
 #include "nsCSSRules.h"
 #include "nsFontFaceLoader.h"
 #include "nsIEventListenerManager.h"
 #include "nsStyleStructInlines.h"
+#include "nsIAppShell.h"
+#include "prenv.h"
 
 #ifdef MOZ_SMIL
 #include "nsSMILAnimationController.h"
 #endif // MOZ_SMIL
 
 #ifdef IBMBIDI
 #include "nsBidiPresUtils.h"
 #endif // IBMBIDI
@@ -2022,8 +2024,130 @@ nsPresContext::NotifyInvalidation(const 
   r->SimplifyOutward(10);
 }
 
 PRBool
 nsPresContext::HasCachedStyleData()
 {
   return mShell && mShell->StyleSet()->HasCachedStyleData();
 }
+
+static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
+static PRBool sGotInterruptEnv = PR_FALSE;
+static PRUint32 sInterruptSeed = 1;
+static PRUint32 sInterruptChecksToSkip = 200;
+enum InterruptMode {
+  ModeRandom,
+  ModeCounter,
+  ModeEvent
+};
+static InterruptMode sInterruptMode = ModeEvent;
+static PRUint32 sInterruptCounter;
+static PRUint32 sInterruptMaxCounter = 10;
+
+static void GetInterruptEnv()
+{
+  char *ev = PR_GetEnv("GECKO_REFLOW_INTERRUPT_MODE");
+  if (ev) {
+#ifndef XP_WIN
+    if (PL_strcasecmp(ev, "random") == 0) {
+      ev = PR_GetEnv("GECKO_REFLOW_INTERRUPT_SEED");
+      if (ev) {
+        sInterruptSeed = atoi(ev);
+      }
+      srandom(sInterruptSeed);
+      sInterruptMode = ModeRandom;
+    } else
+#endif
+      if (PL_strcasecmp(ev, "counter") == 0) {
+      ev = PR_GetEnv("GECKO_REFLOW_INTERRUPT_FREQUENCY");
+      if (ev) {
+        sInterruptMaxCounter = atoi(ev);
+      }
+      sInterruptCounter = 0;
+      sInterruptMode = ModeCounter;
+    }
+  }
+  ev = PR_GetEnv("GECKO_REFLOW_INTERRUPT_CHECKS_TO_SKIP");
+  if (ev) {
+    sInterruptChecksToSkip = atoi(ev);
+  }
+}
+
+PRBool
+nsPresContext::HavePendingInputEvent()
+{
+  switch (sInterruptMode) {
+#ifndef XP_WIN
+    case ModeRandom:
+      return (random() & 1);
+#endif
+    case ModeCounter:
+      if (sInterruptCounter < sInterruptMaxCounter) {
+        ++sInterruptCounter;
+        return PR_FALSE;
+      }
+      sInterruptCounter = 0;
+      return PR_TRUE;
+    default:
+    case ModeEvent: {
+      nsIFrame* f = PresShell()->GetRootFrame();
+      if (f) {
+        nsIWidget* w = f->GetWindow();
+        if (w) {
+          return w->HasPendingInputEvent();
+        }
+      }
+      return PR_FALSE;
+    }
+  }
+}
+
+void
+nsPresContext::ReflowStarted(PRBool aInterruptible)
+{
+  // We don't support interrupting in paginated contexts, since page
+  // sequences only handle initial reflow
+  mInterruptsEnabled = aInterruptible && !IsPaginated();
+
+  // Don't set mHasPendingInterrupt based on HavePendingInputEvent() here.  If
+  // we ever change that, then we need to update the code in
+  // PresShell::DoReflow to only add the just-reflown root to dirty roots if
+  // it's actually dirty.  Otherwise we can end up adding a root that has no
+  // interruptible descendants, just because we detected an interrupt at reflow
+  // start.
+  mHasPendingInterrupt = PR_FALSE;
+
+  mInterruptChecksToSkip = sInterruptChecksToSkip;
+}
+
+PRBool
+nsPresContext::CheckForInterrupt(nsIFrame* aFrame)
+{
+  if (mHasPendingInterrupt) {
+    mShell->FrameNeedsToContinueReflow(aFrame);
+    return PR_TRUE;
+  }
+
+  if (!sGotInterruptEnv) {
+    sGotInterruptEnv = PR_TRUE;
+    GetInterruptEnv();
+  }
+
+  if (!mInterruptsEnabled) {
+    return PR_FALSE;
+  }
+
+  if (mInterruptChecksToSkip > 0) {
+    --mInterruptChecksToSkip;
+    return PR_FALSE;
+  }
+  mInterruptChecksToSkip = sInterruptChecksToSkip;
+
+  mHasPendingInterrupt = HavePendingInputEvent();
+  if (mHasPendingInterrupt) {
+#ifdef NOISY_INTERRUPTIBLE_REFLOW
+    printf("*** DETECTED pending interrupt (time=%lld)\n", PR_Now());
+#endif /* NOISY_INTERRUPTIBLE_REFLOW */
+    mShell->FrameNeedsToContinueReflow(aFrame);
+  }
+  return mHasPendingInterrupt;
+}
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -801,16 +801,68 @@ public:
     return !mSameDocDirtyRegion.IsEmpty() || !mCrossDocDirtyRegion.IsEmpty();
   }
 
   void ClearMozAfterPaintEvents() {
     mSameDocDirtyRegion.SetEmpty();
     mCrossDocDirtyRegion.SetEmpty();
   }
 
+  /**
+   * Notify the prescontext that the presshell is about to reflow a reflow root.
+   * The single argument indicates whether this reflow should be interruptible.
+   * If aInterruptible is false then CheckForInterrupt and HasPendingInterrupt
+   * will always return false. If aInterruptible is true then CheckForInterrupt
+   * will return true when a pending event is detected.  This is for use by the
+   * presshell only.  Reflow code wanting to prevent interrupts should use
+   * InterruptPreventer.
+   */
+  void ReflowStarted(PRBool aInterruptible);
+
+  /**
+   * A class that can be used to temporarily disable reflow interruption.
+   */
+  class InterruptPreventer;
+  friend class InterruptPreventer;
+  class NS_STACK_CLASS InterruptPreventer {
+  public:
+    InterruptPreventer(nsPresContext* aCtx) :
+      mCtx(aCtx),
+      mInterruptsEnabled(aCtx->mInterruptsEnabled),
+      mHasPendingInterrupt(aCtx->mHasPendingInterrupt)
+    {
+      mCtx->mInterruptsEnabled = PR_FALSE;
+      mCtx->mHasPendingInterrupt = PR_FALSE;
+    }
+    ~InterruptPreventer() {
+      mCtx->mInterruptsEnabled = mInterruptsEnabled;
+      mCtx->mHasPendingInterrupt = mHasPendingInterrupt;
+    }
+
+  private:
+    nsPresContext* mCtx;
+    PRBool mInterruptsEnabled;
+    PRBool mHasPendingInterrupt;
+  };
+    
+  /**
+   * Check for interrupts. This may return true if a pending event is
+   * detected. Once it has returned true, it will keep returning true until
+   * SetInterruptState is called again.  In all cases where returns true, the
+   * passed-in frame (which should be the frame whose reflow will be
+   * interrupted if true is returend) will be passed to
+   * nsIPresShell::FrameNeedsToContinueReflow.
+   */
+  PRBool CheckForInterrupt(nsIFrame* aFrame);
+  /**
+   * Returns true if CheckForInterrupt has returned true since the last
+   * SetInterruptState. Cannot itself trigger an interrupt check.
+   */
+  PRBool HasPendingInterrupt() { return mHasPendingInterrupt; }
+
 protected:
   friend class nsRunnableMethod<nsPresContext>;
   NS_HIDDEN_(void) ThemeChangedInternal();
   NS_HIDDEN_(void) SysColorChangedInternal();
 
   NS_HIDDEN_(void) SetImgAnimations(nsIContent *aParent, PRUint16 aMode);
 #ifdef MOZ_SMIL
   NS_HIDDEN_(void) SetSMILAnimations(nsIDocument *aDoc, PRUint16 aNewMode,
@@ -829,16 +881,18 @@ protected:
 
   NS_HIDDEN_(void) UpdateCharSet(const nsAFlatCString& aCharSet);
 
   void HandleRebuildUserFontSet() {
     mPostedFlushUserFontSet = PR_FALSE;
     FlushUserFontSet();
   }
 
+  PRBool HavePendingInputEvent();
+
   // Can't be inline because we can't include nsStyleSet.h.
   PRBool HasCachedStyleData();
 
   // IMPORTANT: The ownership implicit in the following member variables
   // has been explicitly checked.  If you add any members to this class,
   // please make the ownership explicit (pinkerton, scc).
   
   nsPresContextType     mType;
@@ -916,16 +970,20 @@ protected:
   nsFont                mDefaultSerifFont;
   nsFont                mDefaultSansSerifFont;
   nsFont                mDefaultMonospaceFont;
   nsFont                mDefaultCursiveFont;
   nsFont                mDefaultFantasyFont;
 
   nscoord               mBorderWidthTable[3];
 
+  PRUint32              mInterruptChecksToSkip;
+
+  unsigned              mHasPendingInterrupt : 1;
+  unsigned              mInterruptsEnabled : 1;
   unsigned              mUseDocumentFonts : 1;
   unsigned              mUseDocumentColors : 1;
   unsigned              mUnderlineLinks : 1;
   unsigned              mUseFocusColors : 1;
   unsigned              mFocusRingOnAnything : 1;
   unsigned              mFocusRingStyle : 1;
   unsigned              mDrawImageBackground : 1;
   unsigned              mDrawColorBackground : 1;
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -929,16 +929,17 @@ public:
   NS_IMETHOD GetPageSequenceFrame(nsIPageSequenceFrame** aResult) const;
   virtual NS_HIDDEN_(nsIFrame*) GetPrimaryFrameFor(nsIContent* aContent) const;
   virtual NS_HIDDEN_(nsIFrame*) GetRealPrimaryFrameFor(nsIContent* aContent) const;
 
   NS_IMETHOD GetPlaceholderFrameFor(nsIFrame*  aFrame,
                                     nsIFrame** aPlaceholderFrame) const;
   NS_IMETHOD FrameNeedsReflow(nsIFrame *aFrame, IntrinsicDirty aIntrinsicDirty,
                               nsFrameState aBitToAdd);
+  NS_IMETHOD_(void) FrameNeedsToContinueReflow(nsIFrame *aFrame);
   NS_IMETHOD CancelAllPendingReflows();
   NS_IMETHOD IsSafeToFlush(PRBool& aIsSafeToFlush);
   NS_IMETHOD FlushPendingNotifications(mozFlushType aType);
 
   /**
    * Recreates the frames for a node
    */
   NS_IMETHOD RecreateFramesFor(nsIContent* aContent);
@@ -952,17 +953,17 @@ public:
   NS_IMETHOD ClearFrameRefs(nsIFrame* aFrame);
   NS_IMETHOD CreateRenderingContext(nsIFrame *aFrame,
                                     nsIRenderingContext** aContext);
   NS_IMETHOD GoToAnchor(const nsAString& aAnchorName, PRBool aScroll);
   NS_IMETHOD ScrollToAnchor();
 
   NS_IMETHOD ScrollContentIntoView(nsIContent* aContent,
                                    PRIntn      aVPercent,
-                                   PRIntn      aHPercent) const;
+                                   PRIntn      aHPercent);
 
   NS_IMETHOD SetIgnoreFrameDestruction(PRBool aIgnore);
   NS_IMETHOD NotifyDestroyingFrame(nsIFrame* aFrame);
   
   NS_IMETHOD DoCopy();
   NS_IMETHOD GetSelectionForCopy(nsISelection** outSelection);
 
   NS_IMETHOD GetLinkLocation(nsIDOMNode* aNode, nsAString& aLocationString);
@@ -1128,33 +1129,41 @@ public:
   static PRLogModuleInfo* gLog;
 #endif
 
   NS_IMETHOD DisableNonTestMouseEvents(PRBool aDisable);
 
 protected:
   virtual ~PresShell();
 
-  void HandlePostedReflowCallbacks();
+  void HandlePostedReflowCallbacks(PRBool aInterruptible);
   void CancelPostedReflowCallbacks();
 
   void UnsuppressAndInvalidate();
 
   void     WillDoReflow();
-  void     DidDoReflow();
-  nsresult ProcessReflowCommands(PRBool aInterruptible);
+  void     DidDoReflow(PRBool aInterruptible);
+  // ProcessReflowCommands returns whether we processed all our dirty roots
+  // without interruptions.
+  PRBool   ProcessReflowCommands(PRBool aInterruptible);
   void     ClearReflowEventStatus();
   void     PostReflowEvent();
-  
-  void DoReflow(nsIFrame* aFrame);
+
+  // DoReflow returns whether the reflow finished without interruption
+  PRBool DoReflow(nsIFrame* aFrame, PRBool aInterruptible);
 #ifdef DEBUG
   void DoVerifyReflow();
   void VerifyHasDirtyRootAncestor(nsIFrame* aFrame);
 #endif
 
+  // Helper for ScrollContentIntoView
+  nsresult DoScrollContentIntoView(nsIContent* aContent,
+                                   PRIntn      aVPercent,
+                                   PRIntn      aHPercent);
+
   friend class nsPresShellEventCB;
 
   class ReflowEvent;
   friend class ReflowEvent;
 
   class ReflowEvent : public nsRunnable {
   public:
     NS_DECL_NSIRUNNABLE
@@ -1228,21 +1237,16 @@ protected:
   void RemoveSheet(nsStyleSet::sheetType aType, nsISupports* aSheet);
 
   // Hide a view if it is a popup
   void HideViewIfPopup(nsIView* aView);
 
   // Utility method to restore the root scrollframe state
   void RestoreRootScrollPosition();
 
-  // Method to handle actually flushing.  This allows the caller to control
-  // whether the reflow flush (if any) should be interruptible.
-  nsresult DoFlushPendingNotifications(mozFlushType aType,
-                                       PRBool aInterruptibleReflow);
-
   nsCOMPtr<nsICSSStyleSheet> mPrefStyleSheet; // mStyleSet owns it but we
                                               // maintain a ref, may be null
 #ifdef DEBUG
   PRUint32                  mUpdateCount;
 #endif
   // reflow roots that need to be reflowed, as both a queue and a hashtable
   nsTArray<nsIFrame*> mDirtyRoots;
 
@@ -1263,32 +1267,53 @@ protected:
   nsRefPtr<nsCaret>             mOriginalCaret;
   PRInt16                       mSelectionFlags;
   FrameArena                    mFrameArena;
   StackArena                    mStackArena;
   nsCOMPtr<nsIDragService>      mDragService;
   
   nsRevocableEventPtr<ReflowEvent> mReflowEvent;
 
+#ifdef DEBUG
+  // The reflow root under which we're currently reflowing.  Null when
+  // not in reflow.
+  nsIFrame* mCurrentReflowRoot;
+#endif
+
+  // Set of frames that we should mark with NS_FRAME_HAS_DIRTY_CHILDREN after
+  // we finish reflowing mCurrentReflowRoot.
+  nsTHashtable< nsPtrHashKey<nsIFrame> > mFramesToDirty;
+
+  // Information needed to properly handle scrolling content into view if the
+  // pre-scroll reflow flush can be interrupted.  mContentToScrollTo is
+  // non-null between the initial scroll attempt and the first time we finish
+  // processing all our dirty roots.  mContentScrollVPosition and
+  // mContentScrollHPosition are only used when it's non-null.
+  nsCOMPtr<nsIContent> mContentToScrollTo;
+  PRIntn mContentScrollVPosition;
+  PRIntn mContentScrollHPosition;
+
   struct nsBlurOrFocusTarget
   {
     nsBlurOrFocusTarget(nsPIDOMEventTarget* aTarget, PRUint32 aEventType)
     : mTarget(aTarget), mEventType(aEventType) {}
     nsBlurOrFocusTarget(const nsBlurOrFocusTarget& aOther)
     : mTarget(aOther.mTarget), mEventType(aOther.mEventType) {}
 
     nsCOMPtr<nsPIDOMEventTarget> mTarget;
     PRUint32                     mEventType;
   };
 
   nsTArray<nsBlurOrFocusTarget> mDelayedBlurFocusTargets;
 
   nsCallbackEventRequest* mFirstCallbackEventRequest;
   nsCallbackEventRequest* mLastCallbackEventRequest;
 
+  PRPackedBool      mSuppressInterruptibleReflows;
+
   PRPackedBool      mIsThemeSupportDisabled;  // Whether or not form controls should use nsITheme in this shell.
 
   PRPackedBool      mIsDocumentGone;      // We've been disconnected from the document.
   PRPackedBool      mPaintingSuppressed;  // For all documents we initially lock down painting.
                                           // We will refuse to paint the document until either
                                           // (a) our timer fires or (b) all frames are constructed.
   PRPackedBool      mShouldUnsuppressPainting;  // Indicates that it is safe to unlock painting once all pending
                                                 // reflows have been processed.
@@ -1613,16 +1638,20 @@ PresShell::Init(nsIDocument* aDocument,
   }
   if (mDocument) {
     NS_WARNING("PresShell double init'ed");
     return NS_ERROR_ALREADY_INITIALIZED;
   }
   result = mStackArena.Init();
   NS_ENSURE_SUCCESS(result, result);
 
+  if (!mFramesToDirty.Init()) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
   mDocument = aDocument;
   NS_ADDREF(mDocument);
   mViewManager = aViewManager;
 
   // Create our frame constructor.
   mFrameConstructor = new nsCSSFrameConstructor(mDocument, this);
   NS_ENSURE_TRUE(mFrameConstructor, NS_ERROR_OUT_OF_MEMORY);
 
@@ -1744,16 +1773,18 @@ PresShell::Destroy()
     delete mReflowCountMgr;
     mReflowCountMgr = nsnull;
   }
 #endif
 
   if (mHaveShutDown)
     return NS_OK;
 
+  mContentToScrollTo = nsnull;
+
   if (mPresContext) {
     // We need to notify the destroying the nsPresContext to ESM for
     // suppressing to use from ESM.
     mPresContext->EventStateManager()->NotifyDestroyPresContext(mPresContext);
   }
 
   {
     nsCOMPtr<nsIObserverService> os =
@@ -2689,24 +2720,22 @@ PresShell::ResizeReflow(nscoord aWidth, 
       // the way don't have region accumulation issues?
 
       {
         nsAutoScriptBlocker scriptBlocker;
         WillDoReflow();
 
         // Kick off a top-down reflow
         AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow);
-        mIsReflowing = PR_TRUE;
 
         mDirtyRoots.RemoveElement(rootFrame);
-        DoReflow(rootFrame);
-        mIsReflowing = PR_FALSE;
+        DoReflow(rootFrame, PR_TRUE);
       }
 
-      DidDoReflow();
+      DidDoReflow(PR_TRUE);
     }
 
     batch.EndUpdateViewBatch(NS_VMREFRESH_NO_SYNC);
   }
 
   if (aHeight == NS_UNCONSTRAINEDSIZE) {
     mPresContext->SetVisibleArea(
       nsRect(0, 0, aWidth, rootFrame->GetRect().height));
@@ -3406,16 +3435,30 @@ PresShell::FrameNeedsReflow(nsIFrame *aF
     }
   } while (subtrees.Length() != 0);
 
   PostReflowEvent();
 
   return NS_OK;
 }
 
+NS_IMETHODIMP_(void)
+PresShell::FrameNeedsToContinueReflow(nsIFrame *aFrame)
+{
+  NS_ASSERTION(mIsReflowing, "Must be in reflow when marking path dirty.");  
+  NS_PRECONDITION(mCurrentReflowRoot, "Must have a current reflow root here");
+  NS_ASSERTION(aFrame == mCurrentReflowRoot ||
+               nsLayoutUtils::IsProperAncestorFrame(mCurrentReflowRoot, aFrame),
+               "Frame passed in is not the descendant of mCurrentReflowRoot");
+  NS_ASSERTION(aFrame->GetStateBits() & NS_FRAME_IN_REFLOW,
+               "Frame passed in not in reflow?");
+
+  mFramesToDirty.PutEntry(aFrame);
+}
+
 nsIScrollableView*
 PresShell::GetViewToScroll(nsLayoutUtils::Direction aDirection)
 {
   nsCOMPtr<nsIEventStateManager> esm = mPresContext->EventStateManager();
   nsIScrollableView* scrollView = nsnull;
   nsCOMPtr<nsIContent> focusedContent;
   esm->GetFocusedContent(getter_AddRefs(focusedContent));
   if (!focusedContent && mSelection) {
@@ -3539,16 +3582,18 @@ PresShell::ClearFrameRefs(nsIFrame* aFra
       //One of our stack frames was deleted.  Get its content so that when we
       //pop it we can still get its new frame from its content
       nsIContent *currentEventContent = aFrame->GetContent();
       mCurrentEventContentStack.ReplaceObjectAt(currentEventContent, i);
       mCurrentEventFrameStack[i] = nsnull;
     }
   }
 
+  mFramesToDirty.RemoveEntry(aFrame);
+
   nsWeakFrame* weakFrame = mWeakFrames;
   while (weakFrame) {
     nsWeakFrame* prev = weakFrame->GetPreviousWeakFrame();
     if (weakFrame->GetFrame() == aFrame) {
       // This removes weakFrame from mWeakFrames.
       weakFrame->Clear(this);
     }
     weakFrame = prev;
@@ -4077,48 +4122,67 @@ static void ScrollViewToShowRect(nsIScro
   }
 
   aScrollingView->ScrollTo(scrollOffsetX, scrollOffsetY, 0);
 }
 
 NS_IMETHODIMP
 PresShell::ScrollContentIntoView(nsIContent* aContent,
                                  PRIntn      aVPercent,
-                                 PRIntn      aHPercent) const
-{
+                                 PRIntn      aHPercent)
+{
+  mContentToScrollTo = aContent;
+  mContentScrollVPosition = aVPercent;
+  mContentScrollHPosition = aHPercent;
+
   nsCOMPtr<nsIContent> content = aContent; // Keep content alive while flushing.
   NS_ENSURE_TRUE(content, NS_ERROR_NULL_POINTER);
   nsCOMPtr<nsIDocument> currentDoc = content->GetCurrentDoc();
   NS_ENSURE_STATE(currentDoc);
-  currentDoc->FlushPendingNotifications(Flush_Layout);
-  nsIFrame* frame = GetPrimaryFrameFor(content);
-  if (!frame) {
-    return NS_ERROR_NULL_POINTER;
-  }
+  currentDoc->FlushPendingNotifications(Flush_InterruptibleLayout);
+
+  // If mContentToScrollTo is non-null, that means we interrupted the reflow
+  // and won't necessarily get the position correct, but do a best-effort
+  // scroll.
 
   // Before we scroll the frame into view, ask the command dispatcher
   // if we're resetting focus because a window just got an activate
   // event. If we are, we do not want to scroll the frame into view.
   // Example: The user clicks on an anchor, and then deactivates the 
   // window. When they reactivate the window, the expected behavior
   // is not for the anchor link to scroll back into view. That is what
   // this check is preventing.
   // XXX: The dependency on the command dispatcher needs to be fixed.
   nsPIDOMWindow* ourWindow = currentDoc->GetWindow();
   if (ourWindow) {
     nsIFocusController *focusController = ourWindow->GetRootFocusController();
     if (focusController) {
       PRBool dontScroll = PR_FALSE;
       focusController->GetSuppressFocusScroll(&dontScroll);
       if (dontScroll) {
+        mContentToScrollTo = nsnull;
         return NS_OK;
       }
     }
   }
 
+  return DoScrollContentIntoView(content, aVPercent, aHPercent);
+}
+
+nsresult
+PresShell::DoScrollContentIntoView(nsIContent* aContent,
+                                   PRIntn      aVPercent,
+                                   PRIntn      aHPercent)
+{
+  nsIFrame* frame = GetPrimaryFrameFor(aContent);
+  if (!frame) {
+    mContentToScrollTo = nsnull;
+    return NS_ERROR_NULL_POINTER;
+  }
+
   // This is a two-step process.
   // Step 1: Find the bounds of the rect we want to scroll into view.  For
   //         example, for an inline frame we may want to scroll in the whole
   //         line.
   // Step 2: Walk the views that are parents of the frame and scroll them
   //         appropriately.
   
   nsIView *closestView = nsnull;
@@ -4571,17 +4635,17 @@ PresShell::CancelPostedReflowCallbacks()
     FreeFrame(sizeof(nsCallbackEventRequest), node);
     if (callback) {
       callback->ReflowCallbackCanceled();
     }
   }
 }
 
 void
-PresShell::HandlePostedReflowCallbacks()
+PresShell::HandlePostedReflowCallbacks(PRBool aInterruptible)
 {
    PRBool shouldFlush = PR_FALSE;
 
    while (mFirstCallbackEventRequest) {
      nsCallbackEventRequest* node = mFirstCallbackEventRequest;
      mFirstCallbackEventRequest = node->next;
      if (!mFirstCallbackEventRequest) {
        mLastCallbackEventRequest = nsnull;
@@ -4590,18 +4654,20 @@ PresShell::HandlePostedReflowCallbacks()
      FreeFrame(sizeof(nsCallbackEventRequest), node);
      if (callback) {
        if (callback->ReflowFinished()) {
          shouldFlush = PR_TRUE;
        }
      }
    }
 
+   mozFlushType flushType =
+     aInterruptible ? Flush_InterruptibleLayout : Flush_Layout;
    if (shouldFlush)
-     FlushPendingNotifications(Flush_Layout);
+     FlushPendingNotifications(flushType);
 }
 
 NS_IMETHODIMP 
 PresShell::IsSafeToFlush(PRBool& aIsSafeToFlush)
 {
   aIsSafeToFlush = nsContentUtils::IsSafeToRunScript();
 #ifdef DEBUG
   // Not safe if we are reflowing or in the middle of frame construction
@@ -4616,26 +4682,19 @@ PresShell::IsSafeToFlush(PRBool& aIsSafe
     }
   }
   NS_ASSERTION(!aIsSafeToFlush || isSafeToFlush, "Missing a script blocker!");
 #endif
   return NS_OK;
 }
 
 
-NS_IMETHODIMP 
+NS_IMETHODIMP
 PresShell::FlushPendingNotifications(mozFlushType aType)
 {
-  return DoFlushPendingNotifications(aType, PR_FALSE);
-}
-
-nsresult
-PresShell::DoFlushPendingNotifications(mozFlushType aType,
-                                       PRBool aInterruptibleReflow)
-{
   NS_ASSERTION(aType >= Flush_Frames, "Why did we get called?");
   
   PRBool isSafeToFlush;
   IsSafeToFlush(isSafeToFlush);
 
   NS_ASSERTION(!isSafeToFlush || mViewManager, "Must have view manager");
   // Make sure the view manager stays alive while batching view updates.
   nsCOMPtr<nsIViewManager> viewManagerDeathGrip = mViewManager;
@@ -4693,30 +4752,36 @@ PresShell::DoFlushPendingNotifications(m
       nsAutoScriptBlocker scriptBlocker;
       mFrameConstructor->ProcessPendingRestyles();
     }
 
 
     // There might be more pending constructors now, but we're not going to
     // worry about them.  They can't be triggered during reflow, so we should
     // be good.
-    
-    if (aType >= Flush_Layout && !mIsDestroying) {
+
+    if (aType >= (mSuppressInterruptibleReflows ? Flush_Layout : Flush_InterruptibleLayout) &&
+        !mIsDestroying) {
       mFrameConstructor->RecalcQuotesAndCounters();
       mViewManager->FlushDelayedResize();
-      ProcessReflowCommands(aInterruptibleReflow);
+      if (ProcessReflowCommands(aType < Flush_Layout) && mContentToScrollTo) {
+        // We didn't get interrupted.  Go ahead and scroll to our content
+        DoScrollContentIntoView(mContentToScrollTo, mContentScrollVPosition,
+                                mContentScrollHPosition);
+        mContentToScrollTo = nsnull;
+      }
     }
 
     PRUint32 updateFlags = NS_VMREFRESH_NO_SYNC;
     if (aType >= Flush_Display) {
       // Flushing paints, so perform the invalidates and drawing
       // immediately
       updateFlags = NS_VMREFRESH_IMMEDIATE;
     }
-    else if (aType < Flush_Layout) {
+    else if (aType < Flush_InterruptibleLayout) {
       // Not flushing reflows, so do deferred invalidates.  This will keep us
       // from possibly flushing out reflows due to invalidates being processed
       // at the end of this view batch.
       updateFlags = NS_VMREFRESH_DEFERRED;
     }
     batch.EndUpdateViewBatch(updateFlags);
   }
 
@@ -6540,17 +6605,17 @@ PresShell::WillPaint()
   if (mPaintingSuppressed) {
     return;
   }
   
   // Process reflows, if we have them, to reduce flicker due to invalidates and
   // reflow being interspersed.  Note that we _do_ allow this to be
   // interruptible; if we can't do all the reflows it's better to flicker a bit
   // than to freeze up.
-  DoFlushPendingNotifications(Flush_Layout, PR_TRUE);
+  FlushPendingNotifications(Flush_InterruptibleLayout);
 }
 
 nsresult
 PresShell::GetAgentStyleSheets(nsCOMArray<nsIStyleSheet>& aSheets)
 {
   aSheets.Clear();
   PRInt32 sheetCount = mStyleSet->SheetCount(nsStyleSet::eAgentSheet);
 
@@ -6750,17 +6815,27 @@ PresShell::ReflowEvent::Run() {
     }
 #endif
     // NOTE: the ReflowEvent class is a friend of the PresShell class
     ps->ClearReflowEventStatus();
     // Set a kung fu death grip on the view manager associated with the pres shell
     // before processing that pres shell's reflow commands.  Fixes bug 54868.
     nsCOMPtr<nsIViewManager> viewManager = ps->GetViewManager();
 
-    ps->DoFlushPendingNotifications(Flush_Layout, PR_TRUE);
+    ps->mSuppressInterruptibleReflows = PR_FALSE;
+
+#ifdef NOISY_INTERRUPTIBLE_REFLOW
+    printf("*** Entering reflow event (time=%lld)\n", PR_Now());
+#endif /* NOISY_INTERRUPTIBLE_REFLOW */
+
+    ps->FlushPendingNotifications(Flush_InterruptibleLayout);
+
+#ifdef NOISY_INTERRUPTIBLE_REFLOW
+    printf("*** Returning from reflow event (time=%lld)\n", PR_Now());
+#endif /* NOISY_INTERRUPTIBLE_REFLOW */
 
     // Now, explicitly release the pres shell before the view manager
     ps = nsnull;
     viewManager = nsnull;
   }
   return NS_OK;
 }
 
@@ -6797,46 +6872,66 @@ PresShell::WillDoReflow()
   }
 
   mPresContext->FlushUserFontSet();
 
   mFrameConstructor->BeginUpdate();
 }
 
 void
-PresShell::DidDoReflow()
+PresShell::DidDoReflow(PRBool aInterruptible)
 {
   mFrameConstructor->EndUpdate();
   
-  HandlePostedReflowCallbacks();
+  HandlePostedReflowCallbacks(aInterruptible);
   // Null-check mViewManager in case this happens during Destroy.  See
   // bugs 244435 and 238546.
   if (!mPaintingSuppressed && mViewManager)
     mViewManager->SynthesizeMouseMove(PR_FALSE);
   if (mCaret) {
     // Update the caret's position now to account for any changes created by
     // the reflow.
     mCaret->InvalidateOutsideCaret();
     mCaret->UpdateCaretPosition();
   }
 }
 
-void
-PresShell::DoReflow(nsIFrame* target)
+static PLDHashOperator
+MarkFramesDirtyToRoot(nsPtrHashKey<nsIFrame>* p, void* closure)
+{
+  nsIFrame* target = static_cast<nsIFrame*>(closure);
+  for (nsIFrame* f = p->GetKey(); f && !NS_SUBTREE_DIRTY(f);
+       f = f->GetParent()) {
+    f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+
+    if (f == target) {
+      break;
+    }
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+PRBool
+PresShell::DoReflow(nsIFrame* target, PRBool aInterruptible)
 {
   nsIFrame* rootFrame = FrameManager()->GetRootFrame();
 
   nsCOMPtr<nsIRenderingContext> rcx;
   // Always create the rendering context relative to the root frame during
   // reflow; otherwise, it crashes on the mac (I'm not quite sure why)
   nsresult rv = CreateRenderingContext(rootFrame, getter_AddRefs(rcx));
   if (NS_FAILED(rv)) {
-   NS_NOTREACHED("CreateRenderingContext failure");
-   return;
- }
+    NS_NOTREACHED("CreateRenderingContext failure");
+    return PR_FALSE;
+  }
+
+#ifdef DEBUG
+  mCurrentReflowRoot = target;
+#endif
 
   target->WillReflow(mPresContext);
 
   // If the target frame is the root of the frame hierarchy, then
   // use all the available space. If it's simply a `reflow root',
   // then use the target frame's size as the available space.
   nsSize size;
   if (target == rootFrame)
@@ -6861,16 +6956,19 @@ PresShell::DoReflow(nsIFrame* target)
     computedHeight = PR_MAX(computedHeight, 0);
     reflowState.SetComputedHeight(computedHeight);
   }
   NS_ASSERTION(reflowState.ComputedWidth() ==
                  size.width -
                    reflowState.mComputedBorderPadding.LeftRight(),
                "reflow state computed incorrect width");
 
+  mPresContext->ReflowStarted(aInterruptible);
+  mIsReflowing = PR_TRUE;
+
   nsReflowStatus status;
   nsHTMLReflowMetrics desiredSize;
   target->Reflow(mPresContext, desiredSize, reflowState, status);
 
   // If an incremental reflow is initiated at a frame other than the
   // root frame, then its desired size had better not change!  If it's
   // initiated at the root, then the size better not change unless its
   // height was unconstrained to start with.
@@ -6892,16 +6990,48 @@ PresShell::DoReflow(nsIFrame* target)
                                              target->GetView(),
                                              &desiredSize.mOverflowArea);
 
   target->DidReflow(mPresContext, nsnull, NS_FRAME_REFLOW_FINISHED);
   if (target == rootFrame && size.height == NS_UNCONSTRAINEDSIZE) {
     mPresContext->SetVisibleArea(nsRect(0, 0, desiredSize.width,
                                         desiredSize.height));
   }
+
+#ifdef DEBUG
+  mCurrentReflowRoot = nsnull;
+#endif
+
+  NS_ASSERTION(mPresContext->HasPendingInterrupt() ||
+               mFramesToDirty.Count() == 0,
+               "Why do we need to dirty anything if not interrupted?");
+
+  mIsReflowing = PR_FALSE;
+  PRBool interrupted = mPresContext->HasPendingInterrupt();
+  if (interrupted) {
+    // Make sure target gets reflowed again.
+    mFramesToDirty.EnumerateEntries(&MarkFramesDirtyToRoot, target);
+    NS_ASSERTION(NS_SUBTREE_DIRTY(target), "Why is the target not dirty?");
+    mDirtyRoots.AppendElement(target);
+
+    // 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();
+
+    // Any FlushPendingNotifications with interruptible reflows
+    // should be suppressed now. We don't want to do extra reflow work
+    // before our reflow event happens.
+    mSuppressInterruptibleReflows = PR_TRUE;
+    PostReflowEvent();
+  }
+
+  return !interrupted;
 }
 
 #ifdef DEBUG
 void
 PresShell::DoVerifyReflow()
 {
   if (nsIFrameDebug::GetVerifyTreeEnable()) {
     nsIFrame* rootFrame = FrameManager()->GetRootFrame();
@@ -6928,22 +7058,23 @@ PresShell::DoVerifyReflow()
 
     if (0 != mDirtyRoots.Length()) {
       printf("XXX yikes! reflow commands queued during verify-reflow\n");
     }
   }
 }
 #endif
 
-nsresult
+PRBool
 PresShell::ProcessReflowCommands(PRBool aInterruptible)
 {
   MOZ_TIMER_DEBUGLOG(("Start: Reflow: PresShell::ProcessReflowCommands(), this=%p\n", this));
   MOZ_TIMER_START(mReflowWatch);  
 
+  PRBool interrupted = PR_FALSE;
   if (0 != mDirtyRoots.Length()) {
 
 #ifdef DEBUG
     if (VERIFY_REFLOW_DUMP_COMMANDS & gVerifyReflowFlags) {
       printf("ProcessReflowCommands: begin incremental reflow\n");
     }
 #endif
 
@@ -6953,47 +7084,43 @@ PresShell::ProcessReflowCommands(PRBool 
     const PRIntervalTime deadline = aInterruptible
         ? PR_IntervalNow() + PR_MicrosecondsToInterval(gMaxRCProcessingTime)
         : (PRIntervalTime)0;
 
     // Scope for the reflow entry point
     {
       nsAutoScriptBlocker scriptBlocker;
       AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow);
-      mIsReflowing = PR_TRUE;
 
       do {
         // Send an incremental reflow notification to the target frame.
         PRInt32 idx = mDirtyRoots.Length() - 1;
         nsIFrame *target = mDirtyRoots[idx];
         mDirtyRoots.RemoveElementAt(idx);
 
         if (!NS_SUBTREE_DIRTY(target)) {
           // It's not dirty anymore, which probably means the notification
           // was posted in the middle of a reflow (perhaps with a reflow
           // root in the middle).  Don't do anything.
           continue;
         }
 
-        DoReflow(target);
+        interrupted = !DoReflow(target, aInterruptible);
 
         // Keep going until we're out of reflow commands, or we've run
-        // past our deadline.
-      } while (mDirtyRoots.Length() &&
+        // past our deadline, or we're interrupted.
+      } while (!interrupted && mDirtyRoots.Length() &&
                (!aInterruptible || PR_IntervalNow() < deadline));
 
-      // XXXwaterson for interruptible reflow, examine the tree and
-      // re-enqueue any unflowed reflow targets.
-
-      mIsReflowing = PR_FALSE;
+      interrupted = mDirtyRoots.Length() != 0;
     }
 
     // Exiting the scriptblocker might have killed us
     if (!mIsDestroying) {
-      DidDoReflow();
+      DidDoReflow(aInterruptible);
     }
 
     // DidDoReflow might have killed us
     if (!mIsDestroying) {
 #ifdef DEBUG
       if (VERIFY_REFLOW_DUMP_COMMANDS & gVerifyReflowFlags) {
         printf("\nPresShell::ProcessReflowCommands() finished: this=%p\n",
                (void*)this);
@@ -7019,17 +7146,17 @@ PresShell::ProcessReflowCommands(PRBool 
     // We only unlock if we're out of reflows.  It's pointless
     // to unlock if reflows are still pending, since reflows
     // are just going to thrash the frames around some more.  By
     // waiting we avoid an overeager "jitter" effect.
     mShouldUnsuppressPainting = PR_FALSE;
     UnsuppressAndInvalidate();
   }
 
-  return NS_OK;
+  return !interrupted;
 }
 
 void
 PresShell::ClearReflowEventStatus()
 {
   mReflowEvent.Forget();
 }
 
--- a/layout/generic/nsAbsoluteContainingBlock.cpp
+++ b/layout/generic/nsAbsoluteContainingBlock.cpp
@@ -142,19 +142,19 @@ nsAbsoluteContainingBlock::Reflow(nsCont
     aChildBounds->SetRect(0, 0, 0, 0);
   nsReflowStatus reflowStatus = NS_FRAME_COMPLETE;
 
   PRBool reflowAll = aReflowState.ShouldReflowAllKids();
 
   nsIFrame* kidFrame;
   nsOverflowContinuationTracker tracker(aPresContext, aDelegatingFrame, PR_TRUE);
   for (kidFrame = mAbsoluteFrames.FirstChild(); kidFrame; kidFrame = kidFrame->GetNextSibling()) {
-    if (reflowAll ||
-        NS_SUBTREE_DIRTY(kidFrame) ||
-        FrameDependsOnContainer(kidFrame, aCBWidthChanged, aCBHeightChanged)) {
+    PRBool kidNeedsReflow = reflowAll || NS_SUBTREE_DIRTY(kidFrame) ||
+      FrameDependsOnContainer(kidFrame, aCBWidthChanged, aCBHeightChanged);
+    if (kidNeedsReflow && !aPresContext->HasPendingInterrupt()) {
       // Reflow the frame
       nsReflowStatus  kidStatus = NS_FRAME_COMPLETE;
       ReflowAbsoluteFrame(aDelegatingFrame, aPresContext, aReflowState,
                           aContainingBlockWidth, aContainingBlockHeight,
                           aConstrainHeight, kidFrame, kidStatus, aChildBounds);
       nsIFrame* nextFrame = kidFrame->GetNextInFlow();
       if (!NS_FRAME_IS_FULLY_COMPLETE(kidStatus)) {
         // Need a continuation
@@ -183,17 +183,26 @@ nsAbsoluteContainingBlock::Reflow(nsCont
     }
     else {
       tracker.Skip(kidFrame, reflowStatus);
       if (aChildBounds) {
         aChildBounds->UnionRect(*aChildBounds, kidFrame->GetOverflowRect() +
                                                kidFrame->GetPosition());
       }
     }
+
+    if (kidNeedsReflow && aPresContext->HasPendingInterrupt()) {
+      if (aDelegatingFrame->GetStateBits() & NS_FRAME_IS_DIRTY) {
+        kidFrame->AddStateBits(NS_FRAME_IS_DIRTY);
+      } else {
+        kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+      }
+    }
   }
+
   // Abspos frames can't cause their parent to be incomplete,
   // only overflow incomplete.
   if (NS_FRAME_IS_NOT_COMPLETE(reflowStatus))
     NS_FRAME_SET_OVERFLOW_INCOMPLETE(reflowStatus);
 
   NS_MergeReflowStatusInto(&aReflowStatus, reflowStatus);
   return NS_OK;
 }
@@ -330,20 +339,34 @@ void
 nsAbsoluteContainingBlock::DestroyFrames(nsIFrame* aDelegatingFrame)
 {
   mAbsoluteFrames.DestroyFrames();
 }
 
 void
 nsAbsoluteContainingBlock::MarkSizeDependentFramesDirty()
 {
+  DoMarkFramesDirty(PR_FALSE);
+}
+
+void
+nsAbsoluteContainingBlock::MarkAllFramesDirty()
+{
+  DoMarkFramesDirty(PR_TRUE);
+}
+
+void
+nsAbsoluteContainingBlock::DoMarkFramesDirty(PRBool aMarkAllDirty)
+{
   for (nsIFrame* kidFrame = mAbsoluteFrames.FirstChild();
        kidFrame;
        kidFrame = kidFrame->GetNextSibling()) {
-    if (FrameDependsOnContainer(kidFrame, PR_TRUE, PR_TRUE)) {
+    if (aMarkAllDirty) {
+      kidFrame->AddStateBits(NS_FRAME_IS_DIRTY);
+    } else if (FrameDependsOnContainer(kidFrame, PR_TRUE, PR_TRUE)) {
       // Add the weakest flags that will make sure we reflow this frame later
       kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
     }
   }
 }
 
 // XXX Optimize the case where it's a resize reflow and the absolutely
 // positioned child has the exact same size and position and skip the
--- a/layout/generic/nsAbsoluteContainingBlock.h
+++ b/layout/generic/nsAbsoluteContainingBlock.h
@@ -122,18 +122,23 @@ public:
                   PRBool                   aCBHeightChanged,
                   nsRect*                  aChildBounds = nsnull);
 
 
   void DestroyFrames(nsIFrame* aDelegatingFrame);
 
   PRBool  HasAbsoluteFrames() {return mAbsoluteFrames.NotEmpty();}
 
+  // Mark our size-dependent absolute frames with NS_FRAME_HAS_DIRTY_CHILDREN
+  // so that we'll make sure to reflow them.
   void MarkSizeDependentFramesDirty();
 
+  // Mark all our absolute frames with NS_FRAME_IS_DIRTY
+  void MarkAllFramesDirty();
+
 protected:
   // Returns PR_TRUE if the position of f depends on the position of
   // its placeholder or if the position or size of f depends on a
   // containing block dimension that changed.
   PRBool FrameDependsOnContainer(nsIFrame* f, PRBool aCBWidthChanged,
                                  PRBool aCBHeightChanged);
 
   nsresult ReflowAbsoluteFrame(nsIFrame*                aDelegatingFrame,
@@ -141,16 +146,21 @@ protected:
                                const nsHTMLReflowState& aReflowState,
                                nscoord                  aContainingBlockWidth,
                                nscoord                  aContainingBlockHeight,
                                PRBool                   aConstrainHeight,
                                nsIFrame*                aKidFrame,
                                nsReflowStatus&          aStatus,
                                nsRect*                  aChildBounds);
 
+  // Mark our absolute frames dirty.  If aMarkAllDirty is true, all will be
+  // marked with NS_FRAME_IS_DIRTY.  Otherwise, the size-dependant ones will be
+  // marked with NS_FRAME_HAS_DIRTY_CHILDREN.
+  void DoMarkFramesDirty(PRBool aMarkAllDirty);
+
 protected:
   nsFrameList mAbsoluteFrames;  // additional named child list
 
 #ifdef DEBUG
   nsIAtom* const mChildListName; // nsGkAtoms::fixedList or nsGkAtoms::absoluteList
 
   // helper routine for debug printout
   void PrettyUC(nscoord aSize,
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -1117,22 +1117,30 @@ nsBlockFrame::Reflow(nsPresContext*     
   // XXX checking oldSize is bogus, there are various reasons we might have
   // reflowed but our size might not have been changed to what we
   // asked for (e.g., we ended up being pushed to a new page)
   // When WillReflowAgainForClearance is true, we will reflow again without
   // resetting the size. Because of this, we must not reflow our abs-pos children
   // in that situation --- what we think is our "new size"
   // will not be our real new size. This also happens to be more efficient.
   if (mAbsoluteContainer.HasAbsoluteFrames()) {
-    if (aReflowState.WillReflowAgainForClearance()) {
+    PRBool haveInterrupt = aPresContext->HasPendingInterrupt();
+    if (aReflowState.WillReflowAgainForClearance() ||
+        haveInterrupt) {
       // Make sure that when we reflow again we'll actually reflow all the abs
-      // pos frames that might conceivably depend on our size.  Sadly, we can't
-      // do much better than that, because we don't really know what our size
-      // will be, and it might in fact not change on the followup reflow!
-      mAbsoluteContainer.MarkSizeDependentFramesDirty();
+      // pos frames that might conceivably depend on our size (or all of them,
+      // if we're dirty right now and interrupted; in that case we also need
+      // to mark them all with NS_FRAME_IS_DIRTY).  Sadly, we can't do much
+      // better than that, because we don't really know what our size will be,
+      // and it might in fact not change on the followup reflow!
+      if (haveInterrupt && (GetStateBits() & NS_FRAME_IS_DIRTY)) {
+        mAbsoluteContainer.MarkAllFramesDirty();
+      } else {
+        mAbsoluteContainer.MarkSizeDependentFramesDirty();
+      }
     } else {
       nsRect childBounds;
       nsSize containingBlockSize =
         CalculateContainingBlockSizeForAbsolutes(aReflowState,
                                                  nsSize(aMetrics.width,
                                                         aMetrics.height));
 
       // Mark frames that depend on changes we just made to this frame as dirty:
@@ -2011,16 +2019,28 @@ nsBlockFrame::ReflowDirtyLines(nsBlockRe
       inlineFloatBreakType = line->GetBreakTypeAfter();
     }
 
     if (LineHasClear(line.get())) {
       foundAnyClears = PR_TRUE;
     }
 
     DumpLine(aState, line, deltaY, -1);
+
+    if (aState.mPresContext->CheckForInterrupt(this)) {
+      willReflowAgain = PR_TRUE;
+      // Another option here might be to leave |line| clean if
+      // !HasPendingInterrupt() before the CheckForInterrupt() call, since in
+      // that case the line really did reflow as it should have.  Not sure
+      // whether that would be safe, so doing this for now instead.  Also not
+      // sure whether we really want to mark all lines dirty after an
+      // interrupt, but until we get better at propagating float damage we
+      // really do need to do it this way; see comments inside MarkLineDirty.
+      MarkLineDirtyForInterrupt(line);
+    }
   }
 
   // Handle BR-clearance from the last line of the block
   if (inlineFloatBreakType != NS_STYLE_CLEAR_NONE) {
     aState.mY = aState.ClearFloats(aState.mY, inlineFloatBreakType);
   }
 
   if (needToRecoverState) {
@@ -2032,28 +2052,31 @@ nsBlockFrame::ReflowDirtyLines(nsBlockRe
     // walking |GetNextSibling|.
     aState.mPrevChild = line.prev()->LastChild();
   }
 
   // Should we really have to do this?
   if (repositionViews)
     ::PlaceFrameView(this);
 
-  // We can skip trying to pull up the next line if there is no next
-  // in flow or we were told not to or we know it will be futile, i.e.,
+  // We can skip trying to pull up the next line if our height is constrained
+  // (so we can report being incomplete) and there is no next in flow or we
+  // were told not to or we know it will be futile, i.e.,
   // -- the next in flow is not changing
   // -- and we cannot have added more space for its first line to be
   // pulled up into,
   // -- it's an incremental reflow of a descendant
   // -- and we didn't reflow any floats (so the available space
   // didn't change)
   // -- my chain of next-in-flows either has no first line, or its first
   // line isn't dirty.
-  PRBool skipPull = willReflowAgain;
-  if (aState.mNextInFlow &&
+  PRBool heightConstrained =
+    aState.mReflowState.availableHeight != NS_UNCONSTRAINEDSIZE;
+  PRBool skipPull = willReflowAgain && heightConstrained;
+  if (!skipPull && heightConstrained && aState.mNextInFlow &&
       (aState.mReflowState.mFlags.mNextInFlowUntouched &&
        !lastLineMovedUp && 
        !(GetStateBits() & NS_FRAME_IS_DIRTY) &&
        !reflowedFloat)) {
     // We'll place lineIter at the last line of this block, so that 
     // nsBlockInFlowLineIterator::Next() will take us to the first
     // line of my next-in-flow-chain.  (But first, check that I 
     // have any lines -- if I don't, just bail out of this
@@ -2062,24 +2085,28 @@ nsBlockFrame::ReflowDirtyLines(nsBlockRe
     if (lineIter != this->begin_lines()) {
       lineIter--; // I have lines; step back from dummy iterator to last line.
       nsBlockInFlowLineIterator bifLineIter(this, lineIter, PR_FALSE);
 
       // Check for next-in-flow-chain's first line.
       // (First, see if there is such a line, and second, see if it's clean)
       if (!bifLineIter.Next() ||                
           !bifLineIter.GetLine()->IsDirty()) {
-        if (IS_TRUE_OVERFLOW_CONTAINER(aState.mNextInFlow))
-          NS_FRAME_SET_OVERFLOW_INCOMPLETE(aState.mReflowStatus);
-        else
-          NS_FRAME_SET_INCOMPLETE(aState.mReflowStatus);
         skipPull=PR_TRUE;
       }
     }
   }
+
+  if (skipPull && aState.mNextInFlow) {
+    NS_ASSERTION(heightConstrained, "Height should be constrained here\n");
+    if (IS_TRUE_OVERFLOW_CONTAINER(aState.mNextInFlow))
+      NS_FRAME_SET_OVERFLOW_INCOMPLETE(aState.mReflowStatus);
+    else
+      NS_FRAME_SET_INCOMPLETE(aState.mReflowStatus);
+  }
   
   if (!skipPull && aState.mNextInFlow) {
     // Pull data from a next-in-flow if there's still room for more
     // content here.
     while (keepGoing && (nsnull != aState.mNextInFlow)) {
       // Grab first line from our next-in-flow
       nsBlockFrame* nextInFlow = aState.mNextInFlow;
       line_iterator nifLine = nextInFlow->begin_lines();
@@ -2152,38 +2179,48 @@ nsBlockFrame::ReflowDirtyLines(nsBlockRe
 
       line = mLines.before_insert(end_lines(), toMove);
 
       DumpLine(aState, toMove, deltaY, 0);
 #ifdef DEBUG
       AutoNoisyIndenter indent2(gNoisyReflow);
 #endif
 
-      // Now reflow it and any lines that it makes during it's reflow
-      // (we have to loop here because reflowing the line may case a new
-      // line to be created; see SplitLine's callers for examples of
-      // when this happens).
-      while (line != end_lines()) {
-        rv = ReflowLine(aState, line, &keepGoing);
-        NS_ENSURE_SUCCESS(rv, rv);
-        DumpLine(aState, line, deltaY, -1);
-        if (!keepGoing) {
-          if (0 == line->GetChildCount()) {
-            DeleteLine(aState, line, line_end);
+      if (aState.mPresContext->HasPendingInterrupt()) {
+        MarkLineDirtyForInterrupt(line);
+      } else {
+        // Now reflow it and any lines that it makes during it's reflow
+        // (we have to loop here because reflowing the line may case a new
+        // line to be created; see SplitLine's callers for examples of
+        // when this happens).
+        while (line != end_lines()) {
+          rv = ReflowLine(aState, line, &keepGoing);
+          NS_ENSURE_SUCCESS(rv, rv);
+          DumpLine(aState, line, deltaY, -1);
+          if (!keepGoing) {
+            if (0 == line->GetChildCount()) {
+              DeleteLine(aState, line, line_end);
+            }
+            break;
           }
-          break;
+
+          if (LineHasClear(line.get())) {
+            foundAnyClears = PR_TRUE;
+          }
+
+          if (aState.mPresContext->CheckForInterrupt(this)) {
+            willReflowAgain = PR_TRUE;
+            MarkLineDirtyForInterrupt(line);
+            break;
+          }
+
+          // If this is an inline frame then its time to stop
+          ++line;
+          aState.AdvanceToNextLine();
         }
-
-        if (LineHasClear(line.get())) {
-          foundAnyClears = PR_TRUE;
-        }
-
-        // If this is an inline frame then its time to stop
-        ++line;
-        aState.AdvanceToNextLine();
       }
     }
 
     if (NS_FRAME_IS_NOT_COMPLETE(aState.mReflowStatus)) {
       aState.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
     } //XXXfr shouldn't set this flag when nextinflow has no lines
   }
 
@@ -2211,16 +2248,64 @@ nsBlockFrame::ReflowDirtyLines(nsBlockRe
     printf(": done reflowing dirty lines (status=%x)\n",
            aState.mReflowStatus);
   }
 #endif
 
   return rv;
 }
 
+static void MarkAllDescendantLinesDirty(nsBlockFrame* aBlock)
+{
+  nsLineList::iterator line = aBlock->begin_lines();
+  nsLineList::iterator endLine = aBlock->end_lines();
+  while (line != endLine) {
+    if (line->IsBlock()) {
+      nsIFrame* f = line->mFirstChild;
+      nsBlockFrame* bf = nsLayoutUtils::GetAsBlock(f);
+      if (bf) {
+        MarkAllDescendantLinesDirty(bf);
+      }
+    }
+    line->MarkDirty();
+    ++line;
+  }
+}
+
+void
+nsBlockFrame::MarkLineDirtyForInterrupt(nsLineBox* aLine)
+{
+  aLine->MarkDirty();
+
+  // Just checking NS_FRAME_IS_DIRTY is ok, because we've already
+  // marked the lines that need to be marked dirty based on our
+  // vertical resize stuff.  So we'll definitely reflow all those kids;
+  // the only question is how they should behave.
+  if (GetStateBits() & NS_FRAME_IS_DIRTY) {
+    // Mark all our child frames dirty so we make sure to reflow them
+    // later.
+    PRInt32 n = aLine->GetChildCount();
+    for (nsIFrame* f = aLine->mFirstChild; n > 0;
+         f = f->GetNextSibling(), --n) {
+      f->AddStateBits(NS_FRAME_IS_DIRTY);
+    }
+  } else {
+    // Dirty all the descendant lines of block kids to handle float damage,
+    // since our nsFloatManager will go away by the next time we're reflowing.
+    // XXXbz Can we do something more like what PropagateFloatDamage does?
+    // Would need to sort out the exact business with mBlockDelta for that....
+    // This marks way too much dirty.  If we ever make this better, revisit
+    // which lines we mark dirty in the interrupt case in ReflowDirtyLines.
+    nsBlockFrame* bf = nsLayoutUtils::GetAsBlock(aLine->mFirstChild);
+    if (bf) {
+      MarkAllDescendantLinesDirty(bf);
+    }
+  }
+}
+
 void
 nsBlockFrame::DeleteLine(nsBlockReflowState& aState,
                          nsLineList::iterator aLine,
                          nsLineList::iterator aLineEnd)
 {
   NS_PRECONDITION(0 == aLine->GetChildCount(), "can't delete !empty line");
   if (0 == aLine->GetChildCount()) {
     NS_ASSERTION(aState.mCurrentLine == aLine,
@@ -4953,33 +5038,16 @@ nsBlockFrame::RemoveFloat(nsIFrame* aFlo
   
   // If this is during reflow, it could be the out-of-flow frame for a
   // placeholder in our block reflow state's mOverflowPlaceholders. But that's
   // OK; it's not part of any child list, so we can just go ahead and delete it.
   aFloat->Destroy();
   return line_end;
 }
 
-static void MarkAllDescendantLinesDirty(nsBlockFrame* aBlock)
-{
-  nsLineList::iterator line = aBlock->begin_lines();
-  nsLineList::iterator endLine = aBlock->end_lines();
-  while (line != endLine) {
-    if (line->IsBlock()) {
-      nsIFrame* f = line->mFirstChild;
-      nsBlockFrame* bf = nsLayoutUtils::GetAsBlock(f);
-      if (bf) {
-        MarkAllDescendantLinesDirty(bf);
-      }
-    }
-    line->MarkDirty();
-    ++line;
-  }
-}
-
 static void MarkSameFloatManagerLinesDirty(nsBlockFrame* aBlock)
 {
   nsBlockFrame* blockWithFloatMgr = aBlock;
   while (!(blockWithFloatMgr->GetStateBits() & NS_BLOCK_FLOAT_MGR)) {
     nsBlockFrame* bf = nsLayoutUtils::GetAsBlock(blockWithFloatMgr->GetParent());
     if (!bf) {
       break;
     }
--- a/layout/generic/nsBlockFrame.h
+++ b/layout/generic/nsBlockFrame.h
@@ -459,16 +459,19 @@ protected:
   /** set up the conditions necessary for an resize reflow
     * the primary task is to mark the minimumly sufficient lines dirty. 
     */
   nsresult PrepareResizeReflow(nsBlockReflowState& aState);
 
   /** reflow all lines that have been marked dirty */
   nsresult ReflowDirtyLines(nsBlockReflowState& aState);
 
+  /** Mark a given line dirty due to reflow being interrupted on or before it */
+  void MarkLineDirtyForInterrupt(nsLineBox* aLine);
+
   //----------------------------------------
   // Methods for line reflow
   /**
    * Reflow a line.  
    * @param aState           the current reflow state
    * @param aLine            the line to reflow.  can contain a single block frame
    *                         or contain 1 or more inline frames.
    * @param aKeepReflowGoing [OUT] indicates whether the caller should continue to reflow more lines
--- a/layout/generic/nsColumnSetFrame.cpp
+++ b/layout/generic/nsColumnSetFrame.cpp
@@ -750,31 +750,49 @@ nsColumnSetFrame::ReflowChildren(nsHTMLR
         if (continuationColumns) {
           SetOverflowFrames(PresContext(), continuationColumns);
           child->SetNextSibling(nsnull);
         }
         break;
       }
     }
 
+    if (PresContext()->HasPendingInterrupt()) {
+      // Stop the loop now while |child| still points to the frame that bailed
+      // out.  We could keep going here and condition a bunch of the code in
+      // this loop on whether there's an interrupt, or even just keep going and
+      // trying to reflow the blocks (even though we know they'll interrupt
+      // right after their first line), but stopping now is conceptually the
+      // simplest (and probably fastest) thing.
+      break;
+    }
+
     // Advance to the next column
     child = child->GetNextSibling();
 
     if (child) {
       if (!RTL) {
         childOrigin.x += aConfig.mColWidth + aConfig.mColGap;
       } else {
         childOrigin.x -= aConfig.mColWidth + aConfig.mColGap;
       }
       
 #ifdef DEBUG_roc
       printf("*** NEXT CHILD ORIGIN.x = %d\n", childOrigin.x);
 #endif
     }
   }
+
+  if (PresContext()->HasPendingInterrupt() &&
+      (GetStateBits() & NS_FRAME_IS_DIRTY)) {
+    // Mark all our kids starting with |child| dirty
+    for (; child; child = child->GetNextSibling()) {
+      child->AddStateBits(NS_FRAME_IS_DIRTY);
+    }
+  }
   
   // If we're doing RTL, we need to make sure our last column is at the left-hand side of the frame.
   if (RTL && childOrigin.x != targetX) {
     overflowRect = nsRect(0, 0, 0, 0);
     contentRect = nsRect(0, 0, 0, 0);
     PRInt32 deltaX = targetX - childOrigin.x;
 #ifdef DEBUG_roc
     printf("*** CHILDORIGIN.x = %d, targetX = %d, DELTAX = %d\n", childOrigin.x, targetX, deltaX);
@@ -892,30 +910,30 @@ nsColumnSetFrame::Reflow(nsPresContext* 
   // content back here and then have to push it out again!
   nsIFrame* nextInFlow = GetNextInFlow();
   PRBool unboundedLastColumn = isBalancing && !nextInFlow;
   nsCollapsingMargin carriedOutBottomMargin;
   ColumnBalanceData colData;
   PRBool feasible = ReflowChildren(aDesiredSize, aReflowState,
     aStatus, config, unboundedLastColumn, &carriedOutBottomMargin, colData);
 
-  if (isBalancing) {
+  if (isBalancing && !aPresContext->HasPendingInterrupt()) {
     nscoord availableContentHeight = GetAvailableContentHeight(aReflowState);
   
     // Termination of the algorithm below is guaranteed because
     // knownFeasibleHeight - knownInfeasibleHeight decreases in every
     // iteration.
     nscoord knownFeasibleHeight = NS_INTRINSICSIZE;
     nscoord knownInfeasibleHeight = 0;
     // We set this flag when we detect that we may contain a frame
     // that can break anywhere (thus foiling the linear decrease-by-one
     // search)
     PRBool maybeContinuousBreakingDetected = PR_FALSE;
 
-    while (1) {
+    while (!aPresContext->HasPendingInterrupt()) {
       nscoord lastKnownFeasibleHeight = knownFeasibleHeight;
 
       // Record what we learned from the last reflow
       if (feasible) {
         // maxHeight is feasible. Also, mLastBalanceHeight is feasible.
         knownFeasibleHeight = PR_MIN(knownFeasibleHeight, colData.mMaxHeight);
         knownFeasibleHeight = PR_MIN(knownFeasibleHeight, mLastBalanceHeight);
 
@@ -997,17 +1015,17 @@ nsColumnSetFrame::Reflow(nsPresContext* 
       
       unboundedLastColumn = PR_FALSE;
       AddStateBits(NS_FRAME_IS_DIRTY);
       feasible = ReflowChildren(aDesiredSize, aReflowState,
                                 aStatus, config, PR_FALSE, 
                                 &carriedOutBottomMargin, colData);
     }
 
-    if (!feasible) {
+    if (!feasible && !aPresContext->HasPendingInterrupt()) {
       // We may need to reflow one more time at the feasible height to
       // get a valid layout.
       PRBool skip = PR_FALSE;
       if (knownInfeasibleHeight >= availableContentHeight) {
         config.mColMaxHeight = availableContentHeight;
         if (mLastBalanceHeight == availableContentHeight) {
           skip = PR_TRUE;
         }
@@ -1016,16 +1034,23 @@ nsColumnSetFrame::Reflow(nsPresContext* 
       }
       if (!skip) {
         AddStateBits(NS_FRAME_IS_DIRTY);
         ReflowChildren(aDesiredSize, aReflowState, aStatus, config,
                        PR_FALSE, &carriedOutBottomMargin, colData);
       }
     }
   }
+
+  if (aPresContext->HasPendingInterrupt() &&
+      aReflowState.availableHeight == NS_UNCONSTRAINEDSIZE) {
+    // In this situation, we might be lying about our reflow status, because
+    // our last kid (the one that got interrupted) was incomplete.  Fix that.
+    aStatus = NS_FRAME_COMPLETE;
+  }
   
   CheckInvalidateSizeChange(aDesiredSize);
 
   FinishAndStoreOverflow(&aDesiredSize);
   aDesiredSize.mCarriedOutBottomMargin = carriedOutBottomMargin;
 
   NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
 
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -1921,17 +1921,17 @@ nsGfxScrollFrameInner::PostScrollEvent()
   }
 }
 
 NS_IMETHODIMP
 nsGfxScrollFrameInner::AsyncScrollPortEvent::Run()
 {
   if (mInner) {
     mInner->mOuter->PresContext()->GetPresShell()->
-      FlushPendingNotifications(Flush_Layout);
+      FlushPendingNotifications(Flush_InterruptibleLayout);
   }
   return mInner ? mInner->FireScrollPortEvent() : NS_OK;
 }
 
 PRBool
 nsXULScrollFrame::AddHorizontalScrollbar(nsBoxLayoutState& aState,
                                          nsRect& aScrollAreaSize, PRBool aOnTop)
 {
--- a/layout/generic/nsLineLayout.cpp
+++ b/layout/generic/nsLineLayout.cpp
@@ -2051,16 +2051,19 @@ nsLineLayout::VerticalAlignFrames(PerSpa
         nscoord yTop = -fontAscent - leading/2;
         nscoord yBottom = yTop + minimumLineHeight;
         if (yTop < minY) minY = yTop;
         if (yBottom > maxY) maxY = yBottom;
 
 #ifdef NOISY_VERTICAL_ALIGN
         printf(" new values: %d,%d\n", minY, maxY);
 #endif
+#ifdef NOISY_VERTICAL_ALIGN
+        printf("            Used mMinLineHeight: %d, fontHeight: %d, fontAscent: %d\n", mMinLineHeight, fontHeight, fontAscent);
+#endif
       }
       else {
         // XXX issues:
         // [1] BR's on empty lines stop working
         // [2] May not honor css2's notion of handling empty elements
         // [3] blank lines in a pre-section ("\n") (handled with preMode)
 
         // XXX Are there other problems with this?
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/67752-1-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      span { display: inline-block; width: 1em; height: 10px; background: green; }
+    </style>
+  </head>
+  <body style="position: relative; font-size: 20px">
+    Test
+    <div style="position: absolute; left: 0; width: 25px; height: 0; top: 0;">
+      <span></span>
+      <span></span>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/67752-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <head>
+    <script>
+      function f() {
+        document.body.style.fontSize = "20px";
+        // End the test asynchronously so we get a chance to do a reflow before
+        // that happens.
+        setTimeout("document.documentElement.className = ''", 0);
+      }
+    </script>
+    <style>
+      span { display: inline-block; width: 1em; height: 10px; background: green; }
+    </style>
+  </head>
+  <body onload="f()" style="position: relative; font-size: 10px">
+    Test
+    <div style="position: absolute; left: 0; width: 25px; height: 0; top: 0;">
+      <span></span>
+      <span></span>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/67752-2-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      span { display: inline-block; width: 1em; height: 10px; background: green; }
+    </style>
+  </head>
+  <body style="font-size: 20px">
+    Test
+    <div style="position: absolute; left: 0; width: 25px; height: 0; top: 0;">
+      <span></span>
+      <span></span>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/67752-2.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <head>
+    <script>
+      function f() {
+        document.body.style.fontSize = "20px";
+        // End the test asynchronously so we get a chance to do a reflow before
+        // that happens.
+        setTimeout("document.documentElement.className = ''", 0);
+      }
+    </script>
+    <style>
+      span { display: inline-block; width: 1em; height: 10px; background: green; }
+    </style>
+  </head>
+  <body onload="f()" style="font-size: 10px">
+    Test
+    <div style="position: absolute; left: 0; width: 25px; height: 0; top: 0;">
+      <span></span>
+      <span></span>
+    </div>
+  </body>
+</html>
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -61,16 +61,18 @@ fails == 25888-3r.html 25888-3r-ref.html
 == 50630-1a.html 50630-1-ref.html
 == 50630-1b.html 50630-1-ref.html
 == 50630-1c.html 50630-1-ref.html
 == 50630-2.html 50630-2-ref.html
 == 50630-3.html 50630-3-ref.html
 == 50630-4.html 50630-4-ref.html
 == 50630-4.html 50630-4-ref2.html
 == 50630-5.html 50630-5-ref.html
+== 67752-1.html 67752-1-ref.html
+== 67752-2.html 67752-2-ref.html
 == 68061-1.xml 68061-1-ref.xml
 == 68061-2.xml 68061-2-ref.xml
 == 76331-1.html 76331-1-ref.html
 == 84400-1.html 84400-1-ref.html
 == 84400-2.html 84400-2-ref.html
 == 97777-1.html 97777-1-ref.html
 == 97777-2.html 97777-2-ref.html
 == 98223-1.html 98223-1-ref.html
--- a/layout/svg/base/src/nsSVGForeignObjectFrame.cpp
+++ b/layout/svg/base/src/nsSVGForeignObjectFrame.cpp
@@ -374,16 +374,19 @@ nsSVGForeignObjectFrame::UpdateCoveredRe
 NS_IMETHODIMP
 nsSVGForeignObjectFrame::InitialUpdate()
 {
   NS_ASSERTION(GetStateBits() & NS_FRAME_FIRST_REFLOW,
                "Yikes! We've been called already! Hopefully we weren't called "
                "before our nsSVGOuterSVGFrame's initial Reflow()!!!");
 
   UpdateCoveredRegion();
+
+  // Make sure to not allow interrupts if we're not being reflown as a root
+  nsPresContext::InterruptPreventer noInterrupts(PresContext());
   DoReflow();
 
   NS_ASSERTION(!(mState & NS_FRAME_IN_REFLOW),
                "We don't actually participate in reflow");
   
   // Do unset the various reflow bits, though.
   mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
               NS_FRAME_HAS_DIRTY_CHILDREN);
@@ -596,16 +599,19 @@ nsSVGForeignObjectFrame::MaybeReflowFrom
   nsIFrame* kid = GetFirstChild(nsnull);
   if (kid->GetStateBits() & NS_FRAME_IS_DIRTY) {
     return;
   }
   kid->AddStateBits(NS_FRAME_IS_DIRTY); // we must be fully marked dirty
   if (kid->GetStateBits() & NS_FRAME_HAS_DIRTY_CHILDREN) {
     return;
   }
+
+  // Make sure to not allow interrupts if we're not being reflown as a root
+  nsPresContext::InterruptPreventer noInterrupts(PresContext());
   DoReflow();
 }
 
 void
 nsSVGForeignObjectFrame::DoReflow()
 {
 #ifdef DEBUG
   printf("**nsSVGForeignObjectFrame::DoReflow()\n");
--- a/widget/public/nsIWidget.h
+++ b/widget/public/nsIWidget.h
@@ -95,20 +95,20 @@ typedef nsEventStatus (* EVENT_CALLBACK)
 #define NS_NATIVE_PLUGIN_PORT_CG    101
 #endif
 #ifdef XP_WIN
 #define NS_NATIVE_TSF_THREAD_MGR       100
 #define NS_NATIVE_TSF_CATEGORY_MGR     101
 #define NS_NATIVE_TSF_DISPLAY_ATTR_MGR 102
 #endif
 
-// af70b716-2e34-463f-8f1c-273dbddd845b
+// 3d277f04-93f4-4384-9fdc-e1e2d1fc4e33
 #define NS_IWIDGET_IID \
-{ 0xaf70b716, 0x2e34, 0x463f, \
-  { 0x8f, 0x1c, 0x27, 0x3d, 0xbd, 0xdd, 0x84, 0x5b } }
+{ 0x3d277f04, 0x93f4, 0x4384, \
+ { 0x9f, 0xdc, 0xe1, 0xe2, 0xd1, 0xfc, 0x4e, 0x33 } }
 
 /*
  * Window shadow styles
  * Also used for the -moz-window-shadow CSS property
  */
 
 #define NS_STYLE_WINDOW_SHADOW_NONE             0
 #define NS_STYLE_WINDOW_SHADOW_DEFAULT          1
@@ -812,16 +812,22 @@ class nsIWidget : public nsISupports {
      * @param aTime Last user input time in milliseconds. This value can be used to compare
      * durations but can not be used for determining wall clock time. The value returned 
      * is platform dependent, but is compatible with the expression 
      * PR_IntervalToMicroseconds(PR_IntervalNow()).
      */
     NS_IMETHOD GetLastInputEventTime(PRUint32& aTime) = 0;
 
     /**
+     * Ask whether there user input events pending.  All input events are
+     * included, including those not targeted at this nsIwidget instance.
+     */
+    virtual PRBool HasPendingInputEvent() = 0;
+
+    /**
      * Called when when we need to begin secure keyboard input, such as when a password field
      * gets focus.
      *
      * NOTE: Calls to this method may not be nested and you can only enable secure keyboard input
      * for one widget at a time.
      */
     NS_IMETHOD BeginSecureKeyboardInput() = 0;
 
--- a/widget/src/cocoa/nsAppShell.mm
+++ b/widget/src/cocoa/nsAppShell.mm
@@ -663,16 +663,20 @@ nsAppShell::ProcessNextNativeEvent(PRBoo
   } else {
     mHadMoreEventsCount = 0;
   }
 
   mRunningEventLoop = wasRunningEventLoop;
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 
+  if (!moreEvents) {
+    nsChildView::UpdateCurrentInputEventCount();
+  }
+
   return moreEvents;
 }
 
 // Returns PR_TRUE if Gecko events are currently being processed in its "main"
 // event loop (or one of its "main" event loops).  Returns PR_FALSE if Gecko
 // events are being processed in a "nested" event loop, or if we're not
 // running in any sort of Gecko event loop.  How we process native events in
 // ProcessNextNativeEvent() turns on our decision (and if we make the wrong
--- a/widget/src/cocoa/nsChildView.h
+++ b/widget/src/cocoa/nsChildView.h
@@ -364,16 +364,18 @@ public:
   NS_IMETHOD        SetCursor(nsCursor aCursor);
   NS_IMETHOD        SetCursor(imgIContainer* aCursor, PRUint32 aHotspotX, PRUint32 aHotspotY);
   
   NS_IMETHOD        CaptureRollupEvents(nsIRollupListener * aListener, PRBool aDoCapture, PRBool aConsumeRollupEvent);
   NS_IMETHOD        SetTitle(const nsAString& title);
 
   NS_IMETHOD        GetAttention(PRInt32 aCycleCount);
 
+  virtual PRBool HasPendingInputEvent();
+
   NS_IMETHOD        ActivateNativeMenuItemAt(const nsAString& indexString);
   NS_IMETHOD        ForceUpdateNativeMenuAt(const nsAString& indexString);
 
   NS_IMETHOD        ResetInputState();
   NS_IMETHOD        SetIMEOpenState(PRBool aState);
   NS_IMETHOD        GetIMEOpenState(PRBool* aState);
   NS_IMETHOD        SetIMEEnabled(PRUint32 aState);
   NS_IMETHOD        GetIMEEnabled(PRUint32* aState);
@@ -404,16 +406,19 @@ public:
 
   virtual gfxASurface* GetThebesSurface();
 
   NS_IMETHOD BeginSecureKeyboardInput();
   NS_IMETHOD EndSecureKeyboardInput();
 
   void              HidePlugin();
 
+  static PRBool DoHasPendingInputEvent();
+  static PRUint32 GetCurrentInputEventCount();
+  static void UpdateCurrentInputEventCount();
 protected:
 
   PRBool            ReportDestroyEvent();
   PRBool            ReportMoveEvent();
   PRBool            ReportSizeEvent();
 
   virtual PRBool    OnPaint(nsPaintEvent & aEvent);
 
@@ -449,14 +454,16 @@ protected:
   PRPackedBool          mIsPluginView; // true if this is a plugin view
   PRPackedBool          mPluginDrawing;
   PRPackedBool          mPluginIsCG; // true if this is a CoreGraphics plugin
 
   PRPackedBool          mInSetFocus;
 
   nsPluginPort          mPluginPort;
   nsIPluginInstanceOwner* mPluginInstanceOwner; // [WEAK]
+
+  static PRUint32 sLastInputEventCount;
 };
 
 void NS_InstallPluginKeyEventsHandler();
 void NS_RemovePluginKeyEventsHandler();
 
 #endif // nsChildView_h_
--- a/widget/src/cocoa/nsChildView.mm
+++ b/widget/src/cocoa/nsChildView.mm
@@ -77,16 +77,18 @@
 
 #include "nsIDOMSimpleGestureEvent.h"
 
 #include "gfxContext.h"
 #include "gfxQuartzSurface.h"
 
 #include <dlfcn.h>
 
+#include <ApplicationServices/ApplicationServices.h>
+
 #undef DEBUG_IME
 #undef DEBUG_UPDATE
 #undef INVALIDATE_DEBUGGING  // flash areas as they are invalidated
 
 // Don't put more than this many rects in the dirty region, just fluff
 // out to the bounding-box if there are more
 #define MAX_RECTS_IN_REGION 100
 
@@ -144,16 +146,18 @@ static void blinkRgn(RgnHandle rgn);
 
 nsIRollupListener * gRollupListener = nsnull;
 nsIWidget         * gRollupWidget   = nsnull;
 
 PRUint32 gLastModifierState = 0;
 
 PRBool gUserCancelledDrag = PR_FALSE;
 
+PRUint32 nsChildView::sLastInputEventCount = 0;
+
 @interface ChildView(Private)
 
 // sets up our view, attaching it to its owning gecko view
 - (id)initWithFrame:(NSRect)inFrame geckoChild:(nsChildView*)inChild;
 
 // sends gecko an ime composition event
 - (nsIntRect) sendCompositionEvent:(PRInt32)aEventType;
 
@@ -2043,16 +2047,64 @@ NS_IMETHODIMP nsChildView::GetAttention(
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   [NSApp requestUserAttention:NSInformationalRequest];
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
+/* static */
+PRBool nsChildView::DoHasPendingInputEvent()
+{
+  return sLastInputEventCount != GetCurrentInputEventCount(); 
+}
+
+/* static */
+PRUint32 nsChildView::GetCurrentInputEventCount()
+{
+  // Can't use kCGAnyInputEventType because that updates too rarely for us (and
+  // always in increments of 30+!) and because apparently it's sort of broken
+  // on Tiger.  So just go ahead and query the counters we care about.
+  static const CGEventType eventTypes[] = {
+    kCGEventLeftMouseDown,
+    kCGEventLeftMouseUp,
+    kCGEventRightMouseDown,
+    kCGEventRightMouseUp,
+    kCGEventMouseMoved,
+    kCGEventLeftMouseDragged,
+    kCGEventRightMouseDragged,
+    kCGEventKeyDown,
+    kCGEventKeyUp,
+    kCGEventScrollWheel,
+    kCGEventTabletPointer,
+    kCGEventOtherMouseDown,
+    kCGEventOtherMouseUp,
+    kCGEventOtherMouseDragged
+  };
+
+  PRUint32 eventCount = 0;
+  for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(eventTypes); ++i) {
+    eventCount +=
+      CGEventSourceCounterForEventType(kCGEventSourceStateCombinedSessionState,
+                                       eventTypes[i]);
+  }
+  return eventCount;
+}
+
+/* static */
+void nsChildView::UpdateCurrentInputEventCount()
+{
+  sLastInputEventCount = GetCurrentInputEventCount();
+}
+
+PRBool nsChildView::HasPendingInputEvent()
+{
+  return DoHasPendingInputEvent();
+}
 
 #pragma mark -
 
 
 // Force Input Method Editor to commit the uncommitted input
 // Note that this and other IME methods don't necessarily
 // get called on the same ChildView that input is going through.
 NS_IMETHODIMP nsChildView::ResetInputState()
--- a/widget/src/cocoa/nsCocoaWindow.h
+++ b/widget/src/cocoa/nsCocoaWindow.h
@@ -242,16 +242,17 @@ public:
     NS_IMETHOD Invalidate(PRBool aIsSynchronous);
     NS_IMETHOD Update();
     NS_IMETHOD Scroll(PRInt32 aDx, PRInt32 aDy, nsIntRect *alCipRect) { return NS_OK; }
     NS_IMETHOD BeginResizingChildren(void) { return NS_OK; }
     NS_IMETHOD EndResizingChildren(void) { return NS_OK; }
     NS_IMETHOD DispatchEvent(nsGUIEvent* event, nsEventStatus & aStatus) ;
     NS_IMETHOD CaptureRollupEvents(nsIRollupListener * aListener, PRBool aDoCapture, PRBool aConsumeRollupEvent);
     NS_IMETHOD GetAttention(PRInt32 aCycleCount);
+    virtual PRBool HasPendingInputEvent();
     virtual nsTransparencyMode GetTransparencyMode();
     virtual void SetTransparencyMode(nsTransparencyMode aMode);
     NS_IMETHOD SetWindowShadowStyle(PRInt32 aStyle);
     NS_IMETHOD SetWindowTitlebarColor(nscolor aColor, PRBool aActive);
 
     // dispatch an NS_SIZEMODE event on miniaturize or deminiaturize
     void DispatchSizeModeEvent(nsSizeMode aSizeMode);
 
--- a/widget/src/cocoa/nsCocoaWindow.mm
+++ b/widget/src/cocoa/nsCocoaWindow.mm
@@ -57,16 +57,17 @@
 #include "nsIPrefBranch.h"
 #include "nsToolkit.h"
 #include "nsPrintfCString.h"
 #include "nsThreadUtils.h"
 #include "nsMenuBarX.h"
 #include "nsMenuUtilsX.h"
 #include "nsStyleConsts.h"
 #include "nsNativeThemeColors.h"
+#include "nsChildView.h"
 
 #include "gfxPlatform.h"
 #include "qcms.h"
 
 // defined in nsAppShell.mm
 extern nsCocoaAppModalWindowList *gCocoaAppModalWindowList;
 
 PRInt32 gXULModalLevel = 0;
@@ -1307,16 +1308,21 @@ NS_IMETHODIMP nsCocoaWindow::GetAttentio
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   [NSApp requestUserAttention:NSInformationalRequest];
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
+PRBool
+nsCocoaWindow::HasPendingInputEvent()
+{
+  return nsChildView::DoHasPendingInputEvent();
+}
 
 NS_IMETHODIMP nsCocoaWindow::SetWindowShadowStyle(PRInt32 aStyle)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   if ([mWindow hasShadow] != (aStyle != NS_STYLE_WINDOW_SHADOW_NONE))
     [mWindow setHasShadow:(aStyle != NS_STYLE_WINDOW_SHADOW_NONE)];
   return NS_OK;
--- a/widget/src/gtk2/nsWindow.cpp
+++ b/widget/src/gtk2/nsWindow.cpp
@@ -1906,16 +1906,49 @@ nsWindow::GetAttention(PRInt32 aCycleCou
     if (top_window && (GTK_WIDGET_VISIBLE(top_window)) &&
         top_window != top_focused_window) {
         SetUrgencyHint(top_window, PR_TRUE);
     }
 
     return NS_OK;
 }
 
+PRBool
+nsWindow::HasPendingInputEvent()
+{
+    // This sucks, but gtk/gdk has no way to answer the question we want while
+    // excluding paint events, and there's no X API that will let us peek
+    // without blocking or removing.  To prevent event reordering, peek
+    // anything except expose events.  Reordering expose and others should be
+    // ok, hopefully.
+    PRBool haveEvent;
+#ifdef MOZ_X11
+    XEvent ev;
+    haveEvent =
+        XCheckMaskEvent(GDK_DISPLAY(),
+                        KeyPressMask | KeyReleaseMask | ButtonPressMask |
+                        ButtonReleaseMask | EnterWindowMask | LeaveWindowMask |
+                        PointerMotionMask | PointerMotionHintMask |
+                        Button1MotionMask | Button2MotionMask |
+                        Button3MotionMask | Button4MotionMask |
+                        Button5MotionMask | ButtonMotionMask | KeymapStateMask |
+                        VisibilityChangeMask | StructureNotifyMask |
+                        ResizeRedirectMask | SubstructureNotifyMask |
+                        SubstructureRedirectMask | FocusChangeMask |
+                        PropertyChangeMask | ColormapChangeMask |
+                        OwnerGrabButtonMask, &ev);
+    if (haveEvent) {
+        XPutBackEvent(GDK_DISPLAY(), &ev);
+    }
+#else
+    haveEvent = PR_FALSE;
+#endif
+    return haveEvent;
+}
+
 void
 nsWindow::LoseFocus(void)
 {
     // make sure that we reset our key down counter so the next keypress
     // for this widget will get the down event
     memset(mKeyDownFlags, 0, sizeof(mKeyDownFlags));
 
     // Dispatch a lostfocus event
--- a/widget/src/gtk2/nsWindow.h
+++ b/widget/src/gtk2/nsWindow.h
@@ -199,16 +199,19 @@ public:
     NS_IMETHOD         EndResizingChildren(void);
     NS_IMETHOD         EnableDragDrop(PRBool aEnable);
     NS_IMETHOD         PreCreateWidget(nsWidgetInitData *aWidgetInitData);
     NS_IMETHOD         CaptureMouse(PRBool aCapture);
     NS_IMETHOD         CaptureRollupEvents(nsIRollupListener *aListener,
                                            PRBool aDoCapture,
                                            PRBool aConsumeRollupEvent);
     NS_IMETHOD         GetAttention(PRInt32 aCycleCount);
+
+    virtual PRBool     HasPendingInputEvent();
+
     NS_IMETHOD         MakeFullScreen(PRBool aFullScreen);
     NS_IMETHOD         HideWindowChrome(PRBool aShouldHide);
 
     // utility methods
     void               LoseFocus();
     gint               ConvertBorderStyles(nsBorderStyle aStyle);
 
     // event callbacks
--- a/widget/src/os2/nsWindow.cpp
+++ b/widget/src/os2/nsWindow.cpp
@@ -3388,26 +3388,30 @@ NS_METHOD nsWindow::SetIcon(const nsAStr
 
   WinSendMsg(mFrameWnd, WM_SETICON, (MPARAM)hWorkingIcon, (MPARAM)0);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWindow::GetLastInputEventTime(PRUint32& aTime)
 {
-   ULONG ulStatus = WinQueryQueueStatus(HWND_DESKTOP);
-
-   // If there is pending input then return the current time.
-   if (ulStatus & (QS_KEY | QS_MOUSE)) {
-     gLastInputEventTime = PR_IntervalToMicroseconds(PR_IntervalNow());
-   }
-
-   aTime = gLastInputEventTime;
-
-   return NS_OK;
+  // If there is pending input then return the current time.
+  if (HasPendingInputEvent()) {
+    gLastInputEventTime = PR_IntervalToMicroseconds(PR_IntervalNow());
+  }
+
+  aTime = gLastInputEventTime;
+
+  return NS_OK;
+}
+
+PRBool
+nsWindow::HasPendingInputEvent()
+{
+  return (WinQueryQueueStatus(HWND_DESKTOP) & (QS_KEY | QS_MOUSE)) != 0;
 }
 
 // --------------------------------------------------------------------------
 // OS2-specific routines to emulate Windows behaviors
 // --------------------------------------------------------------------------
 
 BOOL nsWindow::SetWindowPos( HWND ib, long x, long y, long cx, long cy, ULONG flags)
 {
--- a/widget/src/os2/nsWindow.h
+++ b/widget/src/os2/nsWindow.h
@@ -156,16 +156,17 @@ class nsWindow : public nsBaseWidget,
 
    NS_IMETHOD BeginResizingChildren();
    NS_IMETHOD EndResizingChildren();
    virtual nsIntPoint WidgetToScreenOffset();
    NS_IMETHOD DispatchEvent( struct nsGUIEvent *event, nsEventStatus &aStatus);
    NS_IMETHOD CaptureRollupEvents(nsIRollupListener * aListener, PRBool aDoCapture, PRBool aConsumeRollupEvent);
 
    NS_IMETHOD              GetLastInputEventTime(PRUint32& aTime);
+   virtual PRBool          HasPendingInputEvent();
 
    // Widget appearance
    NS_IMETHOD              SetCursor( nsCursor aCursor);
    NS_IMETHOD              SetCursor(imgIContainer* aCursor,
                                      PRUint32 aHotspotX, PRUint32 aHotspotY);
    NS_IMETHOD              HideWindowChrome(PRBool aShouldHide);
    NS_IMETHOD              SetTitle( const nsAString& aTitle); 
    NS_IMETHOD              SetIcon(const nsAString& aIconSpec); 
--- a/widget/src/windows/nsWindow.cpp
+++ b/widget/src/windows/nsWindow.cpp
@@ -7962,33 +7962,38 @@ void nsWindow::StopFlashing()
     FLASHW_STOP, 0, 0 };
   ::FlashWindowEx(&flashInfo);
 #endif
 }
 
 NS_IMETHODIMP
 nsWindow::GetLastInputEventTime(PRUint32& aTime)
 {
-  WORD qstatus = HIWORD(GetQueueStatus(QS_INPUT));
-
-  // If there is pending input or the user is currently
-  // moving the window then return the current time.
-  // Note: When the user is moving the window WIN32 spins
-  // a separate event loop and input events are not
-  // reported to the application.
-  nsToolkit* toolkit = (nsToolkit *)mToolkit;
-  if (qstatus || (toolkit && toolkit->UserIsMovingWindow())) {
+  if (HasPendingInputEvent()) {
     gLastInputEventTime = PR_IntervalToMicroseconds(PR_IntervalNow());
   }
 
   aTime = gLastInputEventTime;
 
   return NS_OK;
 }
 
+PRBool
+nsWindow::HasPendingInputEvent()
+{
+  // If there is pending input or the user is currently
+  // moving the window then return true.
+  // Note: When the user is moving the window WIN32 spins
+  // a separate event loop and input events are not
+  // reported to the application.
+  WORD qstatus = HIWORD(GetQueueStatus(QS_INPUT));
+  nsToolkit* toolkit = (nsToolkit *)mToolkit;
+  return qstatus || (toolkit && toolkit->UserIsMovingWindow());
+}
+
 //-------------------------------------------------------------------------
 //-------------------------------------------------------------------------
 //-- NOTE!!! These hook functions can be removed when we migrate to
 //-- XBL-Form Controls
 //-------------------------------------------------------------------------
 //-------------------------------------------------------------------------
 //#define DISPLAY_NOISY_MSGF_MSG
 
--- a/widget/src/windows/nsWindow.h
+++ b/widget/src/windows/nsWindow.h
@@ -194,16 +194,17 @@ public:
   NS_IMETHOD              EnableDragDrop(PRBool aEnable);
 
   virtual void            SetUpForPaint(HDC aHDC);
 
   NS_IMETHOD              CaptureRollupEvents(nsIRollupListener * aListener, PRBool aDoCapture, PRBool aConsumeRollupEvent);
 
   NS_IMETHOD              GetAttention(PRInt32 aCycleCount);
   NS_IMETHOD              GetLastInputEventTime(PRUint32& aTime);
+  virtual PRBool          HasPendingInputEvent();
 
   // Note that the result of GetTopLevelWindow method can be different from the
   // result of GetTopLevelHWND method.  The result can be non-floating window.
   // Because our top level window may be contained in another window which is
   // not managed by us.
   nsWindow*               GetTopLevelWindow(PRBool aStopOnDialogOrPopup);
 
   gfxASurface             *GetThebesSurface();
--- a/widget/src/xpwidgets/nsBaseWidget.cpp
+++ b/widget/src/xpwidgets/nsBaseWidget.cpp
@@ -793,16 +793,22 @@ nsBaseWidget::GetAttention(PRInt32 aCycl
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsBaseWidget::GetLastInputEventTime(PRUint32& aTime) {
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
+PRBool
+nsBaseWidget::HasPendingInputEvent()
+{
+  return PR_FALSE;
+}
+
 NS_IMETHODIMP
 nsBaseWidget::SetIcon(const nsAString&)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsBaseWidget::BeginSecureKeyboardInput()
--- a/widget/src/xpwidgets/nsBaseWidget.h
+++ b/widget/src/xpwidgets/nsBaseWidget.h
@@ -117,16 +117,17 @@ public:
   NS_IMETHOD              AddEventListener(nsIEventListener * aListener);
   NS_IMETHOD              SetBounds(const nsIntRect &aRect);
   NS_IMETHOD              GetBounds(nsIntRect &aRect);
   NS_IMETHOD              GetClientBounds(nsIntRect &aRect);
   NS_IMETHOD              GetScreenBounds(nsIntRect &aRect);
   NS_IMETHOD              EnableDragDrop(PRBool aEnable);
   NS_IMETHOD              GetAttention(PRInt32 aCycleCount);
   NS_IMETHOD              GetLastInputEventTime(PRUint32& aTime);
+  virtual PRBool          HasPendingInputEvent();
   NS_IMETHOD              SetIcon(const nsAString &anIconSpec);
   NS_IMETHOD              BeginSecureKeyboardInput();
   NS_IMETHOD              EndSecureKeyboardInput();
   NS_IMETHOD              SetWindowTitlebarColor(nscolor aColor, PRBool aActive);
   virtual PRBool          ShowsResizeIndicator(nsIntRect* aResizerRect);
   virtual void            FreeNativeData(void * data, PRUint32 aDataType) {}
   NS_IMETHOD              BeginResizeDrag(nsGUIEvent* aEvent, PRInt32 aHorizontal, PRInt32 aVertical);
   virtual nsresult        ActivateNativeMenuItemAt(const nsAString& indexString) { return NS_ERROR_NOT_IMPLEMENTED; }