Track event loop nesting level and delay stopping a plugin until it's safe. b=420886 r+sr=jst a1.9b5=beltzner
authormats.palmgren@bredband.net
Tue, 25 Mar 2008 09:56:04 -0700
changeset 13537 2d1abe50b8c5049bc4e9c20de7f5467dda258ece
parent 13536 847345d7af8e94ba55636a755f9c43c7d2fbd616
child 13538 3b6a7bbe74577b5525509adfec2bc6200300d3b6
push idunknown
push userunknown
push dateunknown
bugs420886
milestone1.9b5pre
Track event loop nesting level and delay stopping a plugin until it's safe. b=420886 r+sr=jst a1.9b5=beltzner
layout/generic/nsObjectFrame.cpp
widget/public/nsIAppShell.idl
widget/src/xpwidgets/nsBaseAppShell.cpp
widget/src/xpwidgets/nsBaseAppShell.h
--- a/layout/generic/nsObjectFrame.cpp
+++ b/layout/generic/nsObjectFrame.cpp
@@ -23,16 +23,17 @@
  * Contributor(s):
  *   Pierre Phaneuf <pp@ludusdesign.com>
  *   Jacek Piskozub <piskozub@iopan.gda.pl>
  *   Leon Sha <leon.sha@sun.com>
  *   Roland Mainz <roland.mainz@informatik.med.uni-giessen.de>
  *   Robert O'Callahan <roc+moz@cs.cmu.edu>
  *   Christian Biesinger <cbiesinger@web.de>
  *   Josh Aas <josh@mozilla.com>
+ *   Mats Palmgren <mats.palmgren@bredband.net>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either of the GNU General Public License Version 2 or later (the "GPL"),
  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -140,16 +141,17 @@
 #define FORCE_PR_LOG 1 /* Allow logging in the release build */
 #endif /* MOZ_LOGGING */
 #include "prlog.h"
 
 #include <errno.h>
 
 #include "nsContentCID.h"
 static NS_DEFINE_CID(kRangeCID, NS_RANGE_CID);
+static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
 
 #ifdef XP_MACOSX
 #include "gfxQuartzNativeDrawing.h"
 #endif
 
 #ifdef MOZ_X11
 #include <X11/Xlib.h>
 /* X headers suck */
@@ -392,28 +394,48 @@ public:
   void GUItoMacEvent(const nsGUIEvent& anEvent, EventRecord* origEvent, EventRecord& aMacEvent);
 #endif
 
   void SetOwner(nsObjectFrame *aOwner)
   {
     mOwner = aOwner;
   }
 
+  PRUint32 GetLastEventloopNestingLevel() const {
+    return mLastEventloopNestingLevel; 
+  }
+
+  void ConsiderNewEventloopNestingLevel() {
+    nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
+    if (appShell) {
+      PRUint32 currentLevel = 0;
+      appShell->GetEventloopNestingLevel(&currentLevel);
+      if (currentLevel < mLastEventloopNestingLevel) {
+        mLastEventloopNestingLevel = currentLevel;
+      }
+    }
+  }
+
 private:
   void FixUpURLS(const nsString &name, nsAString &value);
 
   nsPluginNativeWindow       *mPluginWindow;
   nsCOMPtr<nsIPluginInstance> mInstance;
   nsObjectFrame              *mOwner;
   nsCOMPtr<nsIContent>        mContent;
   nsCString                   mDocumentBase;
   char                       *mTagText;
   nsCOMPtr<nsIWidget>         mWidget;
   nsCOMPtr<nsITimer>          mPluginTimer;
   nsCOMPtr<nsIPluginHost>     mPluginHost;
+
+  // Initially, the event loop nesting level we were created on, it's updated
+  // if we detect the appshell is on a lower level as long as we're not stopped.
+  // We delay DoStopPlugin() until the appshell reaches this level or lower.
+  PRUint32                    mLastEventloopNestingLevel;
   PRPackedBool                mContentFocused;
   PRPackedBool                mWidgetVisible;    // used on Mac to store our widget's visible state
 
   // If true, destroy the widget on destruction. Used when plugin stop
   // is being delayed to a safer point in time.
   PRPackedBool                mDestroyWidget;
   PRUint16          mNumCachedAttrs;
   PRUint16          mNumCachedParams;
