Bug 596094 - Restore plugin subclass when Flash resets it in full screen mode (ipc enabled case). r=bent, a=betaN
authorJim Mathies <jmathies@mozilla.com>
Thu, 21 Oct 2010 16:52:48 -0500
changeset 56343 e5f3177aa3bcccc396eceaf86d150c98aaf45513
parent 56342 0c356b93b352e9a7884879015de3fc53312f0ec0
child 56344 fc2352fe2c8ee3a0b3580408a71485ddee0dbcba
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent, betaN
bugs596094
milestone2.0b8pre
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 596094 - Restore plugin subclass when Flash resets it in full screen mode (ipc enabled case). r=bent, a=betaN
dom/plugins/PluginInstanceChild.cpp
dom/plugins/PluginInstanceChild.h
ipc/glue/WindowsMessageLoop.cpp
--- a/dom/plugins/PluginInstanceChild.cpp
+++ b/dom/plugins/PluginInstanceChild.cpp
@@ -89,16 +89,18 @@ 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;
 
+static const TCHAR kPluginIgnoreSubclassProperty[] = TEXT("PluginIgnoreSubclassProperty");
+
 #elif defined(XP_MACOSX)
 #include <ApplicationServices/ApplicationServices.h>
 #endif // defined(XP_MACOSX)
 
 template<>
 struct RunnableMethodTraits<PluginInstanceChild>
 {
     static void RetainCallee(PluginInstanceChild* obj) { }
@@ -198,16 +200,17 @@ PluginInstanceChild::InitQuirksModes(con
     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; 
+        mQuirks |= QUIRK_FLASH_HOOK_SETLONGPTR;
     }
 #endif
 }
 
 NPError
 PluginInstanceChild::InternalGetNPObjectForValue(NPNVariable aValue,
                                                  NPObject** aObject)
 {
@@ -924,24 +927,27 @@ PluginInstanceChild::AnswerNPP_SetWindow
           mWindow.window = (void*)mPluginWindowHWND;
           mWindow.x = aWindow.x;
           mWindow.y = aWindow.y;
           mWindow.width = aWindow.width;
           mWindow.height = aWindow.height;
           mWindow.type = aWindow.type;
 
           if (mPluginIface->setwindow) {
+              SetProp(mPluginWindowHWND, kPluginIgnoreSubclassProperty, (HANDLE)1);
               (void) mPluginIface->setwindow(&mData, &mWindow);
               WNDPROC wndProc = reinterpret_cast<WNDPROC>(
                   GetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC));
               if (wndProc != PluginWindowProc) {
                   mPluginWndProc = reinterpret_cast<WNDPROC>(
                       SetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC,
                                        reinterpret_cast<LONG_PTR>(PluginWindowProc)));
               }
+              RemoveProp(mPluginWindowHWND, kPluginIgnoreSubclassProperty);
+              HookSetWindowLongPtr();
           }
       }
       break;
 
       case NPWindowTypeDrawable:
           mWindow.type = aWindow.type;
           if (mQuirks & QUIRK_WINLESS_TRACKPOPUP_HOOK)
               CreateWinlessPopupSurrogate();
@@ -989,16 +995,17 @@ PluginInstanceChild::Initialize()
 {
     return true;
 }
 
 #if defined(OS_WIN)
 
 static const TCHAR kWindowClassName[] = TEXT("GeckoPluginWindow");
 static const TCHAR kPluginInstanceChildProperty[] = TEXT("PluginInstanceChildProperty");
+static const TCHAR kFlashThrottleProperty[] = TEXT("MozillaFlashThrottleProperty");
 
 // static
 bool
 PluginInstanceChild::RegisterWindowClass()
 {
     static bool alreadyRegistered = false;
     if (alreadyRegistered)
         return true;
@@ -1040,37 +1047,38 @@ PluginInstanceChild::CreatePluginWindow(
                        WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, 0,
                        0, 0, NULL, 0, GetModuleHandle(NULL), 0);
     if (!mPluginWindowHWND)
         return false;
     if (!SetProp(mPluginWindowHWND, kPluginInstanceChildProperty, this))
         return false;
 
     // Apparently some plugins require an ASCII WndProc.
+    printf("setting DefWindowProcA\n");
     SetWindowLongPtrA(mPluginWindowHWND, GWLP_WNDPROC,
                       reinterpret_cast<LONG_PTR>(DefWindowProcA));
 
     return true;
 }
 
 void
 PluginInstanceChild::DestroyPluginWindow()
 {
     if (mPluginWindowHWND) {
         // Unsubclass the window.
         WNDPROC wndProc = reinterpret_cast<WNDPROC>(
             GetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC));
+        // Removed prior to SetWindowLongPtr, see HookSetWindowLongPtr.
+        RemoveProp(mPluginWindowHWND, kPluginInstanceChildProperty);
         if (wndProc == PluginWindowProc) {
             NS_ASSERTION(mPluginWndProc, "Should have old proc here!");
             SetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC,
                              reinterpret_cast<LONG_PTR>(mPluginWndProc));
             mPluginWndProc = 0;
         }
