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 f3e6249b..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 d02dede8..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);
+ }
+ }
}
}