wm: Focus newest-mapped modal window.

Update the window manager so that when a browser has
multiple modal transient windows, the most-recently-mapped
one gets the focus and is raised above its siblings.

BUG=chromium-os:17642
TEST=added; manual testing suggests that there may be another issue in chrome

Change-Id: I1cbe1dff6c590a25aa0c134be5748e44ff7d98dd
Reviewed-on: http://gerrit.chromium.org/gerrit/4551
Reviewed-by: James Cook <jamescook@chromium.org>
Tested-by: Daniel Erat <derat@chromium.org>
diff --git a/layout2/browser_window.cc b/layout2/browser_window.cc
index 95fba27..5c0698c 100644
--- a/layout2/browser_window.cc
+++ b/layout2/browser_window.cc
@@ -168,6 +168,10 @@
   transients_->SetPreferredWindowToFocus(transient_win);
 }
 
+void BrowserWindow::HandleTransientWindowModalityChange(Window* transient_win) {
+  transients_->HandleWindowModalityChange(transient_win);
+}
+
 void BrowserWindow::SetTransientWindowVisibility(bool shown) {
   if (shown)
     transients_->Show();
diff --git a/layout2/browser_window.h b/layout2/browser_window.h
index 1a08b62..5bf28d9 100644
--- a/layout2/browser_window.h
+++ b/layout2/browser_window.h
@@ -82,9 +82,13 @@
 
   // Specify that a previously-registered transient window should get the input
   // focus the next time that TakeFocus() is called.  NULL can be passed to make
-  // the browser window itself get the focus.
+  // the browser window itself get the focus (assuming that no modal transients
+  // are mapped).
   void SetPreferredTransientWindowToFocus(Window* transient_win);
 
+  // Handle one of this browser's transient windows' modality changing.
+  void HandleTransientWindowModalityChange(Window* transient_win);
+
   // Show or hide all of this browser's transient windows.
   void SetTransientWindowVisibility(bool shown);
 
diff --git a/layout2/layout_manager2.cc b/layout2/layout_manager2.cc
index 26da27b..e27f81c 100644
--- a/layout2/layout_manager2.cc
+++ b/layout2/layout_manager2.cc
@@ -1447,7 +1447,8 @@
   new_state[wm_->GetXAtom(ATOM_NET_WM_STATE_MODAL)] = modal;
   win->ChangeWmState(new_state);
 
-  if (modal)
+  owning_browser->HandleTransientWindowModalityChange(win);
+  if (modal || owning_browser == GetActiveBrowserWindow())
     FocusBrowserWindow(owning_browser);
 }
 
diff --git a/layout2/layout_manager2_test.cc b/layout2/layout_manager2_test.cc
index c2b8f8d..8f06252 100644
--- a/layout2/layout_manager2_test.cc
+++ b/layout2/layout_manager2_test.cc
@@ -380,13 +380,29 @@
   SendKey(xconn_->GetRootWindow(), kAltTabKeyCombo, kEventTime, kEventTime);
   EXPECT_EQ(transient_xid2, xconn_->focused_xid());
 
-  // Send an event asking for |transient_xid2| to be made non-modal.
-  xconn_->InitClientMessageEvent(
-      &event, transient_xid2, kWmStateAtom, 0, kWmStateModalAtom, 0, 0, 0);
-  wm_->HandleEvent(&event);
-  xconn_->InitPropertyNotifyEvent(&event, transient_xid2, kWmStateAtom);
-  wm_->HandleEvent(&event);
+  // Send an event asking for |transient_xid3| to be made modal.  It should be
+  // focused and raised on top of |transient_xid2| (see
+  // http://crosbug.com/17642).
+  SendWmStateMessage(transient_xid3, kWmStateModalAtom, true);
+  EXPECT_EQ(transient_xid3, xconn_->focused_xid());
+  EXPECT_TRUE(
+      TestWindowIsAboveOtherWindow(wm_->GetWindowOrDie(transient_xid3),
+                                   wm_->GetWindowOrDie(transient_xid2)));
+
+  // Send an event asking for |transient_xid3| to be made non-modal.
+  // Since |transient_xid2| is still modal, it should now be focused and on top.
+  SendWmStateMessage(transient_xid3, kWmStateModalAtom, false);
   EXPECT_EQ(transient_xid2, xconn_->focused_xid());