@@ -782,17 +804,16 @@ nsObjectFrame::Reflow(nsPresContext*    
 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();
   }
 
   NS_PRECONDITION(!mInstantiating, "How did that happen?");
   mInstantiating = PR_TRUE;
 
@@ -1516,16 +1537,18 @@ nsObjectFrame::HandleEvent(nsPresContext
                            nsEventStatus*  anEventStatus)
 {
   NS_ENSURE_ARG_POINTER(anEventStatus);
   nsresult rv = NS_OK;
 
   if (!mInstanceOwner)
     return NS_ERROR_NULL_POINTER;
 
+  mInstanceOwner->ConsiderNewEventloopNestingLevel();
+
   if (anEvent->message == NS_PLUGIN_ACTIVATE) {
     nsIContent* content = GetContent();
     if (content) {
       content->SetFocus(aPresContext);
       return rv;
     }
   }
 
@@ -1663,30 +1686,40 @@ nsObjectFrame::TryNotifyContentObjectWra
     // The plugin may have set up new interfaces; we need to mess with our JS
     // wrapper.  Note that we DO NOT want to call this if there is no plugin
     // instance!  That would just reenter Instantiate(), trying to create
     // said plugin instance.
     NotifyContentObjectWrapper();
   }
 }
 
-class nsStopPluginRunnable : public nsRunnable
+class nsStopPluginRunnable : public nsRunnable, public nsITimerCallback
 {
 public:
+  NS_DECL_ISUPPORTS_INHERITED
+
   nsStopPluginRunnable(nsPluginInstanceOwner *aInstanceOwner)
     : mInstanceOwner(aInstanceOwner)
   {
+    NS_ASSERTION(aInstanceOwner, "need an owner");
   }
 
+  // nsRunnable
   NS_IMETHOD Run();
 
+  // nsITimerCallback
+  NS_IMETHOD Notify(nsITimer *timer);
+
 private:  
+  nsCOMPtr<nsITimer> mTimer;
   nsRefPtr<nsPluginInstanceOwner> mInstanceOwner;
 };
 
