Bug 67752. Implement interruptible reflow. r=roc,dbaron

This commit is contained in:
Boris Zbarsky 2009-04-21 19:53:52 -04:00
parent 98015bb24b
commit 05780cc568
37 changed files with 826 additions and 146 deletions

View File

@ -48,8 +48,12 @@ enum mozFlushType {
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___ */

View File

@ -6257,8 +6257,8 @@ nsDocument::FlushPendingNotifications(mozFlushType aType)
// 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);
}

View File

@ -5749,7 +5749,7 @@ nsEventStateManager::FlushPendingEvents(nsPresContext* aPresContext)
NS_PRECONDITION(nsnull != aPresContext, "nsnull ptr");
nsIPresShell *shell = aPresContext->GetPresShell();
if (shell) {
shell->FlushPendingNotifications(Flush_Display);
shell->FlushPendingNotifications(Flush_InterruptibleLayout);
}
}

View File

@ -1308,9 +1308,8 @@ nsGenericHTMLElement::GetFormControlFrameFor(nsIContent* aContent,
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) {
@ -2696,7 +2695,10 @@ 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();

View File

@ -3200,7 +3200,7 @@ HTMLContentSink::FlushPendingNotifications(mozFlushType aType)
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);

View File

@ -1653,7 +1653,7 @@ nsXMLContentSink::FlushPendingNotifications(mozFlushType aType)
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);

View File