+  EXPECT_TRUE(
+      TestWindowIsAboveOtherWindow(wm_->GetWindowOrDie(transient_xid2),
+                                   wm_->GetWindowOrDie(transient_xid3)));
+
+  // Now make |transient_xid2| non-modal as well.
+  SendWmStateMessage(transient_xid2, kWmStateModalAtom, false);
+  EXPECT_EQ(transient_xid2, xconn_->focused_xid());
+  EXPECT_TRUE(
+      TestWindowIsAboveOtherWindow(wm_->GetWindowOrDie(transient_xid2),
+                                   wm_->GetWindowOrDie(transient_xid3)));
 
   // Click on |xid2| and check that it takes the focus.
   xconn_->set_pointer_grab_xid(xid2);
@@ -408,11 +424,7 @@
 
   // Send a message asking for |transient_xid| to be made modal.
   // We should focus it and automatically switch to |xid|.
-  xconn_->InitClientMessageEvent(
-      &event, transient_xid, kWmStateAtom, 1, kWmStateModalAtom, 0, 0, 0);
-  wm_->HandleEvent(&event);
-  xconn_->InitPropertyNotifyEvent(&event, transient_xid, kWmStateAtom);
-  wm_->HandleEvent(&event);
+  SendWmStateMessage(transient_xid, kWmStateModalAtom, true);
   EXPECT_EQ(transient_xid, xconn_->focused_xid());
   EXPECT_EQ(xconn_->root_bounds(), xconn_->GetWindowInfoOrDie(xid)->bounds);
   EXPECT_EQ(xconn_->root_bounds(), GetCompositedWindowBounds(xid));
@@ -478,10 +490,7 @@
 
   // Request that the window be made fullscreen.  Check that the fullscreen hint
   // is set on it and that it's restacked.
-  XEvent event;
-  xconn_->InitClientMessageEvent(
-      &event, xid, kWmStateAtom, 1, kWmStateFullscreenAtom, 0, 0, 0);
-  wm_->HandleEvent(&event);
+  SendWmStateMessage(xid, kWmStateFullscreenAtom, true);
   EXPECT_EQ(xid, xconn_->focused_xid());
   EXPECT_EQ(wm_->root_bounds(), info->bounds);
   EXPECT_EQ(wm_->root_bounds(), GetCompositedWindowBounds(xid));
@@ -522,17 +531,13 @@
       IntArrayPropertyContains(xid3, kWmStateAtom, kWmStateFullscreenAtom));
 
   // Send a message to make the first window fullscreen again...
-  xconn_->InitClientMessageEvent(
-      &event, xid, kWmStateAtom, 1, kWmStateFullscreenAtom, 0, 0, 0);
-  wm_->HandleEvent(&event);
+  SendWmStateMessage(xid, kWmStateFullscreenAtom, true);
   EXPECT_TRUE(WindowIsInLayer(win, StackingManager::LAYER_FULLSCREEN_WINDOW));
   EXPECT_TRUE(
       IntArrayPropertyContains(xid, kWmStateAtom, kWmStateFullscreenAtom));
 
   // ... and then send a message to make it not fullscreen.
-  xconn_->InitClientMessageEvent(
-      &event, xid, kWmStateAtom, 0, kWmStateFullscreenAtom, 0, 0, 0);
-  wm_->HandleEvent(&event);
+  SendWmStateMessage(xid, kWmStateFullscreenAtom, false);
   EXPECT_TRUE(
       WindowIsInLayer(win, StackingManager::LAYER_ACTIVE_BROWSER_WINDOW));
   EXPECT_FALSE(
@@ -540,18 +545,15 @@
 
   // Creating a panel (which will take the focus) should also unfullscreen the
   // browser.
-  xconn_->InitClientMessageEvent(
-      &event, xid, kWmStateAtom, 1, kWmStateFullscreenAtom, 0, 0, 0);
-  wm_->HandleEvent(&event);
+  SendWmStateMessage(xid, kWmStateFullscreenAtom, true);
   Panel* panel = CreatePanel(200, 20, 400);
   ASSERT_EQ(panel->content_xid(), xconn_->focused_xid());
   EXPECT_FALSE(
       IntArrayPropertyContains(xid, kWmStateAtom, kWmStateFullscreenAtom));
 
   // Check that we don't crash when a fullscreen window is unmapped.
-  xconn_->InitClientMessageEvent(
-      &event, xid, kWmStateAtom, 1, kWmStateFullscreenAtom, 0, 0, 0);
-  wm_->HandleEvent(&event);
+  SendWmStateMessage(xid, kWmStateFullscreenAtom, true);
+  XEvent event;
   xconn_->InitUnmapEvent(&event, xid);
   wm_->HandleEvent(&event);
 }
