Track event loop nesting level and delay stopping a plugin until it's safe.
b=420886 r+sr=jst a1.9b5=beltzner
--- 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(¤tLevel);
+ 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(¤tLevel);
+ 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
};