-
-        RemoveProp(mPluginWindowHWND, kPluginInstanceChildProperty);
         DestroyWindow(mPluginWindowHWND);
         mPluginWindowHWND = 0;
     }
 }
 
 void
 PluginInstanceChild::ReparentPluginWindow(HWND hWndParent)
 {
@@ -1117,17 +1125,16 @@ PluginInstanceChild::DummyWindowProc(HWN
 LRESULT CALLBACK
 PluginInstanceChild::PluginWindowProc(HWND hWnd,
                                       UINT message,
                                       WPARAM wParam,
                                       LPARAM lParam)
 {
     NS_ASSERTION(!mozilla::ipc::SyncChannel::IsPumpingMessages(),
                  "Failed to prevent a nonqueued message from running!");
-
     PluginInstanceChild* self = reinterpret_cast<PluginInstanceChild*>(
         GetProp(hWnd, kPluginInstanceChildProperty));
     if (!self) {
         NS_NOTREACHED("Badness!");
         return 0;
     }
 
     NS_ASSERTION(self->mPluginWindowHWND == hWnd, "Wrong window!");
@@ -1182,16 +1189,19 @@ PluginInstanceChild::PluginWindowProc(HW
     }
 
     // Make sure capture is released by the child on mouse events. Fixes a
     // problem with flash full screen mode mouse input. Appears to be
     // caused by a bug in flash, since we are not setting the capture
     // on the window. (In non-oopp land, we would set and release via
     // widget for other reasons.)
     switch(message) {    
+      case WM_LBUTTONDOWN:
+      case WM_MBUTTONDOWN:
+      case WM_RBUTTONDOWN:
       case WM_LBUTTONUP:
       case WM_MBUTTONUP:
       case WM_RBUTTONUP:
       ReleaseCapture();
       break;
     }
 
     LRESULT res = CallWindowProc(self->mPluginWndProc, hWnd, message, wParam,
@@ -1201,16 +1211,168 @@ PluginInstanceChild::PluginWindowProc(HW
         self->DestroyPluginWindow();
 
     if (message == WM_NCDESTROY)
         RemoveProp(hWnd, kPluginInstanceChildProperty);
 
     return res;
 }
 
+/* set window long ptr hook for flash */
+
+/*
+ * Flash will reset the subclass of our widget at various times.
+ * (Notably when entering and exiting full screen mode.) This
+ * occurs independent of the main plugin window event procedure.
+ * We trap these subclass calls to prevent our subclass hook from
+ * getting dropped.
+ * Note, ascii versions can be nixed once flash versions < 10.1
+ * are considered obsolete.
+ */
+ 
+#ifdef _WIN64
+typedef LONG_PTR
+  (WINAPI *User32SetWindowLongPtrA)(HWND hWnd,
+                                    int nIndex,
+                                    LONG_PTR dwNewLong);
+typedef LONG_PTR
+  (WINAPI *User32SetWindowLongPtrW)(HWND hWnd,
+                                    int nIndex,
+                                    LONG_PTR dwNewLong);
+static User32SetWindowLongPtrA sUser32SetWindowLongAHookStub = NULL;
+static User32SetWindowLongPtrW sUser32SetWindowLongWHookStub = NULL;
+#else
+typedef LONG
+(WINAPI *User32SetWindowLongA)(HWND hWnd,
+                               int nIndex,
+                               LONG dwNewLong);
+typedef LONG
+(WINAPI *User32SetWindowLongW)(HWND hWnd,
+                               int nIndex,
+                               LONG dwNewLong);
+static User32SetWindowLongA sUser32SetWindowLongAHookStub = NULL;
+static User32SetWindowLongW sUser32SetWindowLongWHookStub = NULL;
+#endif
+
+extern LRESULT CALLBACK
+NeuteredWindowProc(HWND hwnd,
+                   UINT uMsg,
+                   WPARAM wParam,
+                   LPARAM lParam);
+
+const wchar_t kOldWndProcProp[] = L"MozillaIPCOldWndProc";
+
+// static
+PRBool
+PluginInstanceChild::SetWindowLongHookCheck(HWND hWnd,
+                                            int nIndex,
+                                            LONG_PTR newLong)
+{
+      // Let this go through if it's not a subclass
+  if (nIndex != GWLP_WNDPROC ||
+      // if it's not a subclassed plugin window
+      !GetProp(hWnd, kPluginInstanceChildProperty) ||
+      // if we're not disabled
+      GetProp(hWnd, kPluginIgnoreSubclassProperty) ||
+      // if the subclass is set to a known procedure
+      newLong == reinterpret_cast<LONG_PTR>(PluginWindowProc) ||
+      newLong == reinterpret_cast<LONG_PTR>(NeuteredWindowProc) ||
+      newLong == reinterpret_cast<LONG_PTR>(DefWindowProcA) ||
+      newLong == reinterpret_cast<LONG_PTR>(DefWindowProcW) ||
+      // if the subclass is a WindowsMessageLoop subclass restore
+      GetProp(hWnd, kOldWndProcProp))
+      return PR_TRUE;
+  // prevent the subclass
+  return PR_FALSE;
+}
+
+#ifdef _WIN64
+LONG_PTR WINAPI
+PluginInstanceChild::SetWindowLongPtrAHook(HWND hWnd,
+                                           int nIndex,
+                                           LONG_PTR newLong)
+#else
+LONG WINAPI
+PluginInstanceChild::SetWindowLongAHook(HWND hWnd,
+                                        int nIndex,
+                                        LONG newLong)
+#endif
+{
+    if (SetWindowLongHookCheck(hWnd, nIndex, newLong))
+        return sUser32SetWindowLongAHookStub(hWnd, nIndex, newLong);
+
+    // Set flash's new subclass to get the result. 
+    LONG_PTR proc = sUser32SetWindowLongAHookStub(hWnd, nIndex, newLong);
+
+    // We already checked this in SetWindowLongHookCheck
+    PluginInstanceChild* self = reinterpret_cast<PluginInstanceChild*>(
+        GetProp(hWnd, kPluginInstanceChildProperty));
+
+    // Hook our subclass back up, just like we do on setwindow.   
+    self->mPluginWndProc =
+        reinterpret_cast<WNDPROC>(sUser32SetWindowLongAHookStub(hWnd, nIndex,
+            reinterpret_cast<LONG_PTR>(PluginWindowProc)));
+    return proc;
+}
+
+#ifdef _WIN64
+LONG_PTR WINAPI
+PluginInstanceChild::SetWindowLongPtrWHook(HWND hWnd,
+                                           int nIndex,
+                                           LONG_PTR newLong)
+#else
+LONG WINAPI
+PluginInstanceChild::SetWindowLongWHook(HWND hWnd,
+                                        int nIndex,
+                                        LONG newLong)
+#endif
+{
+    if (SetWindowLongHookCheck(hWnd, nIndex, newLong))
+        return sUser32SetWindowLongWHookStub(hWnd, nIndex, newLong);
+
+    // Set flash's new subclass to get the result. 
+    LONG_PTR proc = sUser32SetWindowLongWHookStub(hWnd, nIndex, newLong);
+
+    // We already checked this in SetWindowLongHookCheck
+    PluginInstanceChild* self = reinterpret_cast<PluginInstanceChild*>(
+        GetProp(hWnd, kPluginInstanceChildProperty));
+
+    // Hook our subclass back up, just like we do on setwindow.   
+    self->mPluginWndProc =
+        reinterpret_cast<WNDPROC>(sUser32SetWindowLongWHookStub(hWnd, nIndex,
+            reinterpret_cast<LONG_PTR>(PluginWindowProc)));
+    return proc;
+}
+
+void
+PluginInstanceChild::HookSetWindowLongPtr()
+{
+#ifdef _WIN64
+    // XXX WindowsDllInterceptor doesn't support hooks
+    // in 64-bit builds, disabling this code for now.
+    return;
+#endif
+
+    if (!(GetQuirks() & QUIRK_FLASH_HOOK_SETLONGPTR))
+        return;
+
+    sUser32Intercept.Init("user32.dll");
+#ifdef _WIN64
+    sUser32Intercept.AddHook("SetWindowLongPtrA", SetWindowLongPtrAHook,
+                             (void**) &sUser32SetWindowLongAHookStub);
+    sUser32Intercept.AddHook("SetWindowLongPtrW", SetWindowLongPtrWHook,
+                             (void**) &sUser32SetWindowLongWHookStub);
+#else
+    sUser32Intercept.AddHook("SetWindowLongA", SetWindowLongAHook,
+                             (void**) &sUser32SetWindowLongAHookStub);
+    sUser32Intercept.AddHook("SetWindowLongW", SetWindowLongWHook,
+                             (void**) &sUser32SetWindowLongWHookStub);
+#endif
+}
+
 /* windowless track popup menu helpers */
 
 BOOL
 WINAPI
 PluginInstanceChild::TrackPopupHookProc(HMENU hMenu,
                                         UINT uFlags,
                                         int x,
                                         int y,
@@ -1605,29 +1767,29 @@ PluginInstanceChild::UnhookWinlessFlashT
   NS_ASSERTION(mWinlessHiddenMsgHWND,
                "Missing mWinlessHiddenMsgHWND w/subclass set??");
 
   // reset the subclass
   SetWindowLongPtr(mWinlessHiddenMsgHWND, GWLP_WNDPROC,
                    reinterpret_cast<LONG_PTR>(tmpProc));
 
   // Remove our instance prop
-  RemoveProp(mWinlessHiddenMsgHWND, kPluginInstanceChildProperty);
+  RemoveProp(mWinlessHiddenMsgHWND, kFlashThrottleProperty);
   mWinlessHiddenMsgHWND = nsnull;
 }
 
 // static
 LRESULT CALLBACK
 PluginInstanceChild::WinlessHiddenFlashWndProc(HWND hWnd,
                                                UINT message,
                                                WPARAM wParam,
                                                LPARAM lParam)
 {
     PluginInstanceChild* self = reinterpret_cast<PluginInstanceChild*>(
-        GetProp(hWnd, kPluginInstanceChildProperty));
+        GetProp(hWnd, kFlashThrottleProperty));
     if (!self) {
         NS_NOTREACHED("Badness!");
         return 0;
     }
 
     NS_ASSERTION(self->mWinlessThrottleOldWndProc,
                  "Missing subclass procedure!!");
 
@@ -1675,17 +1837,17 @@ PluginInstanceChild::EnumThreadWindowsCa
                 NS_WARNING("mWinlessThrottleWndProc already set???");
                 return FALSE;
             }
             // Subsclass and store self as a property
             self->mWinlessHiddenMsgHWND = hWnd;
             self->mWinlessThrottleOldWndProc =
                 reinterpret_cast<WNDPROC>(SetWindowLongPtr(hWnd, GWLP_WNDPROC,
                 reinterpret_cast<LONG_PTR>(WinlessHiddenFlashWndProc)));
-            SetProp(hWnd, kPluginInstanceChildProperty, self);
+            SetProp(hWnd, kFlashThrottleProperty, self);
             NS_ASSERTION(self->mWinlessThrottleOldWndProc,
                          "SetWindowLongPtr failed?!");
         }
         // Return no matter what once we find the right window.
         return FALSE;
     }
 
     return TRUE;
@@ -2644,16 +2806,20 @@ DeleteObject(DeletingObjectEntry* e, voi
 }
 
 bool
 PluginInstanceChild::AnswerNPP_Destroy(NPError* aResult)
 {
     PLUGIN_LOG_DEBUG_METHOD;
     AssertPluginThread();
 
+#if defined(OS_WIN)
+    SetProp(mPluginWindowHWND, kPluginIgnoreSubclassProperty, (HANDLE)1);
+#endif
+
     if (mBackSurface) {
         // Get last surface back, and drop it
         SurfaceDescriptor temp = null_t();
         NPRect r = { 0, 0, 1, 1 };
         SendShow(r, temp, &temp);
     }
     if (gfxSharedImageSurface::IsSharedImage(mCurrentSurface))
         DeallocShmem(static_cast<gfxSharedImageSurface*>(mCurrentSurface.get())->GetShmem());
--- a/dom/plugins/PluginInstanceChild.h
+++ b/dom/plugins/PluginInstanceChild.h
@@ -205,35 +205,39 @@ public:
 
     void InvalidateRect(NPRect* aInvalidRect);
 
     uint32_t ScheduleTimer(uint32_t interval, bool repeat, TimerFunc func);
     void UnscheduleTimer(uint32_t id);
 
     void AsyncCall(PluginThreadCallback aFunc, void* aUserData);
 
-private:
-    friend class PluginModuleChild;
+    int GetQuirks() { return mQuirks; }
 
     // 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 << 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                   = 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,
+        // Win32: Catch resets on our subclass by hooking SetWindowLong.
+        QUIRK_FLASH_HOOK_SETLONGPTR                     = 1 << 3,
     };
 
+private:
+    friend class PluginModuleChild;
+
     void InitQuirksModes(const nsCString& aMimeType);
 
     NPError
     InternalGetNPObjectForValue(NPNVariable aValue,
                                 NPObject** aObject);
 
 #if defined(OS_WIN)
     static bool RegisterWindowClass();
@@ -242,16 +246,20 @@ private:
     void ReparentPluginWindow(HWND hWndParent);
     void SizePluginWindow(int width, int height);
     int16_t WinlessHandleEvent(NPEvent& event);
     void CreateWinlessPopupSurrogate();
     void DestroyWinlessPopupSurrogate();
     void InitPopupMenuHook();
     void SetupFlashMsgThrottle();
     void UnhookWinlessFlashThrottle();
+    void HookSetWindowLongPtr();
+    static inline PRBool SetWindowLongHookCheck(HWND hWnd,
+                                                int nIndex,
+                                                LONG_PTR newLong);
     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,
@@ -264,16 +272,32 @@ private:
                                           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);
