Make File-Picker modal on Linux

Chromium for Linux opens a Gtk file-picker for file browsing, but it is not
modal because there is no GtkWindow as parent of file-picker.

This patch allows the X11 host window to disable input event handling to make
a file-picker modal

The original CL was reverted due to wrong DCHECK with modal_dialog_xid_:
https://codereview.chromium.org/1243503002

BUG=408481
TEST=BrowserSelectFileDialogTest, ModalTest

Review URL: https://codereview.chromium.org/1233913009

Cr-Commit-Position: refs/heads/master@{#360961}
diff --git a/chrome/browser/ui/libgtk2ui/select_file_dialog_impl_gtk2.cc b/chrome/browser/ui/libgtk2ui/select_file_dialog_impl_gtk2.cc
index f3e6249..377bbd2a 100644
--- a/chrome/browser/ui/libgtk2ui/select_file_dialog_impl_gtk2.cc
+++ b/chrome/browser/ui/libgtk2ui/select_file_dialog_impl_gtk2.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/libgtk2ui/select_file_dialog_impl_gtk2.h"
 
+#include <gdk/gdkx.h>
 #include <gtk/gtk.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -31,6 +32,7 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/shell_dialogs/select_file_dialog.h"
 #include "ui/strings/grit/ui_strings.h"
+#include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h"
 #include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
 
 namespace {
@@ -163,9 +165,11 @@
 
   params_map_[dialog] = params;
 
-  // TODO(erg): Figure out how to fake GTK window-to-parent modality without
-  // having the parent be a real GtkWindow.
-  gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+  // Disable input events handling in the host window to make this dialog modal.
+  views::DesktopWindowTreeHostX11* host =
+      views::DesktopWindowTreeHostX11::GetHostForXID(
+      owning_window->GetHost()->GetAcceleratedWidget());
+  host->DisableEventListening(GDK_WINDOW_XID(gtk_widget_get_window(dialog)));
 
   gtk_widget_show_all(dialog);
 
@@ -424,6 +428,12 @@
   aura::Window* parent = GetAuraTransientParent(dialog);
   if (!parent)
     return;
+
+  views::DesktopWindowTreeHostX11* host =
+      views::DesktopWindowTreeHostX11::GetHostForXID(
+      parent->GetHost()->GetAcceleratedWidget());
+  host->EnableEventListening();
+
   std::set<aura::Window*>::iterator iter = parents_.find(parent);
   if (iter != parents_.end()) {
     (*iter)->RemoveObserver(this);
diff --git a/chrome/browser/ui/libgtk2ui/select_file_dialog_interactive_uitest.cc b/chrome/browser/ui/libgtk2ui/select_file_dialog_interactive_uitest.cc
index 657731f..8078d4f 100644
--- a/chrome/browser/ui/libgtk2ui/select_file_dialog_interactive_uitest.cc
+++ b/chrome/browser/ui/libgtk2ui/select_file_dialog_interactive_uitest.cc
@@ -100,3 +100,47 @@
 };
 
 }  // namespace libgtk2ui
+
+// Test that the file-picker is modal.
+IN_PROC_BROWSER_TEST_F(BrowserSelectFileDialogTest, ModalTest) {
+  // Bring the native window to the foreground. Returns true on success.
+  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
+  ASSERT_TRUE(browser()->window()->IsActive());
+
+  // Leaks in GtkFileChooserDialog. http://crbug.com/537468
+  ANNOTATE_SCOPED_MEMORY_LEAK;
+  libgtk2ui::FilePicker file_picker(browser()->window());
+
+  gfx::NativeWindow window = browser()->window()->GetNativeWindow();
+  views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
+  ASSERT_NE(nullptr, widget);
+
+  // Run a nested loop until the browser window becomes inactive
+  // so that the file-picker can be active.
+  WidgetActivationWaiter waiter_inactive(widget, false);
+  waiter_inactive.Wait();
+  EXPECT_FALSE(browser()->window()->IsActive());
+
+  ui_test_utils::ClickOnView(browser(), VIEW_ID_TAB_CONTAINER);
+  // The window should not get focus due to modal dialog.
+  EXPECT_FALSE(browser()->window()->IsActive());
+
+  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
+  EXPECT_FALSE(browser()->window()->IsActive());
+
+  ASSERT_TRUE(ui_test_utils::ShowAndFocusNativeWindow(window));
+  EXPECT_FALSE(browser()->window()->IsActive());
+
+  ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
+      browser(), ui::VKEY_TAB, false, false, true, false));
+  EXPECT_FALSE(browser()->window()->IsActive());
+
+  file_picker.Close();
+
+  // Run a nested loop until the browser window becomes active.
+  WidgetActivationWaiter wait_active(widget, true);
+  wait_active.Wait();
+
+  ui_test_utils::ClickOnView(browser(), VIEW_ID_TAB_CONTAINER);
+  EXPECT_TRUE(browser()->window()->IsActive());
+}
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
index d02dede..b717445 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
@@ -29,6 +29,7 @@
 #include "ui/events/devices/x11/device_list_cache_x11.h"
 #include "ui/events/devices/x11/touch_factory_x11.h"
 #include "ui/events/event_utils.h"