diff --git a/test_lib.cc b/test_lib.cc
index 8b275f5..cb1555c 100644
--- a/test_lib.cc
+++ b/test_lib.cc
@@ -401,6 +401,20 @@
   wm_->HandleEvent(&event);
 }
 
+void BasicWindowManagerTest::SendWmStateMessage(XWindow xid,
+                                                XAtom state_atom,
+                                                bool set) {
+  const XAtom prop_atom = xconn_->GetAtomOrDie("_NET_WM_STATE");
+  XEvent event;
+  xconn_->InitClientMessageEvent(
+      &event, xid, prop_atom, set, state_atom, 0, 0, 0);
+  wm_->HandleEvent(&event);
+  // Assuming that the window manager changed the property as requested, tell it
+  // to take another look.
+  xconn_->InitPropertyNotifyEvent(&event, xid, prop_atom);
+  wm_->HandleEvent(&event);
+}
+
 void BasicWindowManagerTest::SendConfigureNotifyEvent(XWindow xid) {
   XEvent event;
   xconn_->InitConfigureNotifyEvent(&event, xid);
diff --git a/test_lib.h b/test_lib.h
index 0e62feb..48abead 100644
--- a/test_lib.h
+++ b/test_lib.h
@@ -177,6 +177,10 @@
   // window.
   void SendActiveWindowMessage(XWindow xid);
 
+  // Send a message to the window manager asking it to set or unset |state_atom|
+  // (e.g. _NET_WM_STATE_MODAL) in |xid|'s _NET_WM_STATE property.
+  void SendWmStateMessage(XWindow xid, XAtom state_atom, bool set);
+
   // Initialize a ConfigureNotify event for |xid|'s current bounds and stacking
   // and pass it to WindowManager::HandleEvent().
   void SendConfigureNotifyEvent(XWindow xid);
diff --git a/transient_window_collection.cc b/transient_window_collection.cc
index b51a5e9..bd01316 100644
--- a/transient_window_collection.cc
+++ b/transient_window_collection.cc
@@ -180,6 +180,23 @@
   }
 }
 
+void TransientWindowCollection::HandleWindowModalityChange(
+    Window* transient_win) {
+  if (transient_win->wm_state_modal()) {
+    // If this window just became modal, focus it.
+    transient_to_focus_ = GetTransientWindow(*transient_win);
+  } else {
+    // Otherwise, if it's not modal but we were previously preferring it, make
+    // sure that there aren't any other transient windows that we should focus
+    // instead.
+    if (transient_to_focus_ && transient_to_focus_->win == transient_win) {
+      TransientWindow* preferred_transient = FindTransientWindowToFocus();
+      if (preferred_transient && preferred_transient->win->wm_state_modal())
+        transient_to_focus_ = preferred_transient;
+    }
+  }
+}
+
 void TransientWindowCollection::ConfigureAllWindowsRelativeToOwner(
     int anim_ms) {
   for (TransientWindowMap::iterator it = transients_.begin();
diff --git a/transient_window_collection.h b/transient_window_collection.h
index a46b67e..8eb7844 100644
--- a/transient_window_collection.h
+++ b/transient_window_collection.h
@@ -97,6 +97,12 @@
   // This should be called in response to the window being unmapped.
   void RemoveWindow(Window* transient_win);
 
+  // Handle a change in |transient_win|'s modality.
+  // If it's modal, then we set it as the preferred window to focus; if it's
+  // non-modal, then we check if there's another still-modal window that we
+  // should be focusing instead.
+  void HandleWindowModalityChange(Window* transient_win);
+
   // Update all transient windows' positions and scales based on the owner
   // window's position and scale.
   void ConfigureAllWindowsRelativeToOwner(int anim_ms);