Bug 700835 - Restore plugin caching to work around bad crash in latest Apple Java updates. r=josh a=akeybl CALENDAR_1_1b1_BUILD1 CALENDAR_1_1b1_BUILD2 CALENDAR_1_1b1_RELEASE THUNDERBIRD_9_0b1_BUILD1 THUNDERBIRD_9_0b1_BUILD2 THUNDERBIRD_9_0b1_RELEASE
authorSteven Michaud <smichaud@pobox.com>
Fri, 11 Nov 2011 21:20:55 -0600
changeset 78924 59962ec0942ee43741f23f8b22d6c4c58fc3097a
parent 78923 aaa88890a7b9bc6d2a0fd1167a8fd92930fe05ae
child 78925 61ff9efcfde1e1b25d60e50d280c1aedb28f9234
child 78929 27e13d1326c3eadc2ccf682c7312dd300fc97567
push id1
push usersledru@mozilla.com
push dateThu, 04 Dec 2014 17:57:20 +0000
reviewersjosh, akeybl
bugs700835
milestone9.0
Bug 700835 - Restore plugin caching to work around bad crash in latest Apple Java updates. r=josh a=akeybl
dom/plugins/base/nsNPAPIPlugin.cpp
dom/plugins/base/nsNPAPIPluginInstance.cpp
dom/plugins/base/nsNPAPIPluginInstance.h
dom/plugins/base/nsPluginHost.cpp
dom/plugins/base/nsPluginHost.h
--- a/dom/plugins/base/nsNPAPIPlugin.cpp
+++ b/dom/plugins/base/nsNPAPIPlugin.cpp
@@ -2475,19 +2475,18 @@ NPError NP_CALLBACK
           } else {
             rv = contextStack->Pop(nsnull);
           }
         }
         return NS_SUCCEEDED(rv) ? NPERR_NO_ERROR : NPERR_GENERIC_ERROR;
       }
 
     case NPPVpluginKeepLibraryInMemory: {
-      // This variable is not supported any more but we'll pretend it is
-      // so that plugins don't fail on an error return.
-      return NS_OK;
+      NPBool bCached = (result != nsnull);
+      return inst->SetCached(bCached);
     }
 
     case NPPVpluginUsesDOMForCursorBool: {
       PRBool useDOMForCursor = (result != nsnull);
       return inst->SetUsesDOMForCursor(useDOMForCursor);
     }
 
 #ifdef XP_MACOSX
--- a/dom/plugins/base/nsNPAPIPluginInstance.cpp
+++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp
@@ -90,16 +90,17 @@ nsNPAPIPluginInstance::nsNPAPIPluginInst
 #ifdef ANDROID
     mSurface(nsnull),
     mDrawingModel(0),
 #endif
     mRunning(NOT_STARTED),
     mWindowless(PR_FALSE),
     mWindowlessLocal(PR_FALSE),
     mTransparent(PR_FALSE),
+    mCached(PR_FALSE),
     mUsesDOMForCursor(PR_FALSE),
     mInPluginInitCall(PR_FALSE),
     mPlugin(plugin),
     mMIMEType(nsnull),
     mOwner(nsnull),
     mCurrentPluginEvent(nsnull),
 #if defined(MOZ_X11) || defined(XP_WIN) || defined(XP_MACOSX)
     mUsePluginLayersPref(PR_TRUE)
@@ -137,16 +138,22 @@ nsNPAPIPluginInstance::~nsNPAPIPluginIns
 
 void
 nsNPAPIPluginInstance::Destroy()
 {
   Stop();
   mPlugin = nsnull;
 }
 
