Bug 558629 - Meter WM_USER events for Flash in PluginInstanceChild to improve responsiveness and lower CPU utilization. r=bsmedberg, a=developers.
authorJim Mathies <jmathies@mozilla.com>
Mon, 26 Apr 2010 11:29:11 -0500
changeset 34119 8e793e52b99e40d527ceb7301798309badc1c18d
parent 34118 5481c3a1d81eb4830d5628d44b686cbf23d17cf2
child 34120 6f803e665a6598cfaa9c75b5e05d635f5f256058
push id1281
push userbsmedberg@mozilla.com
push dateMon, 26 Apr 2010 21:00:25 +0000
reviewersbsmedberg, developers
bugs558629
milestone1.9.2.5pre
Bug 558629 - Meter WM_USER events for Flash in PluginInstanceChild to improve responsiveness and lower CPU utilization. r=bsmedberg, a=developers.
dom/plugins/ChildAsyncCall.cpp
dom/plugins/ChildAsyncCall.h
dom/plugins/PluginInstanceChild.cpp
dom/plugins/PluginInstanceChild.h
--- a/dom/plugins/ChildAsyncCall.cpp
+++ b/dom/plugins/ChildAsyncCall.cpp
@@ -56,18 +56,27 @@ void
 ChildAsyncCall::Cancel()
 {
   mInstance = NULL;
   mFunc = NULL;
   mData = NULL;
 }
 
 void
+ChildAsyncCall::RemoveFromAsyncList()
+{
+  if (mInstance) {
+    MutexAutoLock lock(mInstance->mAsyncCallMutex);
+    mInstance->mPendingAsyncCalls.RemoveElement(this);
+  }
+}
+
+void
 ChildAsyncCall::Run()
 {
-  if (mFunc) {
-    mInstance->mPendingAsyncCalls.RemoveElement(this);
+  RemoveFromAsyncList();
+
+  if (mFunc)
     mFunc(mData);
-  }
 }
 
 } // namespace plugins
 } // namespace mozilla
--- a/dom/plugins/ChildAsyncCall.h
+++ b/dom/plugins/ChildAsyncCall.h
@@ -54,19 +54,21 @@ class PluginInstanceChild;
 class ChildAsyncCall : public CancelableTask
 {
 public:
   ChildAsyncCall(PluginInstanceChild* instance,
                  PluginThreadCallback aFunc, void* aUserData);
 
   NS_OVERRIDE void Run();
   NS_OVERRIDE void Cancel();
-
-private:
+  
+protected:
   PluginInstanceChild* mInstance;
   PluginThreadCallback mFunc;
   void* mData;
+
+  void RemoveFromAsyncList();
 };
 
 } // namespace plugins
 } // namespace mozilla
 
 #endif // mozilla_plugins_ChildAsyncCall_h
--- a/dom/plugins/PluginInstanceChild.cpp
+++ b/dom/plugins/PluginInstanceChild.cpp
@@ -71,16 +71,21 @@ static WindowsDllInterceptor sUser32Inte
 static HWND sWinlessPopupSurrogateHWND = NULL;
 static User32TrackPopupMenu sUser32TrackPopupMenuStub = NULL;
 
 using mozilla::gfx::SharedDIB;
 
 #include <windows.h>
 #include <windowsx.h>
 
+// Flash WM_USER message delay time for PostDelayedTask. Borrowed
+// from Chromium's web plugin delegate src. See 'flash msg throttling
+// helpers' section for details.
+const int kFlashWMUSERMessageThrottleDelayMs = 5;
+
 #define NS_OOPP_DOUBLEPASS_MSGID TEXT("MozDoublePassMsg")
 
 #ifndef WM_MOUSEHWHEEL
 #define WM_MOUSEHWHEEL                    0x020E
 #endif
 #endif // defined(OS_WIN)
 
 PluginInstanceChild::PluginInstanceChild(const NPPluginFuncs* aPluginIface,
@@ -93,16 +98,17 @@ PluginInstanceChild::PluginInstanceChild
     , mPluginWindowHWND(0)
     , mPluginWndProc(0)
     , mPluginParentHWND(0)
     , mNestedEventHook(0)
     , mNestedEventLevelDepth(0)
     , mNestedEventState(false)
     , mCachedWinlessPluginHWND(0)
     , mWinlessPopupSurrogateHWND(0)
+    , mWinlessThrottleOldWndProc(0)
 #endif // OS_WIN
 {
     memset(&mWindow, 0, sizeof(mWindow));
     mData.ndata = (void*) this;
     mData.pdata = nsnull;
 #if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
     mWindow.ws_info = &mWsInfo;
     memset(&mWsInfo, 0, sizeof(mWsInfo));
@@ -139,16 +145,17 @@ PluginInstanceChild::InitQuirksModes(con
     // application/x-shockwave-flash
     NS_NAMED_LITERAL_CSTRING(flash, "application/x-shockwave-flash");
     if (FindInReadable(silverlight, aMimeType)) {
         mQuirks |= QUIRK_SILVERLIGHT_WINLESS_INPUT_TRANSLATION;
         mQuirks |= QUIRK_WINLESS_TRACKPOPUP_HOOK;
     }
     else if (FindInReadable(flash, aMimeType)) {
         mQuirks |= QUIRK_WINLESS_TRACKPOPUP_HOOK;
+        mQuirks |= QUIRK_FLASH_THROTTLE_WMUSER_EVENTS; 
     }
 #endif
 }
 
 NPError
 PluginInstanceChild::InternalGetNPObjectForValue(NPNVariable aValue,
                                                  NPObject** aObject)
 {
@@ -626,18 +633,21 @@ PluginInstanceChild::AnswerNPP_SetWindow
                       SetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC,
                                        reinterpret_cast<LONG>(PluginWindowProc)));
               }
           }
       }
       break;
 
       case NPWindowTypeDrawable:
+          mWindow.type = aWindow.type;
           if (mQuirks & QUIRK_WINLESS_TRACKPOPUP_HOOK)
               CreateWinlessPopupSurrogate();
+          if (mQuirks & QUIRK_FLASH_THROTTLE_WMUSER_EVENTS)
+              SetupFlashMsgThrottle();
           return SharedSurfaceSetWindow(aWindow);
       break;
 
       default:
           NS_NOTREACHED("Bad plugin window type.");
           return false;
       break;
     }
@@ -835,16 +845,22 @@ PluginInstanceChild::PluginWindowProc(HW
             case WM_MOUSEWHEEL:
             case WM_HSCROLL:
             case WM_VSCROLL:
             ReplyMessage(0);
             break;
         }
     }
 
+    if (message == WM_USER+1 &&
+        (self->mQuirks & PluginInstanceChild::QUIRK_FLASH_THROTTLE_WMUSER_EVENTS)) {
+        self->FlashThrottleMessage(hWnd, message, wParam, lParam, true);
+        return 0;
+    }
+
     LRESULT res = CallWindowProc(self->mPluginWndProc, hWnd, message, wParam,
                                  lParam);
 
     if (message == WM_CLOSE)
         self->DestroyPluginWindow();
 
     if (message == WM_NCDESTROY)
         RemoveProp(hWnd, kPluginInstanceChildProperty);
@@ -1313,16 +1329,167 @@ PluginInstanceChild::SharedSurfacePaint(
                        SRCCOPY);
               mAlphaExtract.doublePass = RENDER_NATIVE;
               return true;
         break;
     }
     return false;
 }
 