@ -101,10 +101,10 @@ class nsPIDOMEventTarget;
typedef short SelectionType;
typedef PRUint32 nsFrameState;
// fa1bf801-9fb6-4d19-8d33-698e9961fc10
// 4fb87dae-8986-429f-b6ba-f040750e3ee8
#define NS_IPRESSHELL_IID \
{ 0xfa1bf801, 0x9fb6, 0x4d19, \
{ 0x8d, 0x33, 0x69, 0x8e, 0x99, 0x61, 0xfc, 0x10 } }
{ 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 @@ typedef PRUint32 nsFrameState;
#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
@ -367,6 +369,19 @@ public:
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;
/**
@ -461,7 +476,7 @@ public:
*/
NS_IMETHOD ScrollContentIntoView(nsIContent* aContent,
PRIntn aVPercent,
PRIntn aHPercent) const = 0;
PRIntn aHPercent) = 0;
/**
* Suppress notification of the frame manager that frames are

View File

@ -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,125 @@ 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;
}

View File

@ -806,6 +806,58 @@ public:
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();
@ -834,6 +886,8 @@ protected:
FlushUserFontSet();
}
PRBool HavePendingInputEvent();
// Can't be inline because we can't include nsStyleSet.h.
PRBool HasCachedStyleData();
@ -921,6 +975,10 @@ protected:
nscoord mBorderWidthTable[3];
PRUint32 mInterruptChecksToSkip;
unsigned mHasPendingInterrupt : 1;
unsigned mInterruptsEnabled : 1;
unsigned mUseDocumentFonts : 1;
unsigned mUseDocumentColors : 1;
unsigned mUnderlineLinks : 1;

View File

@ -821,6 +821,7 @@ public:
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);
@ -844,7 +845,7 @@ public:
NS_IMETHOD ScrollContentIntoView(nsIContent* aContent,
PRIntn aVPercent,
PRIntn aHPercent) const;
PRIntn aHPercent);
NS_IMETHOD SetIgnoreFrameDestruction(PRBool aIgnore);
NS_IMETHOD NotifyDestroyingFrame(nsIFrame* aFrame);
@ -1022,23 +1023,31 @@ public:
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;
@ -1122,11 +1131,6 @@ protected:
// 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
@ -1157,6 +1161,25 @@ protected:
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)
@ -1173,6 +1196,8 @@ protected:
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.
@ -1624,6 +1649,10 @@ PresShell::Init(nsIDocument* aDocument,
result = mStackArena.Init();
NS_ENSURE_SUCCESS(result, result);
if (!mFramesToDirty.Init()) {
return NS_ERROR_OUT_OF_MEMORY;
}
mDocument = aDocument;
NS_ADDREF(mDocument);
mViewManager = aViewManager;
@ -1755,6 +1784,8 @@ PresShell::Destroy()
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.
@ -2700,14 +2731,12 @@ PresShell::ResizeReflow(nscoord aWidth, nscoord aHeight)
// 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);
@ -3417,6 +3446,20 @@ PresShell::FrameNeedsReflow(nsIFrame *aFrame, IntrinsicDirty aIntrinsicDirty,
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)
{
@ -3550,6 +3593,8 @@ PresShell::ClearFrameRefs(nsIFrame* aFrame)
}
}
mFramesToDirty.RemoveEntry(aFrame);
nsWeakFrame* weakFrame = mWeakFrames;
while (weakFrame) {
nsWeakFrame* prev = weakFrame->GetPreviousWeakFrame();
@ -4088,17 +4133,21 @@ static void ScrollViewToShowRect(nsIScrollableView* aScrollingView,
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
@ -4115,11 +4164,26 @@ PresShell::ScrollContentIntoView(nsIContent* aContent,
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
@ -4582,7 +4646,7 @@ PresShell::CancelPostedReflowCallbacks()
}
void
PresShell::HandlePostedReflowCallbacks()
PresShell::HandlePostedReflowCallbacks(PRBool aInterruptible)
{
PRBool shouldFlush = PR_FALSE;
@ -4601,8 +4665,10 @@ PresShell::HandlePostedReflowCallbacks()
}
}
mozFlushType flushType =
aInterruptible ? Flush_InterruptibleLayout : Flush_Layout;
if (shouldFlush)
FlushPendingNotifications(Flush_Layout);
FlushPendingNotifications(flushType);
}
NS_IMETHODIMP
@ -4627,15 +4693,8 @@ PresShell::IsSafeToFlush(PRBool& aIsSafeToFlush)
}
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?");
@ -4704,11 +4763,17 @@ PresShell::DoFlushPendingNotifications(mozFlushType aType,
// 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;
@ -4717,7 +4782,7 @@ PresShell::DoFlushPendingNotifications(mozFlushType aType,
// 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.
@ -6556,7 +6621,7 @@ PresShell::WillPaint()
// 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
@ -6766,7 +6831,17 @@ PresShell::ReflowEvent::Run() {
// 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;
@ -6813,11 +6888,11 @@ PresShell::WillDoReflow()
}
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)
@ -6830,8 +6905,24 @@ PresShell::DidDoReflow()
}
}
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();
@ -6840,9 +6931,13 @@ PresShell::DoReflow(nsIFrame* target)
// 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);
@ -6877,6 +6972,9 @@ PresShell::DoReflow(nsIFrame* target)
reflowState.mComputedBorderPadding.LeftRight(),
"reflow state computed incorrect width");
mPresContext->ReflowStarted(aInterruptible);
mIsReflowing = PR_TRUE;
nsReflowStatus status;
nsHTMLReflowMetrics desiredSize;
target->Reflow(mPresContext, desiredSize, reflowState, status);
@ -6908,6 +7006,38 @@ PresShell::DoReflow(nsIFrame* target)
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
@ -6944,12 +7074,13 @@ PresShell::DoVerifyReflow()
}
#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
@ -6969,7 +7100,6 @@ PresShell::ProcessReflowCommands(PRBool aInterruptible)
{
nsAutoScriptBlocker scriptBlocker;
AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow);
mIsReflowing = PR_TRUE;
do {
// Send an incremental reflow notification to the target frame.
@ -6984,22 +7114,19 @@ PresShell::ProcessReflowCommands(PRBool aInterruptible)
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
@ -7035,7 +7162,7 @@ PresShell::ProcessReflowCommands(PRBool aInterruptible)
UnsuppressAndInvalidate();
}
return NS_OK;
return !interrupted;
}
void

View File

@ -147,9 +147,9 @@ nsAbsoluteContainingBlock::Reflow(nsContainerFrame* aDelegatingFrame,
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,16 @@ nsAbsoluteContainingBlock::Reflow(nsContainerFrame* aDelegatingFrame,
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))
@ -334,11 +343,25 @@ nsAbsoluteContainingBlock::DestroyFrames(nsIFrame* aDelegatingFrame)
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);
}

View File

@ -127,8 +127,13 @@ public:
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
@ -146,6 +151,11 @@ protected:
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

View File

@ -1122,12 +1122,20 @@ nsBlockFrame::Reflow(nsPresContext* aPresContext,
// 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 =
@ -2016,6 +2024,18 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState)
}
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
@ -2037,8 +2057,9 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState)
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 +2068,10 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState)
// 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,14 +2090,18 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState)
// (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
@ -2157,28 +2184,38 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState)
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 (LineHasClear(line.get())) {
foundAnyClears = PR_TRUE;
}
// If this is an inline frame then its time to stop
++line;
aState.AdvanceToNextLine();
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();
}
}
}
@ -2216,6 +2253,58 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState)
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);
nsIFrame* oof = nsPlaceholderFrame::GetRealFrameFor(f);
if (oof != f && oof->GetParent() == this) {
oof->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,
@ -4958,23 +5047,6 @@ nsBlockFrame::RemoveFloat(nsIFrame* aFloat) {
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;

View File

@ -464,6 +464,9 @@ protected:
/** 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
/**

View File

@ -755,6 +755,16 @@ nsColumnSetFrame::ReflowChildren(nsHTMLReflowMetrics& aDesiredSize,
}
}
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();
@ -770,6 +780,14 @@ nsColumnSetFrame::ReflowChildren(nsHTMLReflowMetrics& aDesiredSize,
#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) {
@ -897,7 +915,7 @@ nsColumnSetFrame::Reflow(nsPresContext* aPresContext,
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
@ -910,7 +928,7 @@ nsColumnSetFrame::Reflow(nsPresContext* aPresContext,
// search)
PRBool maybeContinuousBreakingDetected = PR_FALSE;
while (1) {
while (!aPresContext->HasPendingInterrupt()) {
nscoord lastKnownFeasibleHeight = knownFeasibleHeight;
// Record what we learned from the last reflow
@ -1002,7 +1020,7 @@ nsColumnSetFrame::Reflow(nsPresContext* aPresContext,
&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;
@ -1021,6 +1039,13 @@ nsColumnSetFrame::Reflow(nsPresContext* aPresContext,
}
}
}
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);

View File

@ -1926,7 +1926,7 @@ nsGfxScrollFrameInner::AsyncScrollPortEvent::Run()
{
if (mInner) {
mInner->mOuter->PresContext()->GetPresShell()->
FlushPendingNotifications(Flush_Layout);
FlushPendingNotifications(Flush_InterruptibleLayout);
}
return mInner ? mInner->FireScrollPortEvent() : NS_OK;
}

View File

@ -2055,6 +2055,9 @@ nsLineLayout::VerticalAlignFrames(PerSpanData* psd)
#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 {

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -66,6 +66,8 @@ fails == 25888-3r.html 25888-3r-ref.html # bug 25888
== 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

View File

@ -379,6 +379,9 @@ nsSVGForeignObjectFrame::InitialUpdate()
"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),
@ -583,6 +586,9 @@ nsSVGForeignObjectFrame::MaybeReflowFromOuterSVGFrame()
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();
}

View File

@ -100,10 +100,10 @@ typedef nsEventStatus (* EVENT_CALLBACK)(nsGUIEvent *event);
#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
@ -816,6 +816,12 @@ class nsIWidget : public nsISupports {
*/
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.

