diff --git a/content/base/public/mozFlushType.h b/content/base/public/mozFlushType.h --- a/content/base/public/mozFlushType.h +++ b/content/base/public/mozFlushType.h @@ -48,8 +48,12 @@ 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___ */ diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -6282,8 +6282,8 @@ // 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); } diff --git a/content/events/src/nsEventStateManager.cpp b/content/events/src/nsEventStateManager.cpp --- a/content/events/src/nsEventStateManager.cpp +++ b/content/events/src/nsEventStateManager.cpp @@ -5646,7 +5646,7 @@ NS_PRECONDITION(nsnull != aPresContext, "nsnull ptr"); nsIPresShell *shell = aPresContext->GetPresShell(); if (shell) { - shell->FlushPendingNotifications(Flush_Display); + shell->FlushPendingNotifications(Flush_InterruptibleLayout); } } diff --git a/content/html/content/src/nsGenericHTMLElement.cpp b/content/html/content/src/nsGenericHTMLElement.cpp --- a/content/html/content/src/nsGenericHTMLElement.cpp +++ b/content/html/content/src/nsGenericHTMLElement.cpp @@ -1303,9 +1303,8 @@ 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) { @@ -2685,7 +2684,10 @@ { 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 presShell = aPresContext->GetPresShell(); diff --git a/content/html/document/src/nsHTMLContentSink.cpp b/content/html/document/src/nsHTMLContentSink.cpp --- a/content/html/document/src/nsHTMLContentSink.cpp +++ b/content/html/document/src/nsHTMLContentSink.cpp @@ -3199,7 +3199,7 @@ 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); diff --git a/content/xml/document/src/nsXMLContentSink.cpp b/content/xml/document/src/nsXMLContentSink.cpp --- a/content/xml/document/src/nsXMLContentSink.cpp +++ b/content/xml/document/src/nsXMLContentSink.cpp @@ -1647,7 +1647,7 @@ 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); diff --git a/layout/base/nsIPresShell.h b/layout/base/nsIPresShell.h --- a/layout/base/nsIPresShell.h +++ b/layout/base/nsIPresShell.h @@ -102,10 +102,10 @@ typedef short SelectionType; typedef PRUint32 nsFrameState; -// fa7f090d-b19a-4ef8-9552-82992a3b4a83 -#define NS_IPRESSHELL_IID \ -{ 0xfa7f090d, 0xb19a, 0x4ef8, \ - { 0x95, 0x52, 0x82, 0x99, 0x2a, 0x3b, 0x4a, 0x83 } } +// 4fb87dae-8986-429f-b6ba-f040750e3ee8 + #define NS_IPRESSHELL_IID \ + { 0x4fb87dae, 0x8986, 0x429f, \ + { 0xb6, 0xba, 0xf0, 0x40, 0x75, 0x0e, 0x3e, 0xe8 } } // Constants for ScrollContentIntoView() function #define NS_PRESSHELL_SCROLL_TOP 0 @@ -124,6 +124,8 @@ #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 @@ -368,6 +370,18 @@ IntrinsicDirty aIntrinsicDirty, nsFrameState aBitToAdd) = 0; + /** + * Tell the presshell to 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) MarkPathToFrameDirty(nsIFrame *aFrame) = 0; + NS_IMETHOD CancelAllPendingReflows() = 0; /** @@ -462,7 +476,7 @@ */ NS_IMETHOD ScrollContentIntoView(nsIContent* aContent, PRIntn aVPercent, - PRIntn aHPercent) const = 0; + PRIntn aHPercent) = 0; /** * Suppress notification of the frame manager that frames are diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -90,6 +90,8 @@ #include "nsFontFaceLoader.h" #include "nsIEventListenerManager.h" #include "nsStyleStructInlines.h" +#include "nsIAppShell.h" +#include "prenv.h" #ifdef MOZ_SMIL #include "nsSMILAnimationController.h" @@ -2027,3 +2029,112 @@ { return mShell && mShell->StyleSet()->HasCachedStyleData(); } + +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() +{ + sGotInterruptEnv = PR_TRUE; + char *ev = PR_GetEnv("GECKO_REFLOW_INTERRUPT_MODE"); + if (ev) { + if (PL_strcasecmp(ev, "random") == 0) { + ev = PR_GetEnv("GECKO_REFLOW_INTERRUPT_SEED"); + if (ev) { + sInterruptSeed = atoi(ev); + } + srandom(sInterruptSeed); + sInterruptMode = ModeRandom; + } else 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); + } +} + +static PRBool HavePendingInputEvent() +{ + switch (sInterruptMode) { + case ModeRandom: + return (random() & 1); + case ModeCounter: + if (sInterruptCounter < sInterruptMaxCounter) { + ++sInterruptCounter; + return PR_FALSE; + } + sInterruptCounter = 0; + return PR_TRUE; + default: + case ModeEvent: { + static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); + nsCOMPtr appShell = do_GetService(kAppShellCID); + if (!appShell) + return PR_FALSE; + PRBool result; + appShell->GetHasPendingInputEvent(&result); + return result; + } + } +} + +void +nsPresContext::SetInterruptState(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() +{ + if (mHasPendingInterrupt) + return PR_TRUE; + + if (!sGotInterruptEnv) { + GetInterruptEnv(); + } + + if (mInterruptChecksToSkip > 0) { + --mInterruptChecksToSkip; + return PR_FALSE; + } + mInterruptChecksToSkip = sInterruptChecksToSkip; + if (!mInterruptsEnabled) + return PR_FALSE; + + mHasPendingInterrupt = HavePendingInputEvent(); +#ifdef NOISY_INTERRUPTIBLE_REFLOW + if (mHasPendingInterrupt) { + printf("*** DETECTED pending interrupt (time=%lld)\n", PR_Now()); + } +#endif /* NOISY_INTERRUPTIBLE_REFLOW */ + return mHasPendingInterrupt; +} diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h --- a/layout/base/nsPresContext.h +++ b/layout/base/nsPresContext.h @@ -807,6 +807,54 @@ mCrossDocDirtyRegion.SetEmpty(); } + + + /** + * Set the interruptibility of this presContext. 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. + */ + void SetInterruptState(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. + */ + PRBool CheckForInterrupt(); + /** + * 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; NS_HIDDEN_(void) ThemeChangedInternal(); @@ -922,6 +970,10 @@ nscoord mBorderWidthTable[3]; + PRUint32 mInterruptChecksToSkip; + + unsigned mHasPendingInterrupt : 1; + unsigned mInterruptsEnabled : 1; unsigned mUseDocumentFonts : 1; unsigned mUseDocumentColors : 1; unsigned mUnderlineLinks : 1; diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -934,6 +934,7 @@ nsIFrame** aPlaceholderFrame) const; NS_IMETHOD FrameNeedsReflow(nsIFrame *aFrame, IntrinsicDirty aIntrinsicDirty, nsFrameState aBitToAdd); + NS_IMETHOD_(void) MarkPathToFrameDirty(nsIFrame *aFrame); NS_IMETHOD CancelAllPendingReflows(); NS_IMETHOD IsSafeToFlush(PRBool& aIsSafeToFlush); NS_IMETHOD FlushPendingNotifications(mozFlushType aType); @@ -957,7 +958,7 @@ NS_IMETHOD ScrollContentIntoView(nsIContent* aContent, PRIntn aVPercent, - PRIntn aHPercent) const; + PRIntn aHPercent); NS_IMETHOD SetIgnoreFrameDestruction(PRBool aIgnore); NS_IMETHOD NotifyDestroyingFrame(nsIFrame* aFrame); @@ -1133,22 +1134,30 @@ 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; @@ -1233,11 +1242,6 @@ // 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 mPrefStyleSheet; // mStyleSet owns it but we // maintain a ref, may be null #ifdef DEBUG @@ -1268,6 +1272,26 @@ nsRevocableEventPtr mReflowEvent; + +#ifdef DEBUG + // The reflow root under which we're currently reflowing. Null when + // not in reflow. + nsIFrame* mCurrentReflowRoot; +#endif + + // Array of frames that we should mark with NS_FRAME_HAS_DIRTY_CHILDREN after + // we finish reflowing mCurrentReflowRoot. + nsAutoTArray 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 mContentToScrollTo; + PRIntn mContentScrollVPosition; + PRIntn mContentScrollHPosition; + struct nsBlurOrFocusTarget { nsBlurOrFocusTarget(nsPIDOMEventTarget* aTarget, PRUint32 aEventType) @@ -1283,6 +1307,8 @@ nsCallbackEventRequest* mFirstCallbackEventRequest; nsCallbackEventRequest* mLastCallbackEventRequest; + + PRPackedBool mSuppressInterruptibleReflows; PRPackedBool mIsThemeSupportDisabled; // Whether or not form controls should use nsITheme in this shell. @@ -1748,6 +1774,8 @@ if (mHaveShutDown) return NS_OK; + + mContentToScrollTo = nsnull; if (mPresContext) { // We need to notify the destroying the nsPresContext to ESM for @@ -2694,14 +2722,12 @@ // Kick off a top-down reflow AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow); - mIsReflowing = PR_TRUE; mDirtyRoots.RemoveElement(rootFrame); - DoReflow(rootFrame); - mIsReflowing = PR_FALSE; - } - - DidDoReflow(); + DoReflow(rootFrame, PR_TRUE); + } + + DidDoReflow(PR_TRUE); } batch.EndUpdateViewBatch(NS_VMREFRESH_NO_SYNC); @@ -3411,6 +3437,20 @@ return NS_OK; } +NS_IMETHODIMP_(void) +PresShell::MarkPathToFrameDirty(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.AppendElement(aFrame); +} + nsIScrollableView* PresShell::GetViewToScroll(nsLayoutUtils::Direction aDirection) { @@ -3541,6 +3581,13 @@ nsIContent *currentEventContent = aFrame->GetContent(); mCurrentEventContentStack.ReplaceObjectAt(currentEventContent, i); mCurrentEventFrameStack[i] = nsnull; + } + } + + for (unsigned int i = 0; i < mFramesToDirty.Length(); ++i) { + if (aFrame == mFramesToDirty[i]) { + mFramesToDirty.RemoveElementAt(i); + --i; } } @@ -4082,17 +4129,21 @@ NS_IMETHODIMP PresShell::ScrollContentIntoView(nsIContent* aContent, PRIntn aVPercent, - PRIntn aHPercent) const -{ + PRIntn aHPercent) +{ + mContentToScrollTo = aContent; + mContentScrollVPosition = aVPercent; + mContentScrollHPosition = aHPercent; + nsCOMPtr content = aContent; // Keep content alive while flushing. NS_ENSURE_TRUE(content, NS_ERROR_NULL_POINTER); nsCOMPtr 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 @@ -4109,9 +4160,24 @@ 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. @@ -4576,7 +4642,7 @@ } void -PresShell::HandlePostedReflowCallbacks() +PresShell::HandlePostedReflowCallbacks(PRBool aInterruptible) { PRBool shouldFlush = PR_FALSE; @@ -4595,8 +4661,10 @@ } } + mozFlushType flushType = + aInterruptible ? Flush_InterruptibleLayout : Flush_Layout; if (shouldFlush) - FlushPendingNotifications(Flush_Layout); + FlushPendingNotifications(flushType); } NS_IMETHODIMP @@ -4621,15 +4689,8 @@ } -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?"); @@ -4698,11 +4759,17 @@ // 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; @@ -4711,7 +4778,7 @@ // 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. @@ -6545,7 +6612,7 @@ // 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 @@ -6755,7 +6822,17 @@ // before processing that pres shell's reflow commands. Fixes bug 54868. nsCOMPtr 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; @@ -6802,11 +6879,11 @@ } 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) @@ -6819,8 +6896,8 @@ } } -void -PresShell::DoReflow(nsIFrame* target) +PRBool +PresShell::DoReflow(nsIFrame* target, PRBool aInterruptible) { nsIFrame* rootFrame = FrameManager()->GetRootFrame(); @@ -6829,9 +6906,13 @@ // 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); @@ -6865,6 +6946,9 @@ size.width - reflowState.mComputedBorderPadding.LeftRight(), "reflow state computed incorrect width"); + + mPresContext->SetInterruptState(aInterruptible); + mIsReflowing = PR_TRUE; nsReflowStatus status; nsHTMLReflowMetrics desiredSize; @@ -6897,6 +6981,48 @@ mPresContext->SetVisibleArea(nsRect(0, 0, desiredSize.width, desiredSize.height)); } + +#ifdef DEBUG + mCurrentReflowRoot = nsnull; +#endif + + NS_ASSERTION(mPresContext->HasPendingInterrupt() || + mFramesToDirty.Length() == 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. + for (PRUint32 i = 0; i < mFramesToDirty.Length(); ++i) { + for (nsIFrame* f = mFramesToDirty[i]; f && !NS_SUBTREE_DIRTY(f); + f = f->GetParent()) { + f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); + + if (f == target) { + break; + } + } + } + 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.Length() == %u\n", mFramesToDirty.Length()); +#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(); + } + + mPresContext->SetInterruptState(PR_FALSE); + return !interrupted; } #ifdef DEBUG @@ -6933,12 +7059,13 @@ } #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 @@ -6958,7 +7085,6 @@ { nsAutoScriptBlocker scriptBlocker; AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow); - mIsReflowing = PR_TRUE; do { // Send an incremental reflow notification to the target frame. @@ -6973,22 +7099,22 @@ 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)); + + interrupted = mDirtyRoots.Length() != 0; // XXXwaterson for interruptible reflow, examine the tree and // re-enqueue any unflowed reflow targets. - - mIsReflowing = PR_FALSE; } // Exiting the scriptblocker might have killed us if (!mIsDestroying) { - DidDoReflow(); + DidDoReflow(aInterruptible); } // DidDoReflow might have killed us @@ -7024,7 +7150,7 @@ UnsuppressAndInvalidate(); } - return NS_OK; + return !interrupted; } void diff --git a/layout/generic/nsAbsoluteContainingBlock.cpp b/layout/generic/nsAbsoluteContainingBlock.cpp --- a/layout/generic/nsAbsoluteContainingBlock.cpp +++ b/layout/generic/nsAbsoluteContainingBlock.cpp @@ -147,9 +147,9 @@ 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, @@ -188,7 +188,20 @@ 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); + } + } } + + if (aPresContext->HasPendingInterrupt()) { + aPresContext->PresShell()->MarkPathToFrameDirty(aDelegatingFrame); + } + // Abspos frames can't cause their parent to be incomplete, // only overflow incomplete. if (NS_FRAME_IS_NOT_COMPLETE(reflowStatus)) @@ -333,12 +346,14 @@ } void -nsAbsoluteContainingBlock::MarkSizeDependentFramesDirty() +nsAbsoluteContainingBlock::MarkFramesDirty(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); } diff --git a/layout/generic/nsAbsoluteContainingBlock.h b/layout/generic/nsAbsoluteContainingBlock.h --- a/layout/generic/nsAbsoluteContainingBlock.h +++ b/layout/generic/nsAbsoluteContainingBlock.h @@ -127,7 +127,10 @@ PRBool HasAbsoluteFrames() {return mAbsoluteFrames.NotEmpty();} - void MarkSizeDependentFramesDirty(); + // 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 MarkFramesDirty(PRBool aMarkAllDirty); protected: // Returns PR_TRUE if the position of f depends on the position of diff --git a/layout/generic/nsBlockFrame.cpp b/layout/generic/nsBlockFrame.cpp --- a/layout/generic/nsBlockFrame.cpp +++ b/layout/generic/nsBlockFrame.cpp @@ -1122,12 +1122,14 @@ // 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()) { + if (aReflowState.WillReflowAgainForClearance() || + aPresContext->HasPendingInterrupt()) { // 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). 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.MarkFramesDirty((GetStateBits() & NS_FRAME_IS_DIRTY) != 0); } else { nsRect childBounds; nsSize containingBlockSize = @@ -1163,6 +1165,11 @@ // Factor the absolutely positioned child bounds into the overflow area aMetrics.mOverflowArea.UnionRect(aMetrics.mOverflowArea, childBounds); } + } + + if (aPresContext->HasPendingInterrupt()) { + // Make sure we'll actually be reached by a reflow + aPresContext->PresShell()->MarkPathToFrameDirty(this); } // Determine if we need to repaint our border, background or outline @@ -1722,6 +1729,23 @@ aDeltaY, aState.mPrevBottomMargin.get(), aLine->GetChildCount()); } #endif +} + +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; + } } /** @@ -2016,6 +2040,18 @@ } DumpLine(aState, line, deltaY, -1); + + if (aState.mPresContext->CheckForInterrupt()) { + 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 @@ -2037,8 +2073,9 @@ 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, @@ -2047,8 +2084,10 @@ // 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) && @@ -2067,13 +2106,17 @@ // (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) { @@ -2157,28 +2200,38 @@ 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); - } - break; - } - - if (LineHasClear(line.get())) { - foundAnyClears = PR_TRUE; - } - - // If this is an inline frame then its time to stop - ++line; - aState.AdvanceToNextLine(); + 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; + } + + if (LineHasClear(line.get())) { + foundAnyClears = PR_TRUE; + } + + if (aState.mPresContext->CheckForInterrupt()) { + willReflowAgain = PR_TRUE; + MarkLineDirtyForInterrupt(line); + break; + } + + // If this is an inline frame then its time to stop + ++line; + aState.AdvanceToNextLine(); + } } } @@ -2214,6 +2267,38 @@ #endif return rv; +} + +void +nsBlockFrame::MarkLineDirtyForInterrupt(nsLineBox* aLine) +{ + // XXXbz do we need to invalidate text runs here? + 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 @@ -4959,23 +5044,6 @@ 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; diff --git a/layout/generic/nsBlockFrame.h b/layout/generic/nsBlockFrame.h --- a/layout/generic/nsBlockFrame.h +++ b/layout/generic/nsBlockFrame.h @@ -462,6 +462,9 @@ /** 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 /** diff --git a/layout/generic/nsColumnSetFrame.cpp b/layout/generic/nsColumnSetFrame.cpp --- a/layout/generic/nsColumnSetFrame.cpp +++ b/layout/generic/nsColumnSetFrame.cpp @@ -751,6 +751,12 @@ } } + if (PresContext()->HasPendingInterrupt()) { + // Stop the loop now while |child| still points to the frame + // that bailed out + break; + } + // Advance to the next column child = child->GetNextSibling(); @@ -764,6 +770,14 @@ #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); } } @@ -893,7 +907,7 @@ 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 @@ -906,7 +920,7 @@ // search) PRBool maybeContinuousBreakingDetected = PR_FALSE; - while (1) { + while (!aPresContext->HasPendingInterrupt()) { nscoord lastKnownFeasibleHeight = knownFeasibleHeight; // Record what we learned from the last reflow @@ -998,7 +1012,7 @@ &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; @@ -1015,6 +1029,16 @@ ReflowChildren(aDesiredSize, aReflowState, aStatus, config, PR_FALSE, &carriedOutBottomMargin, colData); } + } + } + + if (aPresContext->HasPendingInterrupt()) { + // Make sure we'll actually be reached by a reflow. + aPresContext->PresShell()->MarkPathToFrameDirty(this); + if (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; } } diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp --- a/layout/generic/nsGfxScrollFrame.cpp +++ b/layout/generic/nsGfxScrollFrame.cpp @@ -1929,7 +1929,7 @@ { if (mInner) { mInner->mOuter->PresContext()->GetPresShell()-> - FlushPendingNotifications(Flush_Layout); + FlushPendingNotifications(Flush_InterruptibleLayout); } return mInner ? mInner->FireScrollPortEvent() : NS_OK; } diff --git a/layout/generic/nsLineLayout.cpp b/layout/generic/nsLineLayout.cpp --- a/layout/generic/nsLineLayout.cpp +++ b/layout/generic/nsLineLayout.cpp @@ -2056,6 +2056,9 @@ #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: diff --git a/layout/reftests/bugs/reftest.list b/layout/reftests/bugs/reftest.list --- a/layout/reftests/bugs/reftest.list +++ b/layout/reftests/bugs/reftest.list @@ -65,6 +65,8 @@ == 50630-3.html 50630-3-ref.html == 50630-4.html 50630-4-ref.html == 50630-4.html 50630-4-ref2.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 diff --git a/layout/svg/base/src/nsSVGForeignObjectFrame.cpp b/layout/svg/base/src/nsSVGForeignObjectFrame.cpp --- a/layout/svg/base/src/nsSVGForeignObjectFrame.cpp +++ b/layout/svg/base/src/nsSVGForeignObjectFrame.cpp @@ -392,6 +392,9 @@ "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), @@ -602,6 +605,9 @@ 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(); } diff --git a/widget/public/nsIAppShell.idl b/widget/public/nsIAppShell.idl --- a/widget/public/nsIAppShell.idl +++ b/widget/public/nsIAppShell.idl @@ -43,7 +43,7 @@ * Interface for the native event system layer. This interface is designed * to be used on the main application thread only. */ -[uuid(501403e9-a091-4780-ba55-cfd1e21287a1)] +[uuid(d60a911e-98b3-4dc4-a6c0-f1af6c9277b2)] interface nsIAppShell : nsISupports { /** @@ -98,6 +98,16 @@ void resumeNative(); /** + * Check if there is any input pending to any window for this application. + * (If there is, we probably want to return to the event loop as + * soon as possible.) "Input" should include keypresses and + * mouse button up/down, and mouse moves with a button down, but + * not mouse moves with no button down. + * This should be cheap but we try not to call it too often. + */ + readonly attribute boolean hasPendingInputEvent; + + /** * The current event loop nesting level. */ readonly attribute unsigned long eventloopNestingLevel; diff --git a/widget/public/nsIWidget.h b/widget/public/nsIWidget.h --- a/widget/public/nsIWidget.h +++ b/widget/public/nsIWidget.h @@ -99,10 +99,10 @@ #define NS_NATIVE_TSF_DISPLAY_ATTR_MGR 102 #endif -// 0dda48db-4f61-44a7-9f92-041cd92b8a9c +// 0b019a9e-d552-4d5b-8b87-6bdb05f0732a #define NS_IWIDGET_IID \ -{ 0x0dda48db, 0x4f61, 0x44a7, \ - { 0x9f, 0x92, 0x04, 0x1c, 0xd9, 0x2b, 0x8a, 0x9c } } + { 0x0b019a9e, 0xd552, 0x4d5b, \ + { 0x8b, 0x87, 0x6b, 0xdb, 0x05, 0xf0, 0x73, 0x2a } } // Hide the native window systems real window type so as to avoid // including native window system types and APIs. This is necessary @@ -1260,6 +1260,9 @@ */ NS_IMETHOD OnIMESelectionChange(void) = 0; + virtual void ResetAppInputPendingState() {} + virtual PRBool IsAppInputPending() { return PR_FALSE; } + protected: // keep the list of children. We also keep track of our siblings. // The ownership model is as follows: parent holds a strong ref to diff --git a/widget/src/cocoa/nsAppShell.h b/widget/src/cocoa/nsAppShell.h --- a/widget/src/cocoa/nsAppShell.h +++ b/widget/src/cocoa/nsAppShell.h @@ -97,6 +97,8 @@ NS_IMETHOD AfterProcessNextEvent(nsIThreadInternal *aThread, PRUint32 aRecursionDepth); + NS_IMETHOD GetHasPendingInputEvent(PRBool *aResult); + // public only to be visible to Objective-C code that must call it void WillTerminate(); @@ -125,15 +127,8 @@ PRPackedBool mSkippedNativeCallback; PRPackedBool mRunningCocoaEmbedded; - // mHadMoreEventsCount and kHadMoreEventsCountMax are used in - // ProcessNextNativeEvent(). - PRUint32 mHadMoreEventsCount; - // Setting kHadMoreEventsCountMax to '10' contributed to a fairly large - // (about 10%) increase in the number of calls to malloc (though without - // effecting the total amount of memory used). Cutting it to '3' - // reduced the number of calls by 6%-7% (reducing the original regression - // to 3%-4%). See bmo bug 395397. - static const PRUint32 kHadMoreEventsCountMax = 3; + // Used by GetHasPendingInputEvent + PRIntervalTime mLastNativeEventTime; PRInt32 mRecursionDepth; PRInt32 mNativeEventCallbackDepth; diff --git a/widget/src/cocoa/nsAppShell.mm b/widget/src/cocoa/nsAppShell.mm --- a/widget/src/cocoa/nsAppShell.mm +++ b/widget/src/cocoa/nsAppShell.mm @@ -41,6 +41,7 @@ */ #import +#include "ApplicationServices/ApplicationServices.h" #include "nsAppShell.h" #include "nsCOMPtr.h" @@ -207,7 +208,7 @@ , mTerminated(PR_FALSE) , mNotifiedWillTerminate(PR_FALSE) , mSkippedNativeCallback(PR_FALSE) -, mHadMoreEventsCount(0) +, mLastNativeEventTime(0) , mRecursionDepth(0) , mNativeEventCallbackDepth(0) { @@ -509,17 +510,10 @@ // // Returns true if more events are waiting in the native event queue. // -// But (now that we're using [NSRunLoop runMode:beforeDate:]) it's too -// expensive to call ProcessNextNativeEvent() many times in a row (in a -// tight loop), so we never return true more than kHadMoreEventsCountMax -// times in a row. This doesn't seem to cause native event starvation. -// // protected virtual PRBool nsAppShell::ProcessNextNativeEvent(PRBool aMayWait) { - PRBool moreEvents = PR_FALSE; - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; PRBool eventProcessed = PR_FALSE; @@ -568,15 +562,6 @@ // You also sometimes had to ctrl-click or right-click multiple times to // bring up a context menu.) - // Now that we're using [NSRunLoop runMode:beforeDate:], it's too - // expensive to call ProcessNextNativeEvent() many times in a row, so we - // never return true more than kHadMoreEventsCountMax in a row. I'm not - // entirely sure why [NSRunLoop runMode:beforeDate:] is too expensive, - // since it and its cousin [NSRunLoop acceptInputForMode:beforeDate:] are - // designed to be called in a tight loop. Possibly the problem is due to - // combining [NSRunLoop runMode:beforeDate] with [NSApp - // nextEventMatchingMask:...]. - // We special-case timer events (events of type NSPeriodic) to avoid // starving them. Apple's documentation is very scanty, and it's now // more scanty than it used to be. But it appears that [NSRunLoop @@ -600,6 +585,8 @@ currentMode = NSDefaultRunLoopMode; NSEvent* nextEvent = nil; + + mLastNativeEventTime = PR_IntervalNow(); // If we're running modal (or not in a Gecko "main" event loop) we still // need to use nextEventMatchingMask and sendEvent -- otherwise (in @@ -649,26 +636,18 @@ } } while (mRunningEventLoop); - if (eventProcessed && (mHadMoreEventsCount < kHadMoreEventsCountMax)) { - moreEvents = ([NSApp nextEventMatchingMask:NSAnyEventMask - untilDate:nil - inMode:currentMode - dequeue:NO] != nil); - } - - if (moreEvents) { - // Once this reaches kHadMoreEventsCountMax, it will be reset to 0 the - // next time through (whether or not we process any events then). - ++mHadMoreEventsCount; - } else { - mHadMoreEventsCount = 0; - } - mRunningEventLoop = wasRunningEventLoop; NS_OBJC_END_TRY_ABORT_BLOCK; - return moreEvents; + return PR_FALSE; +} + +NS_IMETHODIMP +nsAppShell::GetHasPendingInputEvent(PRBool *aResult) +{ + *aResult = PR_IntervalToMilliseconds(PR_IntervalNow() - mLastNativeEventTime) > 500; + return NS_OK; } // Returns PR_TRUE if Gecko events are currently being processed in its "main" diff --git a/widget/src/cocoa/nsChildView.h b/widget/src/cocoa/nsChildView.h --- a/widget/src/cocoa/nsChildView.h +++ b/widget/src/cocoa/nsChildView.h @@ -424,7 +424,8 @@ NS_IMETHOD BeginSecureKeyboardInput(); NS_IMETHOD EndSecureKeyboardInput(); - + void ResetAppInputPendingState(); + PRBool IsAppInputPending(); void HidePlugin(); protected: diff --git a/widget/src/cocoa/nsChildView.mm b/widget/src/cocoa/nsChildView.mm --- a/widget/src/cocoa/nsChildView.mm +++ b/widget/src/cocoa/nsChildView.mm @@ -80,6 +80,8 @@ #include "gfxQuartzSurface.h" #include + +#include "ApplicationServices/ApplicationServices.h" #undef DEBUG_IME #undef DEBUG_UPDATE @@ -2334,6 +2336,19 @@ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } +static PRUint32 gEventCount; + +void nsChildView::ResetAppInputPendingState() +{ + gEventCount = CGEventSourceCounterForEventType( + kCGEventSourceStateCombinedSessionState, kCGAnyInputEventType); +} + +PRBool nsChildView::IsAppInputPending() +{ + return gEventCount != CGEventSourceCounterForEventType( + kCGEventSourceStateCombinedSessionState, kCGAnyInputEventType); +} #ifdef ACCESSIBILITY void diff --git a/widget/src/gtk2/nsAppShell.cpp b/widget/src/gtk2/nsAppShell.cpp --- a/widget/src/gtk2/nsAppShell.cpp +++ b/widget/src/gtk2/nsAppShell.cpp @@ -41,6 +41,9 @@ #include #include #include +#ifdef MOZ_X11 +#include +#endif #include "nsAppShell.h" #include "nsWindow.h" #include "prlog.h" @@ -143,3 +146,10 @@ { return g_main_context_iteration(NULL, mayWait); } + +NS_IMETHODIMP +nsAppShell::GetHasPendingInputEvent(PRBool *aResult) +{ + *aResult = gdk_events_pending(); + return NS_OK; +} diff --git a/widget/src/gtk2/nsAppShell.h b/widget/src/gtk2/nsAppShell.h --- a/widget/src/gtk2/nsAppShell.h +++ b/widget/src/gtk2/nsAppShell.h @@ -42,6 +42,7 @@ #include #include "nsBaseAppShell.h" #include "nsCOMPtr.h" +#include "prinrval.h" class nsAppShell : public nsBaseAppShell { public: @@ -53,6 +54,8 @@ nsresult Init(); virtual void ScheduleNativeEventCallback(); virtual PRBool ProcessNextNativeEvent(PRBool mayWait); + + NS_IMETHOD GetHasPendingInputEvent(PRBool *aResult); private: virtual ~nsAppShell(); diff --git a/widget/src/xpwidgets/nsBaseAppShell.cpp b/widget/src/xpwidgets/nsBaseAppShell.cpp --- a/widget/src/xpwidgets/nsBaseAppShell.cpp +++ b/widget/src/xpwidgets/nsBaseAppShell.cpp @@ -313,6 +313,13 @@ return NS_OK; } +NS_IMETHODIMP +nsBaseAppShell::GetHasPendingInputEvent(PRBool *aResult) +{ + *aResult = PR_FALSE; + return NS_OK; +} + // Called from the main thread NS_IMETHODIMP nsBaseAppShell::AfterProcessNextEvent(nsIThreadInternal *thr,