+NS_IMPL_ISUPPORTS_INHERITED1(nsStopPluginRunnable, nsRunnable, nsITimerCallback)
+
 static void
 DoStopPlugin(nsPluginInstanceOwner *aInstanceOwner, PRBool aDelayedStop)
 {
   nsCOMPtr<nsIPluginInstance> inst;
   aInstanceOwner->GetInstance(*getter_AddRefs(inst));
   if (inst) {
     nsPluginWindow *win;
     aInstanceOwner->GetWindow(win);
@@ -1756,27 +1789,79 @@ DoStopPlugin(nsPluginInstanceOwner *aIns
     if (window)
       window->SetPluginWidget(nsnull);
   }
 
   aInstanceOwner->Destroy();
 }
 
 NS_IMETHODIMP
+nsStopPluginRunnable::Notify(nsITimer *aTimer)
+{
+  return Run();
+}
+
+NS_IMETHODIMP
 nsStopPluginRunnable::Run()
 {
+  // InitWithCallback calls Release before AddRef so we need to hold a
+  // strong ref on 'this' since we fall through to this scope if it fails.
+  nsCOMPtr<nsITimerCallback> kungFuDeathGrip = this;
+  nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
+  if (appShell) {
+    PRUint32 currentLevel = 0;
+    appShell->GetEventloopNestingLevel(&currentLevel);
+    if (currentLevel > mInstanceOwner->GetLastEventloopNestingLevel()) {
+      if (!mTimer)
+        mTimer = do_CreateInstance("@mozilla.org/timer;1");
+      if (mTimer) {
+        nsresult rv = mTimer->InitWithCallback(this, 3000, nsITimer::TYPE_ONE_SHOT);
+        if (NS_SUCCEEDED(rv)) {
+          return rv;
+        }
+      }
+      NS_ERROR("Failed to setup a timer to stop the plugin later (at a safe "
+               "time). Stopping the plugin now, this might crash.");
+    }
+  }
+
+  mTimer = nsnull;
+
   DoStopPlugin(mInstanceOwner, PR_FALSE);
 
   return NS_OK;
 }
 
 void
 nsObjectFrame::StopPlugin()
 {
+#ifdef XP_WIN
+  // XXXjst: ns4xPluginInstance::Destroy() is a no-op, clean
+  // this mess up when there are no other instance types.
+  PRBool delayedStop = PR_TRUE;
+  nsCOMPtr<nsIPluginInstance> inst;
+  if (mInstanceOwner)
+    mInstanceOwner->GetInstance(*getter_AddRefs(inst));
+  if (inst) {
+    PRBool doCache = PR_TRUE;
+    inst->GetValue(nsPluginInstanceVariable_DoCacheBool, (void *)&doCache);
+    if (!doCache) {
+      PRBool doCallSetWindowAfterDestroy = PR_FALSE;
+      inst->GetValue(nsPluginInstanceVariable_CallSetWindowAfterDestroyBool, 
+                     (void *)&doCallSetWindowAfterDestroy);
+      if (doCallSetWindowAfterDestroy) {
+        // Because DoStopPlugin ignores its 'aDelayedStop' arg in this case.
+        delayedStop = PR_FALSE;
+      }
+    }
+  }
+  StopPluginInternal(delayedStop);
+#else
   StopPluginInternal(PR_FALSE);
+#endif
 }
 
 void
 nsObjectFrame::StopPluginInternal(PRBool aDelayedStop)
 {
   if (!mInstanceOwner) {
     return;
   }
@@ -4057,16 +4142,22 @@ void nsPluginInstanceOwner::CancelTimer(
         mPluginTimer = nsnull;
     }
 }
 
 nsresult nsPluginInstanceOwner::Init(nsPresContext* aPresContext,
                                      nsObjectFrame* aFrame,
                                      nsIContent*    aContent)
 {
+  mLastEventloopNestingLevel = 0;
+  nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
+  if (appShell) {
+    appShell->GetEventloopNestingLevel(&mLastEventloopNestingLevel);
+  }
+
   PR_LOG(nsObjectFrameLM, PR_LOG_DEBUG,
          ("nsPluginInstanceOwner::Init() called on %p for frame %p\n", this,
           aFrame));
 
   mOwner = aFrame;
   mContent = aContent;
 
   nsWeakFrame weakFrame(aFrame);
--- 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(95f1f2b3-051b-4603-bb91-7058191dfc1e)]
+[uuid(501403e9-a091-4780-ba55-cfd1e21287a1)]
 interface nsIAppShell : nsISupports
 {
   /**
    * Enter an event loop.  Don't leave until exit() is called.
    */
   void run();
 
   /**
@@ -91,9 +91,14 @@ interface nsIAppShell : nsISupports
   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();
+
+  /**
+   * The current event loop nesting level.
+   */
+  readonly attribute unsigned long eventloopNestingLevel;
 };
--- a/widget/src/xpwidgets/nsBaseAppShell.cpp
+++ b/widget/src/xpwidgets/nsBaseAppShell.cpp
@@ -49,16 +49,17 @@
 NS_IMPL_THREADSAFE_ISUPPORTS3(nsBaseAppShell, nsIAppShell, nsIThreadObserver,
                               nsIObserver)
 
 nsBaseAppShell::nsBaseAppShell()
   : mSuspendNativeCount(0)
   , mBlockedWait(nsnull)
   , mFavorPerf(0)
   , mNativeEventPending(0)
+  , mEventloopNestingLevel(0)
   , mStarvationDelay(0)
   , mSwitchTime(0)
   , mLastNativeEventTime(0)
   , mEventloopNestingState(eEventloopNone)
   , mRunWasCalled(PR_FALSE)
   , mExiting(PR_FALSE)
   , mBlockNativeEvent(PR_FALSE)
 {
@@ -110,25 +111,28 @@ nsBaseAppShell::NativeEventCallback()
       return;
     // We're in a nested native event loop and have some gecko events to
     // process.  While doing that we block processing native events from the
     // appshell - instead, we want to get back to the nested native event
     // loop ASAP (bug 420148).
     mBlockNativeEvent = PR_TRUE;
   }
 
+  ++mEventloopNestingLevel;
   EventloopNestingState prevVal = mEventloopNestingState;
   NS_ProcessPendingEvents(thread, THREAD_EVENT_STARVATION_LIMIT);
   mEventloopNestingState = prevVal;
   mBlockNativeEvent = prevBlockNativeEvent;
 
   // Continue processing pending events later (we don't want to starve the
   // embedders event loop).
   if (NS_HasPendingEvents(thread))
     OnDispatchedEvent(nsnull);
+
+  --mEventloopNestingLevel;
 }
 
 PRBool
 nsBaseAppShell::DoProcessNextNativeEvent(PRBool mayWait)
 {
   // The next native event to be processed may trigger our NativeEventCallback,
   // in which case we do not want it to process any thread events since we'll
   // do that when this function returns.
@@ -138,17 +142,19 @@ nsBaseAppShell::DoProcessNextNativeEvent
   //
   // However, if the next native event is not our NativeEventCallback, but it
   // results in another native event loop, then our NativeEventCallback could
   // fire and it will see mEventloopNestingState as eEventloopOther.
   //
   EventloopNestingState prevVal = mEventloopNestingState;
   mEventloopNestingState = eEventloopXPCOM;
 
+  ++mEventloopNestingLevel;
   PRBool result = ProcessNextNativeEvent(mayWait);
+  --mEventloopNestingLevel;
 
   mEventloopNestingState = prevVal;
   return result;
 }
 
 //-------------------------------------------------------------------------
 // nsIAppShell methods:
 
