Bug 784131 - Kill plugins that become unrendered (e.g. display:none). r=josh
authorJohn Schoenick <jschoenick@mozilla.com>
Thu, 06 Sep 2012 17:16:02 -0700
changeset 125699 0d40b8591124
parent 125698 da937d761b89
child 125700 d75dbe2a4dd7
push id24461
push useremorley@mozilla.com
push date2013-03-21 11:51 +0000
treeherdermozilla-central@a73a2b5c423b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjosh
bugs784131
milestone22.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 784131 - Kill plugins that become unrendered (e.g. display:none). r=josh
content/base/src/nsObjectLoadingContent.cpp
content/base/src/nsObjectLoadingContent.h
layout/generic/nsObjectFrame.cpp
--- a/content/base/src/nsObjectLoadingContent.cpp
+++ b/content/base/src/nsObjectLoadingContent.cpp
@@ -133,44 +133,61 @@ nsAsyncInstantiateEvent::Run()
   if (objLC->mPendingInstantiateEvent != this) {
     return NS_OK;
   }
   objLC->mPendingInstantiateEvent = nullptr;
 
   return objLC->SyncStartPluginInstance();
 }
 
-// Checks to see if the content for a plugin instance has a parent.
-// The plugin instance is stopped if there is no parent.
-class InDocCheckEvent : public nsRunnable {
+// Checks to see if the content for a plugin instance should be unloaded
+// (outside an active document) or stopped (in a document but unrendered). This
+// is used to allow scripts to move a plugin around the document hierarchy
+// without re-instantiating it.
+class CheckPluginStopEvent : public nsRunnable {
 public:
-  InDocCheckEvent(nsObjectLoadingContent *aContent)
+  CheckPluginStopEvent(nsObjectLoadingContent *aContent)
   : mContent(aContent) {}
 
-  ~InDocCheckEvent() {}
+  ~CheckPluginStopEvent() {}
 
   NS_IMETHOD Run();
 
 private:
   nsCOMPtr<nsIObjectLoadingContent> mContent;
 };
 
 NS_IMETHODIMP
-InDocCheckEvent::Run()
+CheckPluginStopEvent::Run()
 {
   nsObjectLoadingContent *objLC =
     static_cast<nsObjectLoadingContent *>(mContent.get());
 
+  // If objLC is no longer tracking this event, we've been canceled or
+  // superceded
+  if (objLC->mPendingCheckPluginStopEvent != this) {
+    return NS_OK;
+  }
+  objLC->mPendingCheckPluginStopEvent = nullptr;
+
   nsCOMPtr<nsIContent> content =
     do_QueryInterface(static_cast<nsIImageLoadingContent *>(objLC));
-
   if (!InActiveDocument(content)) {
-    nsObjectLoadingContent *objLC =
-      static_cast<nsObjectLoadingContent *>(mContent.get());
+    // Unload the object entirely
+    LOG(("OBJLC [%p]: Unloading plugin outside of document", this));
     objLC->UnloadObject();
+    return NS_OK;
+  }
+
+  if (!content->GetPrimaryFrame()) {
+    // Still no frame, suspend plugin. HasNewFrame will restart us when we
+    // become rendered again
+    LOG(("OBJLC [%p]: Stopping plugin that lost frame", this));
+    // Okay to leave loaded as a plugin, but stop the unrendered instance
+    objLC->StopPluginInstance();
   }
   return NS_OK;
 }
 
 /**
  * Helper task for firing simple events
  */
 class nsSimplePluginEvent : public nsRunnable {
@@ -639,49 +656,48 @@ nsObjectLoadingContent::UnbindFromTree(b
   nsIDocument* ownerDoc = thisContent->OwnerDoc();
   ownerDoc->RemovePlugin(this);
 
   if (mType == eType_Plugin && mInstanceOwner) {
     // we'll let the plugin continue to run at least until we get back to
     // the event loop. If we get back to the event loop and the node
     // has still not been added back to the document then we tear down the
     // plugin
-    nsCOMPtr<nsIRunnable> event = new InDocCheckEvent(this);
+    nsCOMPtr<nsIRunnable> event = new CheckPluginStopEvent(this);
+    mPendingCheckPluginStopEvent = event;
 
     nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
     if (appShell) {
       appShell->RunInStableState(event);
     }
   } else if (mType != eType_Image) {
     // nsImageLoadingContent handles the image case.
     // Reset state and clear pending events
     /// XXX(johns): The implementation for GenericFrame notes that ideally we
     ///             would keep the docshell around, but trash the frameloader
     UnloadObject();
   }
 
 }
 
 nsObjectLoadingContent::nsObjectLoadingContent()
-  : mPendingInstantiateEvent(nullptr)
-  , mChannel(nullptr)
-  , mType(eType_Loading)
+  : mType(eType_Loading)
   , mFallbackType(eFallbackAlternate)
   , mChannelLoaded(false)
   , mInstantiating(false)
   , mNetworkCreated(true)
   , mActivated(false)
   , mPlayPreviewCanceled(false)
   , mIsStopping(false)
   , mIsLoading(false)
   , mScriptRequested(false) {}
 
 nsObjectLoadingContent::~nsObjectLoadingContent()
 {
-  // Should have been unbound from the tree at this point, and InDocCheckEvent
+  // Should have been unbound from the tree at this point, and CheckPluginStopEvent
   // keeps us alive
   if (mFrameLoader) {
     NS_NOTREACHED("Should not be tearing down frame loaders at this point");
     mFrameLoader->Destroy();
   }
   if (mInstanceOwner) {
     // This is especially bad as delayed stop will try to hold on to this
     // object...
@@ -967,35 +983,59 @@ nsObjectLoadingContent::GetDisplayedType
 {
   *aType = DisplayedType();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsObjectLoadingContent::HasNewFrame(nsIObjectFrame* aFrame)
 {
-  if (mType == eType_Plugin) {
-    if (!mInstanceOwner) {
-      // We have successfully set ourselves up in LoadObject, but not spawned an
-      // instance due to a lack of a frame.
-      AsyncStartPluginInstance();
-      return NS_OK;
+  if (mType != eType_Plugin) {
+    return NS_OK;
+  }
+
+  if (!aFrame) {
+    // Lost our frame. If we aren't going to be getting a new frame, e.g. we've
+    // become display:none, we'll want to stop the plugin. Queue a
+    // CheckPluginStopEvent
+    if (mInstanceOwner) {
+      mInstanceOwner->SetFrame(nullptr);
+
+      nsCOMPtr<nsIRunnable> event = new CheckPluginStopEvent(this);
+      mPendingCheckPluginStopEvent = event;
+      nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
+      if (appShell) {
+        appShell->RunInStableState(event);
+      } else {
+        NS_NOTREACHED("No app shell?");
+      }
     }
-
-    // Disconnect any existing frame
-    mInstanceOwner->SetFrame(nullptr);
-
-    // Set up relationship between instance owner and frame.
-    nsObjectFrame *objFrame = static_cast<nsObjectFrame*>(aFrame);
-    mInstanceOwner->SetFrame(objFrame);
-
-    // Set up new frame to draw.
-    objFrame->FixupWindow(objFrame->GetContentRectRelativeToSelf().Size());
-    objFrame->InvalidateFrame();
+    return NS_OK;
+  }
+
+  // Have a new frame
+
+  if (!mInstanceOwner) {
+    // We are successfully setup as type plugin, but have not spawned an
+    // instance due to a lack of a frame.
+    AsyncStartPluginInstance();
+    return NS_OK;
   }
+
+  // Otherwise, we're just changing frames
+  mInstanceOwner->SetFrame(nullptr);
+
+  // Set up relationship between instance owner and frame.
+  nsObjectFrame *objFrame = static_cast<nsObjectFrame*>(aFrame);
+  mInstanceOwner->SetFrame(objFrame);
+
+  // Set up new frame to draw.
+  objFrame->FixupWindow(objFrame->GetContentRectRelativeToSelf().Size());
+  objFrame->InvalidateFrame();
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsObjectLoadingContent::GetPluginInstance(nsNPAPIPluginInstance** aInstance)
 {
   *aInstance = nullptr;
 
@@ -1774,17 +1814,17 @@ nsObjectLoadingContent::LoadObject(bool 
     // activated until it changes content type
     LOG(("OBJLC [%p]: Object implicitly activated", this));
     mActivated = true;
   }
 
   // Sanity check: We shouldn't have any loaded resources, pending events, or
   // a final listener at this point
   if (mFrameLoader || mPendingInstantiateEvent || mInstanceOwner ||
-      mFinalListener)
+      mPendingCheckPluginStopEvent || mFinalListener)
   {
     NS_NOTREACHED("Trying to load new plugin with existing content");
     rv = NS_ERROR_UNEXPECTED;
     return NS_OK;
   }
 
   // More sanity-checking:
   // If mChannel is set, mChannelLoaded should be set, and vice-versa
@@ -2479,20 +2519,21 @@ nsObjectLoadingContent::LoadFallback(Fal
   NotifyStateChanged(oldType, oldState, false, true);
 }
 
 void
 nsObjectLoadingContent::DoStopPlugin(nsPluginInstanceOwner* aInstanceOwner,
                                      bool aDelayedStop,
                                      bool aForcedReentry)
 {
-  // DoStopPlugin can process events and there may be pending InDocCheckEvent
-  // events which can drop in underneath us and destroy the instance we are
-  // about to destroy unless we prevent that with the mPluginStopping flag.
-  // (aForcedReentry is only true from the callback of an earlier delayed stop)
+  // DoStopPlugin can process events -- There may be pending
+  // CheckPluginStopEvent events which can drop in underneath us and destroy the
+  // instance we are about to destroy. We prevent that with the mPluginStopping
+  // flag.  (aForcedReentry is only true from the callback of an earlier delayed
+  // stop)
   if (mIsStopping && !aForcedReentry) {
     return;
   }
   mIsStopping = true;
 
   nsRefPtr<nsPluginInstanceOwner> kungFuDeathGrip(aInstanceOwner);
   nsRefPtr<nsNPAPIPluginInstance> inst;
   aInstanceOwner->GetInstance(getter_AddRefs(inst));
@@ -2514,18 +2555,19 @@ nsObjectLoadingContent::DoStopPlugin(nsP
   aInstanceOwner->Destroy();
 
   mIsStopping = false;
 }
 
 NS_IMETHODIMP
 nsObjectLoadingContent::StopPluginInstance()
 {
-  // Prevents any pending plugin starts from running
+  // Clear any pending events
   mPendingInstantiateEvent = nullptr;
+  mPendingCheckPluginStopEvent = nullptr;
 
   if (!mInstanceOwner) {
     return NS_OK;
   }
 
   if (mChannel) {
     // The plugin has already used data from this channel, we'll need to
     // re-open it to handle instantiating again, even if we don't invalidate
--- a/content/base/src/nsObjectLoadingContent.h
+++ b/content/base/src/nsObjectLoadingContent.h
@@ -35,17 +35,17 @@ class nsObjectLoadingContent : public ns
                              , public nsIStreamListener
                              , public nsIFrameLoaderOwner
                              , public nsIObjectLoadingContent
                              , public nsIInterfaceRequestor
                              , public nsIChannelEventSink
 {
   friend class AutoSetInstantiatingToFalse;
   friend class AutoSetLoadingToFalse;
-  friend class InDocCheckEvent;
+  friend class CheckPluginStopEvent;
   friend class nsStopPluginRunnable;
   friend class nsAsyncInstantiateEvent;
 
   public:
     // This enum's values must be the same as the constants on
     // nsIObjectLoadingContent
     enum ObjectType {
       // Loading, type not yet known. We may be waiting for a channel to open.
@@ -447,16 +447,19 @@ class nsObjectLoadingContent : public ns
     nsCOMPtr<nsIStreamListener> mFinalListener;
 
     // Frame loader, for content documents we load.
     nsRefPtr<nsFrameLoader>     mFrameLoader;
 
     // Track if we have a pending AsyncInstantiateEvent
     nsCOMPtr<nsIRunnable>       mPendingInstantiateEvent;
 
+    // Tracks if we have a pending CheckPluginStopEvent
+    nsCOMPtr<nsIRunnable>       mPendingCheckPluginStopEvent;
+
     // The content type of our current load target, updated by
     // UpdateObjectParameters(). Takes the channel's type into account once
     // opened.
     //
     // May change if a channel is opened, does not imply a loaded state
     nsCString                   mContentType;
 
     // The content type 'hint' provided by the element's type attribute. May
--- a/layout/generic/nsObjectFrame.cpp
+++ b/layout/generic/nsObjectFrame.cpp
@@ -304,25 +304,22 @@ nsObjectFrame::DestroyFrom(nsIFrame* aDe
 {
   if (mReflowCallbackPosted) {
     PresContext()->PresShell()->CancelReflowCallback(this);
   }
 
   // Tell content owner of the instance to disconnect its frame.
   nsCOMPtr<nsIObjectLoadingContent> objContent(do_QueryInterface(mContent));
   NS_ASSERTION(objContent, "Why not an object loading content?");
-  objContent->DisconnectFrame();
+  objContent->HasNewFrame(nullptr);
 
   if (mBackgroundSink) {
     mBackgroundSink->Destroy();
   }
 
-  if (mInstanceOwner) {
-    mInstanceOwner->SetFrame(nullptr);
-  }
   SetInstanceOwner(nullptr);
 
   nsObjectFrameSuper::DestroyFrom(aDestructRoot);
 }
 
 /* virtual */ void
 nsObjectFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
 {