+TimeStamp
+nsNPAPIPluginInstance::StopTime()
+{
+  return mStopTime;
+}
+
 nsresult nsNPAPIPluginInstance::Initialize(nsIPluginInstanceOwner* aOwner, const char* aMIMEType)
 {
   PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsNPAPIPluginInstance::Initialize this=%p\n",this));
 
   mOwner = aOwner;
 
   if (aMIMEType) {
     mMIMEType = (char*)PR_Malloc(PL_strlen(aMIMEType) + 1);
@@ -195,16 +202,17 @@ nsresult nsNPAPIPluginInstance::Stop()
     return NS_OK;
   }
 
   // Make sure we lock while we're writing to mRunning after we've
   // started as other threads might be checking that inside a lock.
   {
     AsyncCallbackAutoLock lock;
     mRunning = DESTROYING;
+    mStopTime = TimeStamp::Now();
   }
 
   OnPluginDestroy(&mNPP);
 
   // clean up open streams
   while (mStreamListeners.Length() > 0) {
     nsRefPtr<nsNPAPIPluginStreamListener> currentListener(mStreamListeners[0]);
     currentListener->CleanUpStream(NPRES_USER_BREAK);
@@ -869,16 +877,29 @@ nsNPAPIPluginInstance::DefineJavaPropert
 
   if (!ok)
     return NS_ERROR_FAILURE;
 
   return NS_OK;
 }
 
 nsresult
+nsNPAPIPluginInstance::SetCached(PRBool aCache)
+{
+  mCached = aCache;
+  return NS_OK;
+}
+
+PRBool
+nsNPAPIPluginInstance::ShouldCache()
+{
+  return mCached;
+}
+
+nsresult
 nsNPAPIPluginInstance::IsWindowless(PRBool* isWindowless)
 {
 #ifdef ANDROID
   // On android, pre-honeycomb, all plugins are treated as windowless.
   *isWindowless = PR_TRUE;
 #else
   *isWindowless = mWindowless;
 #endif
--- a/dom/plugins/base/nsNPAPIPluginInstance.h
+++ b/dom/plugins/base/nsNPAPIPluginInstance.h
@@ -90,16 +90,17 @@ public:
   nsresult PostEvent(void* event) { return 0; };
 #endif
   nsresult HandleEvent(void* event, PRInt16* result);
   nsresult GetValueFromPlugin(NPPVariable variable, void* value);
   nsresult GetDrawingModel(PRInt32* aModel);
   nsresult IsRemoteDrawingCoreAnimation(PRBool* aDrawing);
   nsresult GetJSObject(JSContext *cx, JSObject** outObject);
   nsresult DefineJavaProperties();
+  PRBool ShouldCache();
   nsresult IsWindowless(PRBool* isWindowless);
   nsresult AsyncSetWindow(NPWindow* window);
   nsresult GetImage(ImageContainer* aContainer, Image** aImage);
   nsresult GetImageSize(nsIntSize* aSize);
   nsresult NotifyPainted(void);
   nsresult UseAsyncPainting(PRBool* aIsAsync);
   nsresult SetBackgroundUnknown();
   nsresult BeginUpdateBackground(nsIntRect* aRect, gfxContext** aContext);
@@ -168,16 +169,22 @@ public:
     return mRunning >= DESTROYING;
   }
 
   // Indicates whether the plugin is running normally or being shut down
   bool CanFireNotifications() {
     return mRunning == RUNNING || mRunning == DESTROYING;
   }
 
+  // return is only valid when the plugin is not running
+  mozilla::TimeStamp StopTime();
+
+  // cache this NPAPI plugin
+  nsresult SetCached(PRBool aCache);
+
   already_AddRefed<nsPIDOMWindow> GetDOMWindow();
 
   nsresult PrivateModeStateChanged();
 
   nsresult GetDOMElement(nsIDOMElement* *result);
 
   nsNPAPITimer* TimerWithID(uint32_t id, PRUint32* index);
   uint32_t      ScheduleTimer(uint32_t interval, NPBool repeat, void (*timerFunc)(NPP npp, uint32_t timerID));
@@ -227,16 +234,17 @@ protected:
     DESTROYED
   } mRunning;
 
   // these are used to store the windowless properties
   // which the browser will later query
   PRPackedBool mWindowless;
   PRPackedBool mWindowlessLocal;
   PRPackedBool mTransparent;
+  PRPackedBool mCached;
   PRPackedBool mUsesDOMForCursor;
 
 public:
   // True while creating the plugin, or calling NPP_SetWindow() on it.
   PRPackedBool mInPluginInitCall;
 
   nsXPIDLCString mFakeURL;
 
@@ -255,16 +263,20 @@ private:
   // InvalidateOwner()) when it's no longer our owner.
   nsIPluginInstanceOwner *mOwner;
 
   nsTArray<nsNPAPITimer*> mTimers;
 
   // non-null during a HandleEvent call
   void* mCurrentPluginEvent;
 
+  // Timestamp for the last time this plugin was stopped.
+  // This is only valid when the plugin is actually stopped!
+  mozilla::TimeStamp mStopTime;
+
   nsCOMPtr<nsIURI> mURI;
 
   PRPackedBool mUsePluginLayersPref;
 #ifdef ANDROID
   void* mSurface;
 #endif
 };
 
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -225,16 +225,20 @@ static const char kPluginsMimeExtKey[] =
 PRLogModuleInfo* nsPluginLogging::gNPNLog = nsnull;
 PRLogModuleInfo* nsPluginLogging::gNPPLog = nsnull;
 PRLogModuleInfo* nsPluginLogging::gPluginLog = nsnull;
 #endif
 
 #define BRAND_PROPERTIES_URL "chrome://branding/locale/brand.properties"
 #define PLUGIN_PROPERTIES_URL "chrome://global/locale/downloadProgress.properties"
 
+// #defines for plugin cache and prefs
+#define NS_PREF_MAX_NUM_CACHED_INSTANCES "browser.plugins.max_num_cached_plugins"
+#define DEFAULT_NUMBER_OF_STOPPED_INSTANCES 10
+
 #ifdef CALL_SAFETY_ON
 // By default we run OOPP, so we don't want to cover up crashes.
 PRBool gSkipPluginSafeCalls = PR_TRUE;
 #endif
 
 nsIFile *nsPluginHost::sPluginTempDir;
 nsPluginHost *nsPluginHost::sInst;
 
