fix crash, suspend native events when instantiating plugins. patch by Steven Michaud. b=345627 r=josh sr=roc
authorjoshmoz@gmail.com
Thu, 22 Mar 2007 16:04:51 -0700
changeset 4 e943454a2e49b2860353c9449359c1822cb14827
parent 3 a00ac31e8ae4fe6cdf5f40a007c1ab36ae01ffae
child 5 331cb67f2a3cb141465e0da88f8cd1ef36e85ffc
push idunknown
push userunknown
push dateunknown
reviewersjosh, roc
bugs345627
milestone1.9a3pre
fix crash, suspend native events when instantiating plugins. patch by Steven Michaud. b=345627 r=josh sr=roc
layout/generic/nsObjectFrame.cpp
widget/public/nsIAppShell.idl
widget/src/cocoa/nsAppShell.h
widget/src/cocoa/nsAppShell.mm
widget/src/mac/nsAppShell.cpp
widget/src/mac/nsAppShell.h
widget/src/xpwidgets/nsBaseAppShell.cpp
widget/src/xpwidgets/nsBaseAppShell.h
--- a/layout/generic/nsObjectFrame.cpp
+++ b/layout/generic/nsObjectFrame.cpp
@@ -53,16 +53,17 @@
 #include "nsIViewManager.h"
 #include "nsIDOMKeyListener.h"
 #include "nsIPluginHost.h"
 #include "nsplugin.h"
 #include "nsString.h"
 #include "nsReadableUtils.h"
 #include "prmem.h"
 #include "nsGkAtoms.h"
+#include "nsIAppShell.h"
 #include "nsIDocument.h"
 #include "nsINodeInfo.h"
 #include "nsIURL.h"
 #include "nsNetUtil.h"
 #include "nsIPluginInstanceOwner.h"
 #include "plstr.h"
 #include "nsILinkHandler.h"
 #ifdef OJI
@@ -714,16 +715,24 @@ nsObjectFrame::Reflow(nsPresContext*    
   return NS_OK;
 }
 
 nsresult
 nsObjectFrame::InstantiatePlugin(nsIPluginHost* aPluginHost, 
                                  const char* aMimeType,
                                  nsIURI* aURI)
 {
+  // If you add early return(s), be sure to balance this call to
+  // appShell->SuspendNative() with additional call(s) to
+  // appShell->ReturnNative().
+  static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
+  nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
+  if (appShell) {
+    appShell->SuspendNative();
+  }
 
 #ifdef DEBUG
   mInstantiating = PR_TRUE;
 #endif
 
   NS_ASSERTION(mContent, "We should have a content node.");
 
   nsIDocument* doc = mContent->GetOwnerDoc();
@@ -740,16 +749,20 @@ nsObjectFrame::InstantiatePlugin(nsIPlug
   } else {   /* embedded mode */
     rv = aPluginHost->InstantiateEmbeddedPlugin(aMimeType, aURI,
                                                 mInstanceOwner);
   }
 #ifdef DEBUG
   mInstantiating = PR_FALSE;
 #endif
 
+  if (appShell) {
+    appShell->ResumeNative();
+  }
+
   // XXX having to do this sucks. it'd be better to move the code from DidReflow
   // to FixupWindow or something.
   AddStateBits(NS_FRAME_IS_DIRTY);
   GetPresContext()->GetPresShell()->
     FrameNeedsReflow(this, nsIPresShell::eStyleChange);
   return rv;
 }
 
--- a/widget/public/nsIAppShell.idl
+++ b/widget/public/nsIAppShell.idl
@@ -38,17 +38,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 /**
  * Interface for the native event system layer.  This interface is designed
  * to be used on the main application thread only.
  */