View File

@ -668,6 +668,10 @@ nsAppShell::ProcessNextNativeEvent(PRBool aMayWait)
NS_OBJC_END_TRY_ABORT_BLOCK;
if (!moreEvents) {
nsChildView::UpdateCurrentInputEventCount();
}
return moreEvents;
}

View File

@ -369,6 +369,8 @@ public:
NS_IMETHOD GetAttention(PRInt32 aCycleCount);
virtual PRBool HasPendingInputEvent();
NS_IMETHOD ActivateNativeMenuItemAt(const nsAString& indexString);
NS_IMETHOD ForceUpdateNativeMenuAt(const nsAString& indexString);
@ -411,6 +413,9 @@ public:
void ResetParent();
static PRBool DoHasPendingInputEvent();
static PRUint32 GetCurrentInputEventCount();
static void UpdateCurrentInputEventCount();
protected:
PRBool ReportDestroyEvent();
@ -456,6 +461,8 @@ protected:
nsPluginPort mPluginPort;
nsIPluginInstanceOwner* mPluginInstanceOwner; // [WEAK]
static PRUint32 sLastInputEventCount;
};
void NS_InstallPluginKeyEventsHandler();

View File

@ -82,6 +82,8 @@
#include <dlfcn.h>
#include <ApplicationServices/ApplicationServices.h>
#undef DEBUG_IME
#undef DEBUG_UPDATE
#undef INVALIDATE_DEBUGGING // flash areas as they are invalidated
@ -149,6 +151,8 @@ 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
@ -2059,6 +2063,54 @@ NS_IMETHODIMP nsChildView::GetAttention(PRInt32 aCycleCount)
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 -

