Bug 3477743: plugin crash. patch by myself and Johnny Stenback, r+sr=roc
--- a/layout/generic/nsObjectFrame.cpp
+++ b/layout/generic/nsObjectFrame.cpp
@@ -117,16 +117,18 @@
#include "nsIClassInfo.h"
#include "nsObjectFrame.h"
#include "nsIObjectFrame.h"
#include "nsPluginNativeWindow.h"
#include "nsPIPluginHost.h"
#include "nsIPluginDocument.h"
+#include "nsThreadUtils.h"
+
#ifdef MOZ_CAIRO_GFX
#include "gfxContext.h"
#endif
// accessibility support
#ifdef ACCESSIBILITY
#include "nsIAccessibilityService.h"
#endif
@@ -336,16 +338,18 @@ public:
NS_IMETHOD DragDrop(nsIDOMEvent* aMouseEvent);
NS_IMETHOD DragGesture(nsIDOMEvent* aMouseEvent);
NS_IMETHOD Drag(nsIDOMEvent* aMouseEvent);
NS_IMETHOD DragEnd(nsIDOMEvent* aMouseEvent);
nsresult Destroy();
+ void PrepareToStop(PRBool aDelayedStop);
+
//nsIEventListener interface
nsEventStatus ProcessEvent(const nsGUIEvent & anEvent);
#ifdef XP_WIN
void Paint(const nsRect& aDirtyRect, HDC ndc);
#elif defined(XP_MACOSX)
void Paint(const nsRect& aDirtyRect);
#elif defined(MOZ_X11)
@@ -374,30 +378,39 @@ public:
void SetPluginHost(nsIPluginHost* aHost);
#ifdef XP_MACOSX
NPDrawingModel GetDrawingModel();
WindowRef FixUpPluginWindow(PRInt32 inPaintState);
void GUItoMacEvent(const nsGUIEvent& anEvent, EventRecord* origEvent, EventRecord& aMacEvent);
#endif
+ void SetOwner(nsObjectFrame *aOwner)
+ {
+ mOwner = aOwner;
+ }
+
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;
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;
char **mCachedAttrParamNames;
char **mCachedAttrParamValues;
nsPluginDOMContextMenuListener * mCXMenuListener; // pointer to wrapper for nsIDOMContextMenuListener
nsresult DispatchKeyToPlugin(nsIDOMEvent* aKeyEvent);
@@ -511,17 +524,17 @@ nsObjectFrame::Init(nsIContent* aCo
void
nsObjectFrame::Destroy()
{
NS_ASSERTION(!mInstantiating, "about to crash due to bug 136927");
// we need to finish with the plugin before native window is destroyed
// doing this in the destructor is too late.
- StopPlugin();
+ StopPluginInternal(PR_TRUE);
nsObjectFrameSuper::Destroy();
}
nsIAtom*
nsObjectFrame::GetType() const
{
return nsGkAtoms::objectFrame;
@@ -1363,17 +1376,17 @@ nsresult nsObjectFrame::GetPluginInstanc
return mInstanceOwner->GetInstance(aPluginInstance);
}
nsresult
nsObjectFrame::PrepareInstanceOwner()
{
// First, have to stop any possibly running plugins.
- StopPlugin();
+ StopPluginInternal(PR_FALSE);
NS_ASSERTION(!mInstanceOwner, "Must not have an instance owner here");
mInstanceOwner = new nsPluginInstanceOwner();
if (!mInstanceOwner)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(mInstanceOwner);
@@ -1435,80 +1448,143 @@ 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();
}
}
-void
-nsObjectFrame::StopPlugin()
+class nsStopPluginRunnable : public nsRunnable
+{
+public:
+ nsStopPluginRunnable(nsPluginInstanceOwner *aInstanceOwner)
+ : mInstanceOwner(aInstanceOwner)
+ {
+ }
+
+ NS_IMETHOD Run();
+
+private:
+ nsRefPtr<nsPluginInstanceOwner> mInstanceOwner;
+};
+
+static void
+DoStopPlugin(nsPluginInstanceOwner *aInstanceOwner)
{
- if (mInstanceOwner != nsnull) {
- nsCOMPtr<nsIPluginInstance> inst;
- mInstanceOwner->GetInstance(*getter_AddRefs(inst));
- if (inst) {
- nsPluginWindow *win;
- mInstanceOwner->GetWindow(win);
- nsPluginNativeWindow *window = (nsPluginNativeWindow *)win;
- nsCOMPtr<nsIPluginInstance> nullinst;
-
- PRBool doCache = PR_TRUE;
- PRBool doCallSetWindowAfterDestroy = PR_FALSE;
-
- // first, determine if the plugin wants to be cached
- inst->GetValue(nsPluginInstanceVariable_DoCacheBool,
- (void *) &doCache);
- if (!doCache) {
- // then determine if the plugin wants Destroy to be called after
- // Set Window. This is for bug 50547.
- inst->GetValue(nsPluginInstanceVariable_CallSetWindowAfterDestroyBool,
- (void *) &doCallSetWindowAfterDestroy);
- if (doCallSetWindowAfterDestroy) {
- inst->Stop();
- inst->Destroy();
-
- if (window)
- window->CallSetWindow(nullinst);
- else
- inst->SetWindow(nsnull);
- }
- else {
- if (window)
- window->CallSetWindow(nullinst);
- else
- inst->SetWindow(nsnull);
-
- inst->Stop();
- inst->Destroy();
- }
+ nsCOMPtr<nsIPluginInstance> inst;
+ aInstanceOwner->GetInstance(*getter_AddRefs(inst));
+ if (inst) {
+ nsPluginWindow *win;
+ aInstanceOwner->GetWindow(win);
+ nsPluginNativeWindow *window = (nsPluginNativeWindow *)win;
+ nsCOMPtr<nsIPluginInstance> nullinst;
+
+ PRBool doCache = PR_TRUE;
+ PRBool doCallSetWindowAfterDestroy = PR_FALSE;
+
+ // first, determine if the plugin wants to be cached
+ inst->GetValue(nsPluginInstanceVariable_DoCacheBool, (void *)&doCache);
+ if (!doCache) {
+ // then determine if the plugin wants Destroy to be called after
+ // Set Window. This is for bug 50547.
+ inst->GetValue(nsPluginInstanceVariable_CallSetWindowAfterDestroyBool,
+ (void *)&doCallSetWindowAfterDestroy);
+ if (doCallSetWindowAfterDestroy) {
+ inst->Stop();
+ inst->Destroy();
+
+ if (window)
+ window->CallSetWindow(nullinst);
+ else
+ inst->SetWindow(nsnull);
}
else {
if (window)
window->CallSetWindow(nullinst);
else
inst->SetWindow(nsnull);
inst->Stop();
+ inst->Destroy();
}
-
- nsCOMPtr<nsIPluginHost> pluginHost = do_GetService(kCPluginManagerCID);
- if (pluginHost)
- pluginHost->StopPluginInstance(inst);
-
- // the frame is going away along with its widget
- // so tell the window to forget its widget too
- if (window)
- window->SetPluginWidget(nsnull);
+ }
+ else {
+ if (window)
+ window->CallSetWindow(nullinst);
+ else
+ inst->SetWindow(nsnull);
+
+ inst->Stop();
}
- mInstanceOwner->Destroy();
- NS_RELEASE(mInstanceOwner);
+ nsCOMPtr<nsIPluginHost> pluginHost = do_GetService(kCPluginManagerCID);
+ if (pluginHost)
+ pluginHost->StopPluginInstance(inst);
+
+ // the frame is going away along with its widget so tell the
+ // window to forget its widget too
+ if (window)
+ window->SetPluginWidget(nsnull);
}
+
+ aInstanceOwner->Destroy();
+}
+
+NS_IMETHODIMP
+nsStopPluginRunnable::Run()
+{
+ DoStopPlugin(mInstanceOwner);
+
+ return NS_OK;
+}
+
+void
+nsObjectFrame::StopPlugin()
+{
+ StopPluginInternal(PR_FALSE);
+}
+
+void
+nsObjectFrame::StopPluginInternal(PRBool aDelayedStop)
+{
+ if (mInstanceOwner == nsnull) {
+ return;
+ }
+
+ mInstanceOwner->PrepareToStop(aDelayedStop);
+
+#ifdef XP_WIN
+ // We only deal with delayed stopping of plugins on Win32 for now,
+ // as that's the only platform where we need to (AFAIK) and it's
+ // unclear how safe widget parenting is on other platforms.
+ if (aDelayedStop) {
+ // nsStopPluginRunnable will hold a strong reference to
+ // mInstanceOwner, and thus keep it alive as long as it needs it.
+ nsCOMPtr<nsIRunnable> evt = new nsStopPluginRunnable(mInstanceOwner);
+ NS_DispatchToCurrentThread(evt);
+
+ // If we're asked to do a delayed stop it means we're stopping the
+ // plugin because we're destroying the frame. In that case, tell
+ // the view to disown the widget (i.e. leave it up to us to
+ // destroy it).
+ nsIView *view = GetView();
+ if (view) {
+ view->DisownWidget();
+ }
+ } else
+#endif
+ {
+ DoStopPlugin(mInstanceOwner);
+ }
+
+ // Break relationship between frame and plugin instance owner
+ mInstanceOwner->SetOwner(nsnull);
+
+ NS_RELEASE(mInstanceOwner);
}
void
nsObjectFrame::NotifyContentObjectWrapper()
{
nsCOMPtr<nsIDocument> doc = mContent->GetDocument();
if (!doc)
return;
@@ -1650,16 +1726,17 @@ nsPluginInstanceOwner::nsPluginInstanceO
mOwner = nsnull;
mTagText = nsnull;
mContentFocused = PR_FALSE;
mWidgetVisible = PR_TRUE;
mNumCachedAttrs = 0;
mNumCachedParams = 0;
mCachedAttrParamNames = nsnull;
mCachedAttrParamValues = nsnull;
+ mDestroyWidget = PR_FALSE;
}
nsPluginInstanceOwner::~nsPluginInstanceOwner()
{
PRInt32 cnt;
// shut off the timer.
if (mPluginTimer != nsnull) {
@@ -3418,30 +3495,63 @@ nsPluginInstanceOwner::Destroy()
// Unregister drag event listener;
target->RemoveEventListener(NS_LITERAL_STRING("dragdrop"), listener, PR_TRUE);
target->RemoveEventListener(NS_LITERAL_STRING("dragover"), listener, PR_TRUE);
target->RemoveEventListener(NS_LITERAL_STRING("dragexit"), listener, PR_TRUE);
target->RemoveEventListener(NS_LITERAL_STRING("dragenter"), listener, PR_TRUE);
target->RemoveEventListener(NS_LITERAL_STRING("draggesture"), listener, PR_TRUE);
}
+ if (mDestroyWidget && mWidget) {
+ mWidget->Destroy();
+ }
+
+ return NS_OK;
+}
+
+/*
+ * Prepare to stop
+ */
+void
+nsPluginInstanceOwner::PrepareToStop(PRBool aDelayedStop)
+{
+ if (!mWidget) {
+ return;
+ }
+
+#ifdef XP_WIN
+ if (aDelayedStop) {
+ // To delay stopping a plugin we need to reparent the plugin
+ // so that we can safely tear down the
+ // plugin after its frame (and view) is gone.
+
+ // Also hide and disable the widget to avoid it from appearing in
+ // odd places after reparenting it, but before it gets destroyed.
+ mWidget->Show(PR_FALSE);
+ mWidget->Enable(PR_FALSE);
+
+ // Reparent the plugins native window. This relies on the widget
+ // and plugin et al not holding any other references to its
+ // parent.
+ mWidget->SetParent(nsnull);
+
+ mDestroyWidget = PR_TRUE;
+ }
+#endif
+
// Unregister scroll position listener
nsIFrame* parentWithView = mOwner->GetAncestorWithView();
nsIView* curView = parentWithView ? parentWithView->GetView() : nsnull;
while (curView) {
nsIScrollableView* scrollingView = curView->ToScrollableView();
if (scrollingView)
scrollingView->RemoveScrollPositionListener((nsIScrollPositionListener *)this);
curView = curView->GetParent();
}
-
- mOwner = nsnull; // break relationship between frame and plugin instance owner
-
- return NS_OK;
}
// Paints are handled differently, so we just simulate an update event.
#ifdef XP_MACOSX
void nsPluginInstanceOwner::Paint(const nsRect& aDirtyRect)
{
if (!mInstance || !mOwner)
--- a/layout/generic/nsObjectFrame.h
+++ b/layout/generic/nsObjectFrame.h
@@ -108,16 +108,23 @@ public:
virtual void Destroy();
NS_IMETHOD GetPluginInstance(nsIPluginInstance*& aPluginInstance);
virtual nsresult Instantiate(nsIChannel* aChannel, nsIStreamListener** aStreamListener);
virtual nsresult Instantiate(const char* aMimeType, nsIURI* aURI);
virtual void TryNotifyContentObjectWrapper();
virtual void StopPlugin();
+ /*
+ * Stop a plugin instance. If aDelayedStop is true, the plugin will
+ * be stopped at a later point when it's safe to do so (i.e. not
+ * while destroying the frame tree). Delayed stopping is only
+ * implemented on Win32 for now.
+ */
+ void StopPluginInternal(PRBool aDelayedStop);
/* fail on any requests to get a cursor from us because plugins set their own! see bug 118877 */
NS_IMETHOD GetCursor(const nsPoint& aPoint, nsIFrame::Cursor& aCursor)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
// accessibility support
--- a/view/public/nsIView.h
+++ b/view/public/nsIView.h
@@ -294,16 +294,24 @@ public:
*/
nsIWidget* GetWidget() const { return mWindow; }
/**
* Returns PR_TRUE if the view has a widget associated with it.
*/
PRBool HasWidget() const { return mWindow != nsnull; }
+ /**
+ * If called, will make the view disown the widget and leave it up
+ * to other code to destroy it.
+ */
+ void DisownWidget() {
+ mWidgetDisowned = PR_TRUE;
+ }
+
#ifdef DEBUG
/**
* Output debug info to FILE
* @param out output file handle
* @param aIndent indentation depth
* NOTE: virtual so that debugging tools not linked into gklayout can access it
*/
virtual void List(FILE* out, PRInt32 aIndent = 0) const;
@@ -324,15 +332,16 @@ protected:
nsView *mFirstChild;
void *mClientData;
PRInt32 mZIndex;
nsViewVisibility mVis;
nscoord mPosX, mPosY;
nsRect mDimBounds; // relative to parent
float mOpacity;
PRUint32 mVFlags;
+ PRBool mWidgetDisowned;
virtual ~nsIView() {}
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsIView, NS_IVIEW_IID)
#endif
--- a/view/src/nsView.cpp
+++ b/view/src/nsView.cpp
@@ -178,16 +178,17 @@ nsView::nsView(nsViewManager* aViewManag
mVis = aVisibility;
// Views should be transparent by default. Not being transparent is
// a promise that the view will paint all its pixels opaquely. Views
// should make this promise explicitly by calling
// SetViewContentTransparency.
mVFlags = 0;
mViewManager = aViewManager;
mDirtyRegion = nsnull;
+ mWidgetDisowned = PR_FALSE;
}
void nsView::DropMouseGrabbing() {
// check to see if we are grabbing events
if (mViewManager->GetMouseEventGrabber() == this) {
// we are grabbing events. Move the grab to the parent if we can.
PRBool boolResult; //not used
// if GetParent() returns null, then we release the grab, which is the best we can do
@@ -245,17 +246,19 @@ nsView::~nsView()
// Destroy and release the widget
if (mWindow)
{
// Release memory for the view wrapper
ViewWrapper* wrapper = GetWrapperFor(mWindow);
NS_IF_RELEASE(wrapper);
mWindow->SetClientData(nsnull);
- mWindow->Destroy();
+ if (!mWidgetDisowned) {
+ mWindow->Destroy();
+ }
NS_RELEASE(mWindow);
}
delete mDirtyRegion;
}
nsresult nsView::QueryInterface(const nsIID& aIID, void** aInstancePtr)
{
if (nsnull == aInstancePtr) {
--- a/widget/src/windows/nsWindow.cpp
+++ b/widget/src/windows/nsWindow.cpp
@@ -1637,18 +1637,30 @@ NS_IMETHODIMP nsWindow::SetParent(nsIWid
if (newParent && mWnd) {
::SetParent(mWnd, newParent);
}
aNewParent->AddChild(this);
return NS_OK;
}
- NS_WARNING("Null aNewParent passed to SetParent");
- return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+
+ nsIWidget* parent = GetParent();
+
+ if (parent) {
+ parent->RemoveChild(this);
+ }
+
+ if (mWnd) {
+ ::SetParent(mWnd, nsnull);
+ }
+
+ return NS_OK;
}
//-------------------------------------------------------------------------
//
// Get this nsWindow parent
//
//-------------------------------------------------------------------------