+/* flash msg throttling helpers */
+
+// Flash has the unfortunate habit of flooding dispatch loops with custom
+// windowing events they use for timing. We throttle these by dropping the
+// delivery priority below any other event, including pending ipc io
+// notifications. We do this for both windowed and windowless controls.
+
+// static
+LRESULT CALLBACK
+PluginInstanceChild::WinlessHiddenFlashWndProc(HWND hWnd,
+                                               UINT message,
+                                               WPARAM wParam,
+                                               LPARAM lParam)
+{
+    PluginInstanceChild* self = reinterpret_cast<PluginInstanceChild*>(
+        GetProp(hWnd, kPluginInstanceChildProperty));
+    if (!self) {
+        NS_NOTREACHED("Badness!");
+        return 0;
+    }
+
+    NS_ASSERTION(self->mWinlessThrottleOldWndProc,
+                 "Missing subclass procedure!!");
+
+    // Throttle
+    if (message == WM_USER+1) {
+        self->FlashThrottleMessage(hWnd, message, wParam, lParam, false);
+        return 0;
+     }
+
+    // Unhook
+    if (message == WM_NCDESTROY) {
+        WNDPROC tmpProc = self->mWinlessThrottleOldWndProc;
+        self->mWinlessThrottleOldWndProc = nsnull;
+        SetWindowLongPtr(hWnd, GWLP_WNDPROC,
+                         reinterpret_cast<LONG>(tmpProc));
+        LRESULT res = CallWindowProc(tmpProc, hWnd, message, wParam, lParam);
+        RemoveProp(hWnd, kPluginInstanceChildProperty);
+        return res;
+    }
+
+    return CallWindowProc(self->mWinlessThrottleOldWndProc,
+                          hWnd, message, wParam, lParam);
+}
+
+// Enumerate all thread windows looking for flash's hidden message window.
+// Once we find it, sub class it so we can throttle user msgs.  
+// static
+BOOL CALLBACK
+PluginInstanceChild::EnumThreadWindowsCallback(HWND hWnd,
+                                               LPARAM aParam)
+{
+    PluginInstanceChild* self = reinterpret_cast<PluginInstanceChild*>(aParam);
+    if (!self) {
+        NS_NOTREACHED("Enum befuddled!");
+        return FALSE;
+    }
+
+    PRUnichar className[64];
+    if (!GetClassNameW(hWnd, className, sizeof(className)/sizeof(PRUnichar)))
+      return TRUE;
+    
+    if (!wcscmp(className, L"SWFlash_PlaceholderX")) {
+        WNDPROC oldWndProc =
+            reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC));
+        // Only set this if we haven't already.
+        if (oldWndProc != WinlessHiddenFlashWndProc) {
+            if (self->mWinlessThrottleOldWndProc) {
+                NS_WARNING("mWinlessThrottleWndProc already set???");
+                return FALSE;
+            }
+            // Subsclass and store self as a property
+            self->mWinlessThrottleOldWndProc =
+                reinterpret_cast<WNDPROC>(SetWindowLongPtr(hWnd, GWLP_WNDPROC,
+                reinterpret_cast<LONG>(WinlessHiddenFlashWndProc)));
+            SetProp(hWnd, kPluginInstanceChildProperty, self);
+            NS_ASSERTION(self->mWinlessThrottleOldWndProc,
+                         "SetWindowLongPtr failed?!");
+        }
+        // Return no matter what once we find the right window.
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+void
+PluginInstanceChild::SetupFlashMsgThrottle()
+{
+    if (mWindow.type == NPWindowTypeDrawable) {
+        // Search for the flash hidden message window and subclass it. Only
+        // search for flash windows belonging to our ui thread!
+        if (mWinlessThrottleOldWndProc)
+            return;
+        EnumThreadWindows(GetCurrentThreadId(), EnumThreadWindowsCallback,
+                          reinterpret_cast<LPARAM>(this));
+    }
+    else {
+        // Already setup through quirks and the subclass.
+        return;
+    }
+}
+
+WNDPROC
+PluginInstanceChild::FlashThrottleAsyncMsg::GetProc()
+{ 
+    if (mInstance) {
+        return mWindowed ? mInstance->mPluginWndProc :
+                           mInstance->mWinlessThrottleOldWndProc;
+    }
+    return nsnull;
+}
+ 
+void
+PluginInstanceChild::FlashThrottleAsyncMsg::Run()
+{
+    RemoveFromAsyncList();
+
+    // GetProc() checks mInstance, and pulls the procedure from
+    // PluginInstanceChild. We don't transport sub-class procedure
+    // ptrs around in FlashThrottleAsyncMsg msgs.
+    if (!GetProc())
+        return;
+  
+    // deliver the event to flash 
+    CallWindowProc(GetProc(), GetWnd(), GetMsg(), GetWParam(), GetLParam());
+}
+
+void
+PluginInstanceChild::FlashThrottleMessage(HWND aWnd,
+                                          UINT aMsg,
+                                          WPARAM aWParam,
+                                          LPARAM aLParam,
+                                          bool isWindowed)
+{
+    // We reuse ChildAsyncCall so we get the cancelation work
+    // that's done in Destroy.
+    FlashThrottleAsyncMsg* task = new FlashThrottleAsyncMsg(this,
+        aWnd, aMsg, aWParam, aLParam, isWindowed);
+    if (!task)
+        return; 
+
+    {
+        MutexAutoLock lock(mAsyncCallMutex);
+        mPendingAsyncCalls.AppendElement(task);
+    }
+    MessageLoop::current()->PostDelayedTask(FROM_HERE,
+        task, kFlashWMUSERMessageThrottleDelayMs);
+}
+
 #endif // OS_WIN
 
 bool
 PluginInstanceChild::AnswerSetPluginFocus()
 {
     PR_LOG(gPluginLog, PR_LOG_DEBUG, ("%s", FULLFUNCTION));
 
 #if defined(OS_WIN)
--- a/dom/plugins/PluginInstanceChild.h
+++ b/dom/plugins/PluginInstanceChild.h
@@ -191,21 +191,25 @@ public:
 private:
     friend class PluginModuleChild;
 
     // Quirks mode support for various plugin mime types
     enum PluginQuirks {
         // Win32: Translate mouse input based on WM_WINDOWPOSCHANGED
         // windowing events due to winless shared dib rendering. See
         // WinlessHandleEvent for details.
-        QUIRK_SILVERLIGHT_WINLESS_INPUT_TRANSLATION = 1,
+        QUIRK_SILVERLIGHT_WINLESS_INPUT_TRANSLATION     = 1 << 0,
         // Win32: Hook TrackPopupMenu api so that we can swap out parent
         // hwnds. The api will fail with parents not associated with our
         // child ui thread. See WinlessHandleEvent for details.
-        QUIRK_WINLESS_TRACKPOPUP_HOOK = 2,
+        QUIRK_WINLESS_TRACKPOPUP_HOOK                   = 1 << 1,
+        // Win32: Throttle flash WM_USER+1 heart beat messages to prevent
+        // flooding chromium's dispatch loop, which can cause ipc traffic
+        // processing lag.
+        QUIRK_FLASH_THROTTLE_WMUSER_EVENTS              = 1 << 2,
     };
 
     void InitQuirksModes(const nsCString& aMimeType);
 
     NPError
     InternalGetNPObjectForValue(NPNVariable aValue,
                                 NPObject** aObject);
 
@@ -219,16 +223,18 @@ private:
     void SetNestedInputEventHook();
     void ResetNestedEventHook();
     void SetNestedInputPumpHook();
     void ResetPumpHooks();
     void CreateWinlessPopupSurrogate();
     void DestroyWinlessPopupSurrogate();
     void InitPopupMenuHook();
     void InternalCallSetNestedEventState(bool aState);
+    void SetupFlashMsgThrottle();
+    void FlashThrottleMessage(HWND, UINT, WPARAM, LPARAM, bool);
     static LRESULT CALLBACK DummyWindowProc(HWND hWnd,
                                             UINT message,
                                             WPARAM wParam,
                                             LPARAM lParam);
     static LRESULT CALLBACK PluginWindowProc(HWND hWnd,
                                              UINT message,
                                              WPARAM wParam,
                                              LPARAM lParam);
@@ -244,16 +250,55 @@ private:
                                                 LPARAM lParam);
     static BOOL WINAPI TrackPopupHookProc(HMENU hMenu,
                                           UINT uFlags,
                                           int x,
                                           int y,
                                           int nReserved,
                                           HWND hWnd,
                                           CONST RECT *prcRect);