@@ -184,30 +190,40 @@ nsBaseAppShell::FavorPerformanceHint(PRB
   } else {
     --mFavorPerf;
     mSwitchTime = PR_IntervalNow();
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsBaseAppShell::SuspendNative(void)
+nsBaseAppShell::SuspendNative()
 {
   ++mSuspendNativeCount;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsBaseAppShell::ResumeNative(void)
+nsBaseAppShell::ResumeNative()
 {
   --mSuspendNativeCount;
   NS_ASSERTION(mSuspendNativeCount >= 0, "Unbalanced call to nsBaseAppShell::ResumeNative!");
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsBaseAppShell::GetEventloopNestingLevel(PRUint32* aNestingLevelResult)
+{
+  NS_ENSURE_ARG_POINTER(aNestingLevelResult);
+
+  *aNestingLevelResult = mEventloopNestingLevel;
+
+  return NS_OK;
+}
+
 //-------------------------------------------------------------------------
 // nsIThreadObserver methods:
 
 // Called from any thread
 NS_IMETHODIMP
 nsBaseAppShell::OnDispatchedEvent(nsIThreadInternal *thr)
 {
   if (mBlockNativeEvent)
--- a/widget/src/xpwidgets/nsBaseAppShell.h
+++ b/widget/src/xpwidgets/nsBaseAppShell.h
@@ -104,16 +104,17 @@ private:
    * mBlockedWait points back to a slot that controls the wait loop in
    * an outer OnProcessNextEvent invocation.  Nested calls always set
    * it to PR_FALSE to unblock an outer loop, since all events may
    * have been consumed by the inner event loop(s).
    */
   PRBool *mBlockedWait;
   PRInt32 mFavorPerf;
   PRInt32 mNativeEventPending;
+  PRUint32 mEventloopNestingLevel;
   PRIntervalTime mStarvationDelay;
   PRIntervalTime mSwitchTime;
   PRIntervalTime mLastNativeEventTime;
   enum EventloopNestingState {
     eEventloopNone,  // top level thread execution
     eEventloopXPCOM, // innermost native event loop is ProcessNextNativeEvent
     eEventloopOther  // innermost native event loop is a native library/plugin etc
   };