+#include "ui/events/null_event_targeter.h"
 #include "ui/events/platform/platform_event_source.h"
 #include "ui/events/platform/x11/x11_event_source.h"
 #include "ui/gfx/display.h"
@@ -173,6 +174,7 @@
       custom_window_shape_(false),
       urgency_hint_set_(false),
       activatable_(true),
+      modal_dialog_xid_(0),
       close_widget_factory_(this) {
 }
 
@@ -1529,7 +1531,8 @@
 }
 
 void DesktopWindowTreeHostX11::DispatchKeyEvent(ui::KeyEvent* event) {
-  GetInputMethod()->DispatchKeyEvent(event);
+  if (native_widget_delegate_->AsWidget()->IsActive())
+    GetInputMethod()->DispatchKeyEvent(event);
 }
 
 void DesktopWindowTreeHostX11::ConvertEventToDifferentHost(
@@ -2049,6 +2052,26 @@
   return gfx::ToEnclosingRect(rect_in_pixels);
 }
 
+const XID DesktopWindowTreeHostX11::GetModalDialog() {
+  return modal_dialog_xid_;
+}
+
+void DesktopWindowTreeHostX11::DisableEventListening(XID dialog) {
+  DCHECK(dialog);
+  DCHECK(!modal_dialog_xid_);
+  modal_dialog_xid_ = dialog;
+  // ScopedWindowTargeter is used to temporarily replace the event-targeter
+  // with NullEventTargeter to make |dialog| modal.
+  targeter_for_modal_.reset(new aura::ScopedWindowTargeter(window(),
+      scoped_ptr<ui::EventTargeter>(new ui::NullEventTargeter)));
+}
+
+void DesktopWindowTreeHostX11::EnableEventListening() {
+  DCHECK(modal_dialog_xid_);
+  modal_dialog_xid_ = 0;
+  targeter_for_modal_.reset();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // DesktopWindowTreeHost, public:
 
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h
index 09b5993..20399df 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h
@@ -13,6 +13,7 @@
 #include "base/cancelable_callback.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
+#include "ui/aura/scoped_window_targeter.h"
 #include "ui/aura/window_tree_host.h"
 #include "ui/base/cursor/cursor_loader_x11.h"
 #include "ui/events/platform/platform_event_dispatcher.h"
@@ -85,6 +86,16 @@
   // internal list of open windows.
   static void CleanUpWindowList(void (*func)(aura::Window* window));
 
+  // Disables event listening to make |dialog| modal.
+  void DisableEventListening(XID dialog);
+
+  // Enables event listening after closing |dialog|.
+  void EnableEventListening();
+
+  // Returns XID of dialog currently displayed. When it returns 0,
+  // there is no dialog on the host window.
+  const XID GetModalDialog();
+
  protected:
   // Overridden from DesktopWindowTreeHost:
   void Init(aura::Window* content_window,
@@ -351,6 +362,10 @@
 
   base::CancelableCallback<void()> delayed_resize_task_;
 
+  scoped_ptr<aura::ScopedWindowTargeter> targeter_for_modal_;
+
+  XID modal_dialog_xid_;
+
   base::WeakPtrFactory<DesktopWindowTreeHostX11> close_widget_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(DesktopWindowTreeHostX11);
diff --git a/ui/views/widget/desktop_aura/x11_desktop_handler.cc b/ui/views/widget/desktop_aura/x11_desktop_handler.cc
index 5ab84f9..9b20295 100644
--- a/ui/views/widget/desktop_aura/x11_desktop_handler.cc
+++ b/ui/views/widget/desktop_aura/x11_desktop_handler.cc
@@ -228,8 +228,15 @@
   if (active_state == ACTIVE) {
     DesktopWindowTreeHostX11* new_host =
         views::DesktopWindowTreeHostX11::GetHostForXID(xid);
-    if (new_host)
-      new_host->HandleNativeWidgetActivationChanged(true);
+    if (new_host) {
+      // Set focus to the modal dialog instead of the host window.
+      if (new_host->GetModalDialog()) {
+        XSetInputFocus(xdisplay_, new_host->GetModalDialog(),
+            RevertToParent, CurrentTime);
+      } else {
+        new_host->HandleNativeWidgetActivationChanged(true);
+      }
+    }
   }
 }