+    static BOOL CALLBACK EnumThreadWindowsCallback(HWND hWnd,
+                                                   LPARAM aParam);
+    static LRESULT CALLBACK WinlessHiddenFlashWndProc(HWND hWnd,
+                                                      UINT message,
+                                                      WPARAM wParam,
+                                                      LPARAM lParam);
+
+    class FlashThrottleAsyncMsg : public ChildAsyncCall
+    {
+      public:
+        FlashThrottleAsyncMsg();
+        FlashThrottleAsyncMsg(PluginInstanceChild* aInst, 
+                              HWND aWnd, UINT aMsg,
+                              WPARAM aWParam, LPARAM aLParam,
+                              bool isWindowed)
+          : ChildAsyncCall(aInst, nsnull, nsnull),
+          mWnd(aWnd),
+          mMsg(aMsg),
+          mWParam(aWParam),
+          mLParam(aLParam),
+          mWindowed(isWindowed)
+        {}
+
+        NS_OVERRIDE void Run();
+
+        WNDPROC GetProc();
+        HWND GetWnd() { return mWnd; }
+        UINT GetMsg() { return mMsg; }
+        WPARAM GetWParam() { return mWParam; }
+        LPARAM GetLParam() { return mLParam; }
+
+      private:
+        HWND                 mWnd;
+        UINT                 mMsg;
+        WPARAM               mWParam;
+        LPARAM               mLParam;
+        bool                 mWindowed;
+    };
+
 #endif
 
     const NPPluginFuncs* mPluginIface;
     NPP_t mData;
     NPWindow mWindow;
     int mQuirks;
 
     // Cached scriptable actors to avoid IPC churn
@@ -268,16 +313,17 @@ private:
     HWND mPluginParentHWND;
     HHOOK mNestedEventHook;
     int mNestedEventLevelDepth;
     bool mNestedEventState;
     HWND mCachedWinlessPluginHWND;
     HWND mWinlessPopupSurrogateHWND;
     nsIntPoint mPluginSize;
     nsIntPoint mPluginOffset;
+    WNDPROC mWinlessThrottleOldWndProc;
 #endif
 
     friend class ChildAsyncCall;
     nsTArray<ChildAsyncCall*> mPendingAsyncCalls;
 
     /**
      * During destruction we enumerate all remaining scriptable objects and
      * invalidate/delete them. Enumeration can re-enter, so maintain a