-[uuid(3785230a-da91-4eaa-8a25-56acc7ff35d0)]
+[uuid(95f1f2b3-051b-4603-bb91-7058191dfc1e)]
 interface nsIAppShell : nsISupports
 {
   /**
    * Enter an event loop.  Don't leave until exit() is called.
    */
   void run();
 
   /**
@@ -68,9 +68,32 @@ interface nsIAppShell : nsISupports
    * starvation.
    *
    * The starvationDelay arg is only used when favorPerfOverStarvation is
    * PR_FALSE. It is the amount of time in milliseconds to wait before the
    * PR_FALSE actually takes effect.
    */
   void favorPerformanceHint(in boolean favorPerfOverStarvation,
                             in unsigned long starvationDelay);
+
+  /**
+   * Suspends the use of additional platform-specific methods (besides the
+   * nsIAppShell->run() event loop) to run Gecko events on the main
+   * application thread.  Under some circumstances these "additional methods"
+   * can cause Gecko event handlers to be re-entered, sometimes leading to
+   * hangs and crashes.  Calls to suspendNative() and resumeNative() may be
+   * nested.  On some platforms (those that don't use any "additional
+   * methods") this will be a no-op.  Does not (in itself) stop Gecko events
+   * from being processed on the main application thread.  But if the
+   * nsIAppShell->run() event loop is blocked when this call is made, Gecko
+   * events will stop being processed until resumeNative() is called (even
+   * if a plugin or library is temporarily processing events on a nested
+   * event loop).
+   */
+  void suspendNative();
+
+  /**
+   * Resumes the use of additional platform-specific methods to run Gecko
+   * events on the main application thread.  Calls to suspendNative() and
+   * resumeNative() may be nested.  On some platforms this will be a no-op.
+   */
+  void resumeNative();
 };
--- a/widget/src/cocoa/nsAppShell.h
+++ b/widget/src/cocoa/nsAppShell.h
@@ -45,16 +45,18 @@
 
 #include "nsBaseAppShell.h"
 
 @class AppShellDelegate;
 
 class nsAppShell : public nsBaseAppShell
 {
 public:
+  NS_IMETHODIMP ResumeNative(void);
+	
   nsAppShell();
 
   nsresult Init();
 
   NS_IMETHOD Run(void);
   NS_IMETHOD OnProcessNextEvent(nsIThreadInternal *aThread, PRBool aMayWait,
                                 PRUint32 aRecursionDepth);
   NS_IMETHOD AfterProcessNextEvent(nsIThreadInternal *aThread,
--- a/widget/src/cocoa/nsAppShell.mm
+++ b/widget/src/cocoa/nsAppShell.mm
@@ -65,16 +65,25 @@
 - (void)handlePortMessage:(NSPortMessage*)aPortMessage;
 - (void)runAppShell;
 - (nsresult)rvFromRun;
 - (void)applicationWillTerminate:(NSNotification*)aNotification;
 @end
 
 // nsAppShell implementation
 
+NS_IMETHODIMP
+nsAppShell::ResumeNative(void)
+{
+  nsresult retval = nsBaseAppShell::ResumeNative();
+  if (NS_SUCCEEDED(retval) && (mSuspendNativeCount == 0))
+    ScheduleNativeEventCallback();
+  return retval;
+}
+
 nsAppShell::nsAppShell()
 : mAutoreleasePools(nsnull)
 , mPort(nil)
 , mDelegate(nil)
 , mRunningEventLoop(PR_FALSE)
 , mTerminated(PR_FALSE)
 {
   // mMainPool sits low on the autorelease pool stack to serve as a catch-all
@@ -190,18 +199,19 @@ nsAppShell::ProcessGeckoEvents()
                                     windowNumber:-1
                                          context:NULL
                                          subtype:0
                                            data1:0
                                            data2:0]
              atStart:NO];
   }
 
-  NativeEventCallback();
-  
+  if (mSuspendNativeCount <= 0)
+    NativeEventCallback();
+
   [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
                                       location:NSMakePoint(0,0)
                                  modifierFlags:0
                                      timestamp:0
                                   windowNumber:-1
                                        context:NULL
                                        subtype:0
                                          data1:0
--- a/widget/src/mac/nsAppShell.cpp
+++ b/widget/src/mac/nsAppShell.cpp
@@ -51,16 +51,25 @@
 
 enum {
   kEventClassMoz = 'MOZZ',
   kEventMozNull  = 0,
 };
 
 // nsAppShell implementation
 
+NS_IMETHODIMP
+nsAppShell::ResumeNative(void)
+{
+  nsresult retval = nsBaseAppShell::ResumeNative();
+  if (NS_SUCCEEDED(retval) && (mSuspendNativeCount == 0))
+    ScheduleNativeEventCallback();
+  return retval;
+}
+
 nsAppShell::nsAppShell()
 : mCFRunLoop(NULL)
 , mCFRunLoopSource(NULL)
 , mRunningEventLoop(PR_FALSE)
 {
 }
 
 nsAppShell::~nsAppShell()
@@ -195,12 +204,13 @@ nsAppShell::ProcessGeckoEvents(void* aIn
                                  kEventAttributeNone, &bogusEvent);
     if (err == noErr) {
       ::PostEventToQueue(::GetMainEventQueue(), bogusEvent,
                          kEventPriorityStandard);
       ::ReleaseEvent(bogusEvent);
     }
   }
 