View File

@ -247,6 +247,7 @@ public:
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);

View File

@ -62,6 +62,7 @@
#include "nsMenuUtilsX.h"
#include "nsStyleConsts.h"
#include "nsNativeThemeColors.h"
#include "nsChildView.h"
#include "gfxPlatform.h"
#include "qcms.h"
@ -1321,6 +1322,11 @@ NS_IMETHODIMP nsCocoaWindow::GetAttention(PRInt32 aCycleCount)
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
PRBool
nsCocoaWindow::HasPendingInputEvent()
{
return nsChildView::DoHasPendingInputEvent();
}
NS_IMETHODIMP nsCocoaWindow::SetWindowShadowStyle(PRInt32 aStyle)
{

View File

@ -1911,6 +1911,39 @@ nsWindow::GetAttention(PRInt32 aCycleCount)
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)
{

View File

@ -204,6 +204,9 @@ public:
PRBool aDoCapture,
PRBool aConsumeRollupEvent);
NS_IMETHOD GetAttention(PRInt32 aCycleCount);
virtual PRBool HasPendingInputEvent();
NS_IMETHOD MakeFullScreen(PRBool aFullScreen);
NS_IMETHOD HideWindowChrome(PRBool aShouldHide);

View File

@ -3393,16 +3393,20 @@ NS_METHOD nsWindow::SetIcon(const nsAString& aIconSpec)
NS_IMETHODIMP
nsWindow::GetLastInputEventTime(PRUint32& aTime)
{
ULONG ulStatus = WinQueryQueueStatus(HWND_DESKTOP);
// If there is pending input then return the current time.
if (HasPendingInputEvent()) {
gLastInputEventTime = PR_IntervalToMicroseconds(PR_IntervalNow());
}
// If there is pending input then return the current time.
if (ulStatus & (QS_KEY | QS_MOUSE)) {
gLastInputEventTime = PR_IntervalToMicroseconds(PR_IntervalNow());
}
aTime = gLastInputEventTime;
aTime = gLastInputEventTime;
return NS_OK;
}
return NS_OK;
PRBool
nsWindow::HasPendingInputEvent()
{
return (WinQueryQueueStatus(HWND_DESKTOP) & (QS_KEY | QS_MOUSE)) != 0;
}
// --------------------------------------------------------------------------

View File

@ -161,6 +161,7 @@ class nsWindow : public nsBaseWidget,
NS_IMETHOD CaptureRollupEvents(nsIRollupListener * aListener, PRBool aDoCapture, PRBool aConsumeRollupEvent);
NS_IMETHOD GetLastInputEventTime(PRUint32& aTime);
virtual PRBool HasPendingInputEvent();
// Widget appearance
NS_IMETHOD SetCursor( nsCursor aCursor);

View File

@ -6990,15 +6990,7 @@ void nsWindow::StopFlashing()
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());
}
@ -7007,6 +6999,19 @@ nsWindow::GetLastInputEventTime(PRUint32& aTime)
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

View File

@ -211,6 +211,7 @@ public:
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.

View File

@ -798,6 +798,12 @@ nsBaseWidget::GetLastInputEventTime(PRUint32& aTime) {
return NS_ERROR_NOT_IMPLEMENTED;
}
PRBool
nsBaseWidget::HasPendingInputEvent()
{
return PR_FALSE;
}
NS_IMETHODIMP
nsBaseWidget::SetIcon(const nsAString&)
{

View File

@ -122,6 +122,7 @@ public:
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();