Bug 953146 part.7 Don't allow other application to activate non-focusable popup r=jimm
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 07 Jan 2014 13:20:04 +0900
changeset 162341 3c354b68a0cad21b266314c6d1f0f947f579963e
parent 162340 1a3822490bb03e22b69643b8d32cc5cfc69c79ff
child 162342 67ff9392766b53c51bb876b5f74e5e6602347668
push idunknown
push userunknown
push dateunknown
reviewersjimm
bugs953146
milestone29.0a1
Bug 953146 part.7 Don't allow other application to activate non-focusable popup r=jimm
widget/windows/nsWindow.cpp
widget/windows/nsWindow.h
widget/windows/nsWindowDefs.h
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -7298,16 +7298,35 @@ nsWindow::GetPopupsToRollup(nsIRollupLis
       break;
     }
   }
   return true;
 }
 
 // static
 bool
+nsWindow::NeedsToHandleNCActivateDelayed(HWND aWnd)
+{
+  // While popup is open, popup window might be activated by other application.
+  // At this time, we need to take back focus to the previous window but it
+  // causes flickering its nonclient area because WM_NCACTIVATE comes before
+  // WM_ACTIVATE and we cannot know which window will take focus at receiving
+  // WM_NCACTIVATE. Therefore, we need a hack for preventing the flickerling.
+  //
+  // If non-popup window receives WM_NCACTIVATE at deactivating, default
+  // wndproc shouldn't handle it as deactivating. Instead, at receiving
+  // WM_ACTIVIATE after that, WM_NCACTIVATE should be sent again manually.
+  // This returns true if the window needs to handle WM_NCACTIVATE later.
+
+  nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
+  return window && !window->IsPopup();
+}
+
+// static
+bool
 nsWindow::DealWithPopups(HWND aWnd, UINT aMessage,
                          WPARAM aWParam, LPARAM aLParam, LRESULT* aResult)
 {
   NS_ASSERTION(aResult, "Bad outResult");
 
   // XXX Why do we use the return value of WM_MOUSEACTIVATE for all messages?
   *aResult = MA_NOACTIVATE;
 
@@ -7318,16 +7337,18 @@ nsWindow::DealWithPopups(HWND aWnd, UINT
   nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
   NS_ENSURE_TRUE(rollupListener, false);
 
   nsCOMPtr<nsIWidget> popup = rollupListener->GetRollupWidget();
   if (!popup) {
     return false;
   }
 
+  static bool sSendingNCACTIVATE = false;
+  static bool sPendingNCACTIVATE = false;
   uint32_t popupsToRollup = UINT32_MAX;
 
   nsWindow* popupWindow = static_cast<nsWindow*>(popup.get());
   UINT nativeMessage = WinUtils::GetNativeMessage(aMessage);
   switch (nativeMessage) {
     case WM_LBUTTONDOWN:
     case WM_RBUTTONDOWN:
     case WM_MBUTTONDOWN:
@@ -7352,23 +7373,69 @@ nsWindow::DealWithPopups(HWND aWnd, UINT
         }
       }
       return false;
 
     case WM_ACTIVATEAPP:
       break;
 
     case WM_ACTIVATE:
+      // NOTE: Don't handle WA_INACTIVE for preventing popup taking focus
+      // because we cannot distinguish it's caused by mouse or not.
+      if (LOWORD(aWParam) == WA_ACTIVE && aLParam) {
+        nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
+        if (window && window->IsPopup()) {
+          // Cancel notifying widget listeners of deactivating the previous
+          // active window (see WM_KILLFOCUS case in ProcessMessage()).
+          sJustGotDeactivate = false;
+          // Reactivate the window later.
+          ::PostMessageW(aWnd, MOZ_WM_REACTIVATE, aWParam, aLParam);
+          return true;
+        }
+      } else if (sPendingNCACTIVATE && LOWORD(aWParam) == WA_INACTIVE &&
+                 NeedsToHandleNCActivateDelayed(aWnd)) {
+        // If focus moves to non-popup widget or focusable popup, the window
+        // needs to update its nonclient area.
+        nsWindow* activeWindow =
+          WinUtils::GetNSWindowPtr(reinterpret_cast<HWND>(aLParam));
+        if (!activeWindow || !activeWindow->IsPopup()) {
+          sSendingNCACTIVATE = true;
+          ::SendMessageW(aWnd, WM_NCACTIVATE, false, 0);
+          sSendingNCACTIVATE = false;
+        }
+        sPendingNCACTIVATE = false;
+      }
       // XXX Why do we need to check the message pos?
       if (!EventIsInsideWindow(popupWindow) &&
           GetPopupsToRollup(rollupListener, &popupsToRollup)) {
         break;
       }
       return false;
 
+    case MOZ_WM_REACTIVATE:
+      // The previous active window should take back focus.
+      if (::IsWindow(reinterpret_cast<HWND>(aLParam))) {
+        ::SetForegroundWindow(reinterpret_cast<HWND>(aLParam));
+      }
+      return true;
+
+    case WM_NCACTIVATE:
+      if (!aWParam && !sSendingNCACTIVATE &&
+          NeedsToHandleNCActivateDelayed(aWnd)) {
+        // Don't just consume WM_NCACTIVATE. It doesn't handle only the
+        // nonclient area state change.
+        ::DefWindowProcW(aWnd, aMessage, TRUE, aLParam);
+        // Accept the deactivating because it's necessary to receive following
+        // WM_ACTIVATE.
+        *aResult = TRUE;
+        sPendingNCACTIVATE = true;
+        return true;
+      }
+      return false;
+
     case WM_MOUSEACTIVATE:
       // XXX Why do we need to check the message pos?
       if (!EventIsInsideWindow(popupWindow) &&
           GetPopupsToRollup(rollupListener, &popupsToRollup)) {
         // WM_MOUSEACTIVATE may be caused by moving the mouse (e.g., X-mouse
         // of TweakUI is enabled. Then, check if the popup should be rolled up
         // with rollup listener. If not, just consume the message.
         if (HIWORD(aLParam) == WM_MOUSEMOVE &&
--- a/widget/windows/nsWindow.h
+++ b/widget/windows/nsWindow.h
@@ -416,16 +416,17 @@ protected:
   /**
    * Popup hooks
    */
   static void             ScheduleHookTimer(HWND aWnd, UINT aMsgId);
   static void             RegisterSpecialDropdownHooks();
   static void             UnregisterSpecialDropdownHooks();
   static bool             GetPopupsToRollup(nsIRollupListener* aRollupListener,
                                             uint32_t* aPopupsToRollup);
+  static bool             NeedsToHandleNCActivateDelayed(HWND aWnd);
   static bool             DealWithPopups(HWND inWnd, UINT inMsg, WPARAM inWParam, LPARAM inLParam, LRESULT* outResult);
 
   /**
    * Window transparency helpers
    */
 #ifdef MOZ_XUL
 private:
   void                    SetWindowTranslucencyInner(nsTransparencyMode aMode);
--- a/widget/windows/nsWindowDefs.h
+++ b/widget/windows/nsWindowDefs.h
@@ -28,20 +28,23 @@
 // Our internal message for WM_MOUSEWHEEL, WM_MOUSEHWHEEL, WM_VSCROLL and
 // WM_HSCROLL
 #define MOZ_WM_MOUSEVWHEEL                (WM_APP+0x0310)
 #define MOZ_WM_MOUSEHWHEEL                (WM_APP+0x0311)
 #define MOZ_WM_VSCROLL                    (WM_APP+0x0312)
 #define MOZ_WM_HSCROLL                    (WM_APP+0x0313)
 #define MOZ_WM_MOUSEWHEEL_FIRST           MOZ_WM_MOUSEVWHEEL
 #define MOZ_WM_MOUSEWHEEL_LAST            MOZ_WM_HSCROLL
+// If a popup window is being activated, we try to reactivate the previous
+// window with this message.
+#define MOZ_WM_REACTIVATE                 (WM_APP+0x0314)
 
 // Internal message for ensuring the file picker is visible on multi monitor
 // systems, and when the screen resolution changes.
-#define MOZ_WM_ENSUREVISIBLE              (WM_APP + 14159)
+#define MOZ_WM_ENSUREVISIBLE              (WM_APP+0x374F)
 
 #ifndef SM_CXPADDEDBORDER
 #define SM_CXPADDEDBORDER                 92
 #endif
 
 #ifndef WM_THEMECHANGED
 #define WM_THEMECHANGED                   0x031A
 #endif