-  self->NativeEventCallback();
+  if (self->mSuspendNativeCount <= 0)
+    self->NativeEventCallback();
 
   NS_RELEASE(self);
 }
--- a/widget/src/mac/nsAppShell.h
+++ b/widget/src/mac/nsAppShell.h
@@ -52,16 +52,18 @@
 #include "nsAutoPtr.h"
 
 class nsIToolkit;
 class nsMacMessagePump;
 
 class nsAppShell : public nsBaseAppShell
 {
 public:
+  NS_IMETHODIMP ResumeNative(void);
+
   nsAppShell();
 
   nsresult Init();
 
 protected:
   virtual ~nsAppShell();
 
   virtual void ScheduleNativeEventCallback();
--- a/widget/src/xpwidgets/nsBaseAppShell.cpp
+++ b/widget/src/xpwidgets/nsBaseAppShell.cpp
@@ -44,17 +44,18 @@
 // events (if not in performance mode), which can result in suppressing the
 // next thread event for at most this many ticks:
 #define THREAD_EVENT_STARVATION_LIMIT PR_MillisecondsToInterval(20)
 
 NS_IMPL_THREADSAFE_ISUPPORTS3(nsBaseAppShell, nsIAppShell, nsIThreadObserver,
                               nsIObserver)
 
 nsBaseAppShell::nsBaseAppShell()
-  : mFavorPerf(0)
+  : mSuspendNativeCount(0)
+  , mFavorPerf(0)
   , mNativeEventPending(PR_FALSE)
   , mStarvationDelay(0)
   , mSwitchTime(0)
   , mLastNativeEventTime(0)
   , mRunWasCalled(PR_FALSE)
   , mExiting(PR_FALSE)
   , mProcessingNextNativeEvent(PR_FALSE)
 {
@@ -172,16 +173,31 @@ nsBaseAppShell::FavorPerformanceHint(PRB
     ++mFavorPerf;
   } else {
     --mFavorPerf;
     mSwitchTime = PR_IntervalNow();
   }
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsBaseAppShell::SuspendNative(void)
+{
+  ++mSuspendNativeCount;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseAppShell::ResumeNative(void)
+{
+  --mSuspendNativeCount;
+  NS_ASSERTION(mSuspendNativeCount >= 0, "Unbalanced call to nsBaseAppShell::ResumeNative!");
+  return NS_OK;
+}
+
 //-------------------------------------------------------------------------
 // nsIThreadObserver methods:
 
 // Called from any thread
 NS_IMETHODIMP
 nsBaseAppShell::OnDispatchedEvent(nsIThreadInternal *thr)
 {
   PRInt32 lastVal = PR_AtomicSet(&mNativeEventPending, 1);
--- a/widget/src/xpwidgets/nsBaseAppShell.h
+++ b/widget/src/xpwidgets/nsBaseAppShell.h
@@ -89,16 +89,18 @@ protected:
    *   If "true", then this method may wait if necessary for the next available
    *   native event.  DispatchNativeEvent may be called to unblock a call to
    *   ProcessNextNativeEvent that is waiting.
    * @return
    *   This method returns "true" if a native event was processed.
    */
   virtual PRBool ProcessNextNativeEvent(PRBool mayWait) = 0;
 
+  PRInt32 mSuspendNativeCount;
+
 private:
   PRBool DoProcessNextNativeEvent(PRBool mayWait);
 
   nsCOMPtr<nsIRunnable> mDummyEvent;
   PRInt32 mFavorPerf;
   PRInt32 mNativeEventPending;
   PRIntervalTime mStarvationDelay;
   PRIntervalTime mSwitchTime;