Bug 3477743: plugin crash. patch by myself and Johnny Stenback, r+sr=roc
authorsharparrow1@yahoo.com
Mon, 13 Aug 2007 13:47:04 -0700
changeset 4577 fa6a32bb13f5c76487ed6bccc41593475bab3a86
parent 4576 68b712189a94842ed9fab098465d65c8a0f5d1d6
child 4578 8778657c9accb09850191d7868d9eeb4d919db1c
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
bugs3477743
milestone1.9a8pre
Bug 3477743: plugin crash. patch by myself and Johnny Stenback, r+sr=roc
layout/generic/nsObjectFrame.cpp
layout/generic/nsObjectFrame.h
view/public/nsIView.h
view/src/nsView.cpp
widget/src/windows/nsWindow.cpp
--- 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
 //
 //-------------------------------------------------------------------------