@@ -3193,23 +3197,44 @@ nsPluginHost::StopPluginInstance(nsNPAPI
 
   PLUGIN_LOG(PLUGIN_LOG_NORMAL,
   ("nsPluginHost::StopPluginInstance called instance=%p\n",aInstance));
 
   if (aInstance->HasStartedDestroying()) {
     return NS_OK;
   }
 
-  nsPluginTag* pluginTag = TagForPlugin(aInstance->GetPlugin());
-
   aInstance->Stop();
-  aInstance->Destroy();
-  mInstances.RemoveElement(aInstance);
-
-  OnPluginInstanceDestroyed(pluginTag);
+
+  // if the instance does not want to be 'cached' just remove it
+  PRBool doCache = aInstance->ShouldCache();
+  if (doCache) {
+    // try to get the max cached instances from a pref or use default
+    PRUint32 cachedInstanceLimit;
+    nsresult rv = NS_ERROR_FAILURE;
+    if (mPrefService)
+      rv = mPrefService->GetIntPref(NS_PREF_MAX_NUM_CACHED_INSTANCES, (int*)&cachedInstanceLimit);
+    if (NS_FAILED(rv))
+      cachedInstanceLimit = DEFAULT_NUMBER_OF_STOPPED_INSTANCES;
+    
+    if (StoppedInstanceCount() >= cachedInstanceLimit) {
+      nsNPAPIPluginInstance *oldestInstance = FindOldestStoppedInstance();
+      if (oldestInstance) {
+        nsPluginTag* pluginTag = TagForPlugin(oldestInstance->GetPlugin());
+        oldestInstance->Destroy();
+        mInstances.RemoveElement(oldestInstance);
+        OnPluginInstanceDestroyed(pluginTag);
+      }
+    }
+  } else {
+    nsPluginTag* pluginTag = TagForPlugin(aInstance->GetPlugin());
+    aInstance->Destroy();
+    mInstances.RemoveElement(aInstance);
+    OnPluginInstanceDestroyed(pluginTag);
+  }
 
   return NS_OK;
 }
 
 nsresult nsPluginHost::NewEmbeddedPluginStreamListener(nsIURI* aURL,
                                                        nsIPluginInstanceOwner *aOwner,
                                                        nsNPAPIPluginInstance* aInstance,
                                                        nsIStreamListener** aListener)
@@ -3925,16 +3950,48 @@ nsPluginHost::FindInstance(const char *m
 
     if (PL_strcasecmp(mt, mimetype) == 0)
       return instance;
   }
 
   return nsnull;
 }
 
+nsNPAPIPluginInstance*
+nsPluginHost::FindOldestStoppedInstance()
+{
+  nsNPAPIPluginInstance *oldestInstance = nsnull;
+  TimeStamp oldestTime = TimeStamp::Now();
+  for (PRUint32 i = 0; i < mInstances.Length(); i++) {
+    nsNPAPIPluginInstance *instance = mInstances[i];
+    if (instance->IsRunning())
+      continue;
+
+    TimeStamp time = instance->StopTime();
+    if (time < oldestTime) {
+      oldestTime = time;
+      oldestInstance = instance;
+    }
+  }
+
+  return oldestInstance;
+}
+
+PRUint32
+nsPluginHost::StoppedInstanceCount()
+{
+  PRUint32 stoppedCount = 0;
+  for (PRUint32 i = 0; i < mInstances.Length(); i++) {
+    nsNPAPIPluginInstance *instance = mInstances[i];
+    if (!instance->IsRunning())
+      stoppedCount++;
+  }
+  return stoppedCount;
+}
+
 nsTArray< nsRefPtr<nsNPAPIPluginInstance> >*
 nsPluginHost::InstanceArray()
 {
   return &mInstances;
 }
 
 void 
 nsPluginHost::DestroyRunningInstances(nsISupportsArray* aReloadDocs, nsPluginTag* aPluginTag)
--- a/dom/plugins/base/nsPluginHost.h
+++ b/dom/plugins/base/nsPluginHost.h
@@ -200,16 +200,18 @@ public:
 
   static nsresult PostPluginUnloadEvent(PRLibrary* aLibrary);
 
   void PluginCrashed(nsNPAPIPlugin* plugin,
                      const nsAString& pluginDumpID,
                      const nsAString& browserDumpID);
 
   nsNPAPIPluginInstance *FindInstance(const char *mimetype);
+  nsNPAPIPluginInstance *FindOldestStoppedInstance();
+  PRUint32 StoppedInstanceCount();
 
   nsTArray< nsRefPtr<nsNPAPIPluginInstance> > *InstanceArray();
 
   void DestroyRunningInstances(nsISupportsArray* aReloadDocs, nsPluginTag* aPluginTag);
 
   // Return the tag for |aLibrary| if found, nsnull if not.
   nsPluginTag* FindTagForLibrary(PRLibrary* aLibrary);