diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp index bde55ee5857..2fee5e9c18e 100644 --- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -379,6 +379,23 @@ static nsresult DeletingFrameSubtree(nsFrameManager* aFrameManager, nsIFrame* aFrame); +void nsFocusEventSuppressor::Suppress(nsIPresShell *aPresShell) +{ + NS_ASSERTION(aPresShell, "Need non-null nsIPresShell!"); + if (!mViewManager) { + nsFrameManager *frameManager = aPresShell->FrameManager(); + mViewManager = frameManager->GetPresContext()->GetViewManager(); + NS_ASSERTION(mViewManager, "We must have an mViewManager here"); + } + mViewManager->SuppressFocusEvents(); +} + +void nsFocusEventSuppressor::Unsuppress() +{ + NS_ASSERTION(mViewManager, "We must have an mViewManager here"); + mViewManager->UnsuppressFocusEvents(); +} + #ifdef MOZ_SVG static nsIFrame * @@ -10233,6 +10250,12 @@ nsCSSFrameConstructor::AttributeChanged(nsIContent* aContent, return result; } +void +nsCSSFrameConstructor::BeginUpdate() { + mFocusSuppressor.Suppress(mPresShell); + ++mUpdateCount; +} + void nsCSSFrameConstructor::EndUpdate() { @@ -10243,7 +10266,7 @@ nsCSSFrameConstructor::EndUpdate() RecalcQuotesAndCounters(); NS_ASSERTION(mUpdateCount == 1, "Odd update count"); } - + mFocusSuppressor.Unsuppress(); --mUpdateCount; } diff --git a/layout/base/nsCSSFrameConstructor.h b/layout/base/nsCSSFrameConstructor.h index 0a025e2bf75..1f8e874d76d 100644 --- a/layout/base/nsCSSFrameConstructor.h +++ b/layout/base/nsCSSFrameConstructor.h @@ -52,6 +52,7 @@ #include "nsHashKeys.h" #include "nsThreadUtils.h" #include "nsPageContentFrame.h" +#include "nsIViewManager.h" class nsIDocument; struct nsFrameItems; @@ -73,6 +74,19 @@ struct nsFindFrameHint nsFindFrameHint() : mPrimaryFrameForPrevSibling(nsnull) { } }; +// Class which makes an nsIPresShell's ViewManager supress +// focus/blur events. This prevents the frame tree from being changed +// by focus handlers etc while *we* are trying to change it. +// Fix for bug 399852. +class nsFocusEventSuppressor +{ +public: + void Suppress(nsIPresShell *aPresShell); + void Unsuppress(); +private: + nsCOMPtr mViewManager; +}; + typedef void (PR_CALLBACK nsLazyFrameConstructionCallback) (nsIContent* aContent, nsIFrame* aFrame, void* aArg); @@ -148,7 +162,7 @@ public: PRInt32 aModType, PRUint32 aStateMask); - void BeginUpdate() { ++mUpdateCount; } + void BeginUpdate(); void EndUpdate(); void RecalcQuotesAndCounters(); @@ -162,6 +176,9 @@ public: nsresult ProcessRestyledFrames(nsStyleChangeList& aRestyleArray); private: + + nsFocusEventSuppressor mFocusSuppressor; + // Note: It's the caller's responsibility to make sure to wrap a // ProcessOneRestyle call in a view update batch. // This function does not call ProcessAttachedQueue() on the binding manager. diff --git a/view/public/nsIViewManager.h b/view/public/nsIViewManager.h index 14f5d65d508..16eae946f60 100644 --- a/view/public/nsIViewManager.h +++ b/view/public/nsIViewManager.h @@ -481,6 +481,31 @@ public: * (aFromScroll is false) or scrolled (aFromScroll is true). */ NS_IMETHOD SynthesizeMouseMove(PRBool aFromScroll)=0; + + /** + * Enables focus/blur event suppression. This stops focus/blur + * events from reaching the widgets. This should be enabled + * when we're messing with the frame tree, so focus/blur handlers + * don't mess with stuff while we are. See Bug 399852. + */ + virtual void SuppressFocusEvents()=0; + + /** + * Disables focus/blur event suppression. This "reboots" the focus + * by sending a blur to what was focused before suppression began, + * and by sending a focus event to what should be currently focused. + * Note this can run arbitrary code, and could even destroy the view + * manager. The suppression should be enabled when we're messing with + * the frame tree, so focus/blur handlers don't mess with stuff while + * we are. See Bug 399852. + */ + virtual void UnsuppressFocusEvents()=0; + + /** + * Returns true when focus suppression is on. + */ + virtual PRBool IsFocusSuppressed()=0; + }; NS_DEFINE_STATIC_IID_ACCESSOR(nsIViewManager, NS_IVIEWMANAGER_IID) diff --git a/view/src/nsView.cpp b/view/src/nsView.cpp index f2bcbd31ab4..3311a365583 100644 --- a/view/src/nsView.cpp +++ b/view/src/nsView.cpp @@ -200,6 +200,13 @@ nsView::~nsView() { MOZ_COUNT_DTOR(nsView); + if (this == nsViewManager::GetViewFocusedBeforeSuppression()) { + nsViewManager::SetViewFocusedBeforeSuppression(nsnull); + } + if (this == nsViewManager::GetCurrentlyFocusedView()) { + nsViewManager::SetCurrentlyFocusedView(nsnull); + } + while (GetFirstChild()) { nsView* child = GetFirstChild(); diff --git a/view/src/nsViewManager.cpp b/view/src/nsViewManager.cpp index 8746a94d718..56d46fc0269 100644 --- a/view/src/nsViewManager.cpp +++ b/view/src/nsViewManager.cpp @@ -935,6 +935,59 @@ void nsViewManager::UpdateViews(nsView *aView, PRUint32 aUpdateFlags) } } +nsView *nsViewManager::sCurrentlyFocusView = nsnull; +nsView *nsViewManager::sViewFocusedBeforeSuppression = nsnull; +PRInt32 nsViewManager::sSuppressCount = 0; + +void nsViewManager::SuppressFocusEvents() +{ + sSuppressCount++; + if (sSuppressCount == 1) { + // We're turning on focus/blur suppression, remember what had + // the focus. + SetViewFocusedBeforeSuppression(GetCurrentlyFocusedView()); + } +} + +void nsViewManager::UnsuppressFocusEvents() +{ + sSuppressCount--; + if (sSuppressCount > 0 || + GetCurrentlyFocusedView() == GetViewFocusedBeforeSuppression()) + return; + + // We're turning off suppression, synthesize LOSTFOCUS/GOTFOCUS. + nsIWidget *widget = nsnull; + nsEventStatus status; + + // Backup what is focused before we send the blur. If the + // blur causes a focus change, keep that new focus change, + // don't overwrite with the old "currently focused view". + nsIView *currentFocusBeforeBlur = GetCurrentlyFocusedView(); + + // Send NS_LOSTFOCUS to widget that was focused before + // focus/blur suppression. + if (GetViewFocusedBeforeSuppression()) { + widget = GetViewFocusedBeforeSuppression()->GetWidget(); + if (widget) { + nsGUIEvent event(PR_TRUE, NS_LOSTFOCUS, widget); + widget->DispatchEvent(&event, status); + } + } + + // Send NS_GOTFOCUS to the widget that we think should be focused. + if (GetCurrentlyFocusedView() && + currentFocusBeforeBlur == GetCurrentlyFocusedView()) + { + widget = GetCurrentlyFocusedView()->GetWidget(); + if (widget) { + nsGUIEvent event(PR_TRUE, NS_GOTFOCUS, widget); + widget->DispatchEvent(&event, status); + } + } + +} + NS_IMETHODIMP nsViewManager::DispatchEvent(nsGUIEvent *aEvent, nsEventStatus *aStatus) { *aStatus = nsEventStatus_eIgnore; @@ -1138,6 +1191,13 @@ NS_IMETHODIMP nsViewManager::DispatchEvent(nsGUIEvent *aEvent, nsEventStatus *aS default: { + if (aEvent->message == NS_GOTFOCUS) { + SetCurrentlyFocusedView(nsView::GetViewFor(aEvent->widget)); + } + if ((aEvent->message == NS_GOTFOCUS || aEvent->message == NS_LOSTFOCUS) && + nsViewManager::IsFocusSuppressed()) + break; + if ((NS_IS_MOUSE_EVENT(aEvent) && // Ignore moves that we synthesize. static_cast(aEvent)->reason == diff --git a/view/src/nsViewManager.h b/view/src/nsViewManager.h index d0c69b50584..eae248ef08d 100644 --- a/view/src/nsViewManager.h +++ b/view/src/nsViewManager.h @@ -201,10 +201,43 @@ public: /* Update the cached RootViewManager pointer on this view manager. */ void InvalidateHierarchy(); + virtual void SuppressFocusEvents(); + virtual void UnsuppressFocusEvents(); + + virtual PRBool IsFocusSuppressed() + { + return sSuppressCount > 0; + } + + static void SetCurrentlyFocusedView(nsView *aView) + { + sCurrentlyFocusView = aView; + } + + static nsView* GetCurrentlyFocusedView() + { + return sCurrentlyFocusView; + } + + static void SetViewFocusedBeforeSuppression(nsView *aView) + { + sViewFocusedBeforeSuppression = aView; + } + + static nsView* GetViewFocusedBeforeSuppression() + { + return sViewFocusedBeforeSuppression; + } + protected: virtual ~nsViewManager(); private: + + static nsView *sCurrentlyFocusView; + static nsView *sViewFocusedBeforeSuppression; + static PRInt32 sSuppressCount; + void FlushPendingInvalidates(); void ProcessPendingUpdates(nsView *aView, PRBool aDoInvalidate); void ReparentChildWidgets(nsIView* aView, nsIWidget *aNewWidget);