+#ifdef _WIN64
+    static LONG_PTR WINAPI SetWindowLongPtrAHook(HWND hWnd,
+                                                 int nIndex,
+                                                 LONG_PTR newLong);
+    static LONG_PTR WINAPI SetWindowLongPtrWHook(HWND hWnd,
+                                                 int nIndex,
+                                                 LONG_PTR newLong);
+                      
+#else
+    static LONG WINAPI SetWindowLongAHook(HWND hWnd,
+                                          int nIndex,
+                                          LONG newLong);
+    static LONG WINAPI SetWindowLongWHook(HWND hWnd,
+                                          int nIndex,
+                                          LONG newLong);
+#endif
 
     class FlashThrottleAsyncMsg : public ChildAsyncCall
     {
       public:
         FlashThrottleAsyncMsg();
         FlashThrottleAsyncMsg(PluginInstanceChild* aInst, 
                               HWND aWnd, UINT aMsg,
                               WPARAM aWParam, LPARAM aLParam,
--- a/ipc/glue/WindowsMessageLoop.cpp
+++ b/ipc/glue/WindowsMessageLoop.cpp
@@ -334,16 +334,19 @@ ProcessOrDeferMessage(HWND hwnd,
     NS_ASSERTION(gDeferredMessages, "Out of memory!");
   }
 
   // Save for later. The array takes ownership of |deferred|.
   gDeferredMessages->AppendElement(deferred);
   return res;
 }
 
+} // anonymous namespace
+
+// We need the pointer value of this in PluginInstanceChild.
 LRESULT CALLBACK
 NeuteredWindowProc(HWND hwnd,
                    UINT uMsg,
                    WPARAM wParam,
                    LPARAM lParam)
 {
   WNDPROC oldWndProc = (WNDPROC)GetProp(hwnd, kOldWndProcProp);
   if (!oldWndProc) {
@@ -352,16 +355,18 @@ NeuteredWindowProc(HWND hwnd,
     return DefWindowProc(hwnd, uMsg, wParam, lParam);
   }
 
   // See if we care about this message. We may either ignore it, send it to
   // DefWindowProc, or defer it for later.
   return ProcessOrDeferMessage(hwnd, uMsg, wParam, lParam);
 }
 
+namespace {
+
 static bool
 WindowIsDeferredWindow(HWND hWnd)
 {
   if (!IsWindow(hWnd)) {
     NS_WARNING("Window has died!");
     return false;
   }
 
@@ -459,27 +464,27 @@ NeuterWindowProcedure(HWND hWnd)
   return true;
 }
 
 void
 RestoreWindowProcedure(HWND hWnd)
 {
   NS_ASSERTION(WindowIsDeferredWindow(hWnd),
                "Not a deferred window, this shouldn't be in our list!");
-
-  LONG_PTR oldWndProc = (LONG_PTR)RemoveProp(hWnd, kOldWndProcProp);
+  LONG_PTR oldWndProc = (LONG_PTR)GetProp(hWnd, kOldWndProcProp);
   if (oldWndProc) {
     NS_ASSERTION(oldWndProc != (LONG_PTR)NeuteredWindowProc,
                  "This shouldn't be possible!");
 
     LONG_PTR currentWndProc =
       SetWindowLongPtr(hWnd, GWLP_WNDPROC, oldWndProc);
     NS_ASSERTION(currentWndProc == (LONG_PTR)NeuteredWindowProc,
                  "This should never be switched out from under us!");
   }
+  RemoveProp(hWnd, kOldWndProcProp);
 }
 
 LRESULT CALLBACK
 CallWindowProcedureHook(int nCode,
                         WPARAM wParam,
                         LPARAM lParam)
 {
   if (nCode >= 0) {