diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp index 20177d7fb55..37f795cfaea 100644 --- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -66,6 +66,7 @@ #include "nsIPresShell.h" #include "nsStyleSet.h" #include "nsIViewManager.h" +#include "nsViewManager.h" #include "nsIEventStateManager.h" #include "nsIScrollableView.h" #include "nsStyleConsts.h" @@ -379,6 +380,26 @@ static nsresult DeletingFrameSubtree(nsFrameManager* aFrameManager, nsIFrame* aFrame); +void nsFocusEventSuppressor::Suppress(nsIPresShell *aPresShell) +{ + NS_ASSERTION(aPresShell, "Need non-null nsIPresShell!"); + NS_ASSERTION(!mViewManager, "Suppress before a pending UnSuppress()"); + nsFrameManager *frameManager = aPresShell->FrameManager(); + mViewManager = frameManager->GetPresContext()->GetViewManager(); + if (mViewManager) { + mOldSuppressState = mViewManager->GetSuppressFocusEvents(); + mViewManager->SetSuppressFocusEvents(PR_TRUE); + } +} + +void nsFocusEventSuppressor::Unsuppress() +{ + if (mViewManager) { + mViewManager->SetSuppressFocusEvents(mOldSuppressState); + mViewManager = nsnull; + } +} + #ifdef MOZ_SVG static nsIFrame * @@ -10237,6 +10258,14 @@ nsCSSFrameConstructor::AttributeChanged(nsIContent* aContent, return result; } +void +nsCSSFrameConstructor::BeginUpdate() { + if (!mUpdateCount) { + mFocusSuppressor.Suppress(mPresShell); + } + ++mUpdateCount; +} + void nsCSSFrameConstructor::EndUpdate() { @@ -10246,6 +10275,8 @@ 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 f26fed5c9db..08bb60d68f9 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,20 @@ 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; + PRBool mOldSuppressState; +}; + typedef void (PR_CALLBACK nsLazyFrameConstructionCallback) (nsIContent* aContent, nsIFrame* aFrame, void* aArg); @@ -148,7 +163,7 @@ public: PRInt32 aModType, PRUint32 aStateMask); - void BeginUpdate() { ++mUpdateCount; } + void BeginUpdate(); void EndUpdate(); void RecalcQuotesAndCounters(); @@ -162,6 +177,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 b1a227ec4f3..8bdf3fcf689 100644 --- a/view/public/nsIViewManager.h +++ b/view/public/nsIViewManager.h @@ -440,6 +440,18 @@ public: * (aFromScroll is false) or scrolled (aFromScroll is true). */ NS_IMETHOD SynthesizeMouseMove(PRBool aFromScroll)=0; + + /** + * Toggles global suppression of focus/blur events. When suppression + * is on, focus/blur events will not be sent to their target widgets/views. + * Note that when called with aSuppress as false, blur/focus events are + * fired to reset the focus. This can run arbitrary code, and could + * even destroy the view manager. + */ + virtual void SetSuppressFocusEvents(PRBool aSuppress)=0; + + virtual PRBool GetSuppressFocusEvents()=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 f9fa1e59df0..d0b754f569f 100644 --- a/view/src/nsViewManager.cpp +++ b/view/src/nsViewManager.cpp @@ -935,6 +935,63 @@ void nsViewManager::UpdateViews(nsView *aView, PRUint32 aUpdateFlags) } } +PRBool nsViewManager::sSuppressFocusEvents = PR_FALSE; +nsView *nsViewManager::sCurrentlyFocusView = nsnull; +nsView *nsViewManager::sViewFocusedBeforeSuppression = nsnull; + +// Enables/disables focus/blur event suppression. When suppression +// is disabled, we "reboot" 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. The suppression should be enabled +// when we're messing with the frame tree, so focus/blur handlers +// don't mess with stuff while we're trying too. See Bug 399852. +void nsViewManager::SetSuppressFocusEvents(PRBool aSuppress) +{ + if (sSuppressFocusEvents && !aSuppress) { + // We're turning off suppression, synthesize LOSTFOCUS/GOTFOCUS. + if (GetCurrentlyFocusedView() != GetViewFocusedBeforeSuppression()) { + + // Turn off suppresion before we send blur/focus events. + sSuppressFocusEvents = aSuppress; + + 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); + } + } + } + + } else if (!sSuppressFocusEvents && aSuppress) { + // We're turning on focus/blur suppression, remember what had + // the focus. + SetViewFocusedBeforeSuppression(GetCurrentlyFocusedView()); + sSuppressFocusEvents = aSuppress; + } +} + NS_IMETHODIMP nsViewManager::DispatchEvent(nsGUIEvent *aEvent, nsEventStatus *aStatus) { *aStatus = nsEventStatus_eIgnore; @@ -1138,6 +1195,12 @@ 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::GetSuppressFocusEvents()) + 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 cca9848a145..f1970245caa 100644 --- a/view/src/nsViewManager.h +++ b/view/src/nsViewManager.h @@ -201,10 +201,42 @@ public: /* Update the cached RootViewManager pointer on this view manager. */ void InvalidateHierarchy(); + virtual void SetSuppressFocusEvents(PRBool aSuppress); + + virtual PRBool GetSuppressFocusEvents() + { + return sSuppressFocusEvents; + } + + 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 PRBool sSuppressFocusEvents; + void FlushPendingInvalidates(); void ProcessPendingUpdates(nsView *aView, PRBool aDoInvalidate); void ReparentChildWidgets(nsIView* aView, nsIWidget *aNewWidget);