Bug 558629 - Meter WM_USER events for Flash in PluginInstanceChild to improve responsiveness and lower CPU utilization. r=bsmedberg, a=developers.
--- 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