Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(988)

Unified Diff: ui/views/widget/desktop_aura/x11_topmost_window_finder_unittest.cc

Issue 264713007: Add unittests for X11TopmostWindowFinder (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ui/views/widget/desktop_aura/x11_topmost_window_finder_unittest.cc
diff --git a/ui/views/widget/desktop_aura/x11_topmost_window_finder_unittest.cc b/ui/views/widget/desktop_aura/x11_topmost_window_finder_unittest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..c7f0e6af3db9a68e95f37d57acf9803530850b0c
--- /dev/null
+++ b/ui/views/widget/desktop_aura/x11_topmost_window_finder_unittest.cc
@@ -0,0 +1,444 @@
+#include "ui/views/widget/desktop_aura/x11_topmost_window_finder.h"
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/run_loop.h"
+#include "third_party/skia/include/core/SkRect.h"
+#include "third_party/skia/include/core/SkRegion.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_tree_host.h"
+#include "ui/events/platform/platform_event_dispatcher.h"
+#include "ui/events/platform/platform_event_source.h"
+#include "ui/gfx/path.h"
+#include "ui/gfx/x/x11_atom_cache.h"
+#include "ui/views/test/views_test_base.h"
+#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
+#include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
+#include "ui/views/widget/widget.h"
+
+#include <X11/extensions/shape.h>
+#include <X11/Xlib.h>
+#include <X11/Xregion.h>
+
+namespace views {
+
+namespace {
+
+// Blocks till the value of |property| on |window| changes.
+class PropertyChangeWaiter : public ui::PlatformEventDispatcher {
+ public:
+ PropertyChangeWaiter(XID window, const char* property);
+ virtual ~PropertyChangeWaiter();
+
+ // Blocks till the value of |property_| changes.
+ void Wait();
+
+ protected:
+ // Returns whether the run loop can exit.
+ virtual bool ShouldKeepOnWaiting(XEvent* event);
+
+ XID xwindow() const {
+ return x_window_;
+ }
+
+ private:
+ // ui::PlatformEventDispatcher:
+ virtual bool CanDispatchEvent(const ui::PlatformEvent& event) OVERRIDE;
+ virtual uint32_t DispatchEvent(const ui::PlatformEvent& event) OVERRIDE;
+
+ XID x_window_;
+ const char* property_;
+
+ // Whether Wait() should block.
+ bool wait_;
+
+ // Ends the run loop.
+ base::Closure quit_closure_;
+
+ // The event mask to be restored upon PropertyChangeWaiter's destruction.
+ long old_event_mask_;
+
+ scoped_ptr<ui::X11AtomCache> atom_cache_;
+
+ DISALLOW_COPY_AND_ASSIGN(PropertyChangeWaiter);
+};
+
+PropertyChangeWaiter::PropertyChangeWaiter(XID window, const char* property)
+ : x_window_(window),
+ property_(property),
+ wait_(true),
+ old_event_mask_(0) {
+ Display* display = gfx::GetXDisplay();
+
+ // Ensure that we are listening to PropertyNotify events for |window|. This
+ // is not the case for windows created via CreateAndShowXWindow().
+ XWindowAttributes attributes;
+ XGetWindowAttributes(display, x_window_, &attributes);
+ old_event_mask_ = attributes.your_event_mask;
+ XSelectInput(display, x_window_, old_event_mask_ | PropertyChangeMask);
+
+ const char* kAtomsToCache[] = { property, NULL };
+ atom_cache_.reset(new ui::X11AtomCache(display, kAtomsToCache));
+ ui::PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this);
+}
+
+PropertyChangeWaiter::~PropertyChangeWaiter() {
+ XSelectInput(gfx::GetXDisplay(), x_window_, old_event_mask_);
+ ui::PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this);
+}
+
+void PropertyChangeWaiter::Wait() {
+ if (!wait_)
+ return;
+
+ // PropertyChangeWaiter (or one of its subclasses) may be constructed after
+ // the property change has occurred.
+ if (!ShouldKeepOnWaiting(NULL)) {
+ wait_ = false;
+ return;
+ }
+
+ base::RunLoop run_loop;
+ quit_closure_ = run_loop.QuitClosure();
+ run_loop.Run();
+}
+
+bool PropertyChangeWaiter::ShouldKeepOnWaiting(XEvent* event) {
+ // Stop waiting once we get a property change.
+ return event != NULL;
+}
+
+bool PropertyChangeWaiter::CanDispatchEvent(const ui::PlatformEvent& event) {
+ return event->type == PropertyNotify &&
+ event->xproperty.window == x_window_ &&
+ event->xproperty.atom == atom_cache_->GetAtom(property_);
+}
+
+uint32_t PropertyChangeWaiter::DispatchEvent(const ui::PlatformEvent& event) {
+ if (wait_ && !ShouldKeepOnWaiting(event)) {
+ wait_ = false;
+ if (!quit_closure_.is_null())
+ quit_closure_.Run();
+ }
+ return ui::POST_DISPATCH_NONE;
+}
+
+// Waits till |window| is minimized.
+class MinimizeWaiter : public PropertyChangeWaiter {
+ public:
+ explicit MinimizeWaiter(XID window);
+ virtual ~MinimizeWaiter();
+
+ private:
+ // PropertyChangeWaiter:
+ virtual bool ShouldKeepOnWaiting(XEvent* event) OVERRIDE;
+
+ scoped_ptr<ui::X11AtomCache> atom_cache_;
+
+ DISALLOW_COPY_AND_ASSIGN(MinimizeWaiter);
+};
+
+MinimizeWaiter::MinimizeWaiter(XID window)
+ : PropertyChangeWaiter(window, "_NET_WM_STATE") {
+ const char* kAtomsToCache[] = { "_NET_WM_STATE_HIDDEN", NULL };
+ atom_cache_.reset(new ui::X11AtomCache(gfx::GetXDisplay(), kAtomsToCache));
+}
+
+MinimizeWaiter::~MinimizeWaiter() {
+}
+
+bool MinimizeWaiter::ShouldKeepOnWaiting(XEvent* event) {
+ std::vector<Atom> wm_states;
+ if (ui::GetAtomArrayProperty(xwindow(), "_NET_WM_STATE", &wm_states)) {
+ std::vector<Atom>::iterator it = std::find(
+ wm_states.begin(),
+ wm_states.end(),
+ atom_cache_->GetAtom("_NET_WM_STATE_HIDDEN"));
+ return it == wm_states.end();
+ }
+ return true;
+}
+
+// Waits till |_NET_CLIENT_LIST_STACKING| is updated to include
+// |expected_windows|.
+class StackingClientListWaiter : public PropertyChangeWaiter {
+ public:
+ StackingClientListWaiter(XID* expected_windows, size_t count);
+ virtual ~StackingClientListWaiter();
+
+ private:
+ // PropertyChangeWaiter:
+ virtual bool ShouldKeepOnWaiting(XEvent* event) OVERRIDE;
+
+ std::vector<XID> expected_windows_;
+
+ DISALLOW_COPY_AND_ASSIGN(StackingClientListWaiter);
+};
+
+StackingClientListWaiter::StackingClientListWaiter(XID* expected_windows,
+ size_t count)
+ : PropertyChangeWaiter(ui::GetX11RootWindow(),
+ "_NET_CLIENT_LIST_STACKING"),
+ expected_windows_(expected_windows, expected_windows + count) {
+}
+
+StackingClientListWaiter::~StackingClientListWaiter() {
+}
+
+bool StackingClientListWaiter::ShouldKeepOnWaiting(XEvent* event) {
+ std::vector<XID> stack;
+ ui::GetXWindowStack(ui::GetX11RootWindow(), &stack);
+ for (size_t i = 0; i < expected_windows_.size(); ++i) {
+ std::vector<XID>::iterator it = std::find(
+ stack.begin(), stack.end(), expected_windows_[i]);
+ if (it == stack.end())
+ return true;
+ }
+ return false;
+}
+
+} // namespace
+
+class X11TopmostWindowFinderTest : public ViewsTestBase {
+ public:
+ X11TopmostWindowFinderTest() {
+ }
+
+ virtual ~X11TopmostWindowFinderTest() {
+ }
+
+ // Creates and shows a Widget with |bounds|. The caller takes ownership of
+ // the returned widget.
+ Widget* CreateAndShowWidget(const gfx::Rect& bounds) WARN_UNUSED_RESULT {
+ Widget* toplevel = new Widget;
+ Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
+ params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.native_widget = new DesktopNativeWidgetAura(toplevel);
+ params.bounds = bounds;
+ params.remove_standard_frame = true;
+ toplevel->Init(params);
+ toplevel->Show();
+ return toplevel;
+ }
+
+ // Creates and shows an X window with |bounds|.
+ XID CreateAndShowXWindow(const gfx::Rect& bounds) {
+ XID root = DefaultRootWindow(xdisplay());
+ XID xid = XCreateSimpleWindow(xdisplay(),
+ root,
+ 1, 1, 1, 1,
+ 0, // border_width
+ 0, // border
+ 0); // background
+ ui::SetUseOSWindowFrame(xid, false);
+ ShowAndSetXIDBounds(xid, bounds);
+ return xid;
+ }
+
+ // Shows |xid| and sets its bounds.
+ void ShowAndSetXIDBounds(XID xid, const gfx::Rect& bounds) {
+ XMapWindow(xdisplay(), xid);
+
+ XWindowChanges changes = {0};
+ changes.x = bounds.x();
+ changes.y = bounds.y();
+ changes.width = bounds.width();
+ changes.height = bounds.height();
+ XConfigureWindow(xdisplay(),
+ xid,
+ CWX | CWY | CWWidth | CWHeight,
+ &changes);
+ }
+
+ // Returns the atom associated with |name|.
+ Atom GetAtom(const char* name) {
+ return XInternAtom(gfx::GetXDisplay(), name, false);
+ }
+
+ Display* xdisplay() {
+ return gfx::GetXDisplay();
+ }
+
+ // Returns the topmost X window at the passed in screen position.
+ XID FindTopmostXWindowAt(int screen_x, int screen_y) {
+ X11TopmostWindowFinder finder;
+ return finder.FindWindowAt(gfx::Point(screen_x, screen_y));
+ }
+
+ // Returns the topmost aura::Window at the passed in screen position. Returns
+ // NULL if the topmost window does not have an associated aura::Window.
+ aura::Window* FindTopmostLocalProcessWindowAt(int screen_x, int screen_y) {
+ X11TopmostWindowFinder finder;
+ return finder.FindLocalProcessWindowAt(gfx::Point(screen_x, screen_y),
+ std::set<aura::Window*>());
+ }
+
+ // Returns the topmost aura::Window at the passed in screen position ignoring
+ // |ignore_window|. Returns NULL if the topmost window does not have an
+ // associated aura::Window.
+ aura::Window* FindTopmostLocalProcessWindowWithIgnore(
+ int screen_x,
+ int screen_y,
+ aura::Window* ignore_window) {
+ std::set<aura::Window*> ignore;
+ ignore.insert(ignore_window);
+ X11TopmostWindowFinder finder;
+ return finder.FindLocalProcessWindowAt(gfx::Point(screen_x, screen_y),
+ ignore);
+ }
+
+ virtual void SetUp() OVERRIDE {
+ // Make X11 synchronous for our display connection. This does force the
+ // window manager to behave synchronously.
+ XSynchronize(xdisplay(), True);
+ ViewsTestBase::SetUp();
+
+ // Ensure that the X11DesktopHandler exists. The X11DesktopHandler is
+ // necessary to properly track menu windows.
+ X11DesktopHandler::get();
+ }
+
+ virtual void TearDown() OVERRIDE {
+ ViewsTestBase::TearDown();
+ XSynchronize(xdisplay(), False);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(X11TopmostWindowFinderTest);
+};
+
+TEST_F(X11TopmostWindowFinderTest, Basic) {
+ // Avoid positioning test windows at 0x0 because window managers often have a
+ // panel/launcher along one of the screen edges and do not allow windows to
+ // position themselves to overlap the panel/launcher.
+ scoped_ptr<Widget> widget1(
+ CreateAndShowWidget(gfx::Rect(100, 100, 200, 100)));
+ aura::Window* window1 = widget1->GetNativeWindow();
+ XID xid1 = window1->GetHost()->GetAcceleratedWidget();
+
+ XID xid2 = CreateAndShowXWindow(gfx::Rect(200, 100, 100, 200));
+
+ scoped_ptr<Widget> widget3(
+ CreateAndShowWidget(gfx::Rect(100, 190, 200, 110)));
+ aura::Window* window3 = widget3->GetNativeWindow();
+ XID xid3 = window3->GetHost()->GetAcceleratedWidget();
+
+ XID xids[] = { xid1, xid2, xid3 };
+ StackingClientListWaiter waiter(xids, arraysize(xids));
+ waiter.Wait();
+
+ EXPECT_EQ(xid1, FindTopmostXWindowAt(150, 150));
+ EXPECT_EQ(window1, FindTopmostLocalProcessWindowAt(150, 150));
+
+ EXPECT_EQ(xid2, FindTopmostXWindowAt(250, 150));
+ EXPECT_EQ(NULL, FindTopmostLocalProcessWindowAt(250, 150));
+
+ EXPECT_EQ(xid3, FindTopmostXWindowAt(250, 250));
+ EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(250, 250));
+
+ EXPECT_EQ(xid3, FindTopmostXWindowAt(150, 250));
+ EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(150, 250));
+
+ EXPECT_EQ(xid3, FindTopmostXWindowAt(150, 195));
+ EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(150, 195));
+
+ EXPECT_NE(xid1, FindTopmostXWindowAt(1000, 1000));
+ EXPECT_NE(xid2, FindTopmostXWindowAt(1000, 1000));
+ EXPECT_EQ(NULL, FindTopmostLocalProcessWindowAt(1000, 1000));
+
+ EXPECT_EQ(window1,
+ FindTopmostLocalProcessWindowWithIgnore(150, 150, window3));
+ EXPECT_EQ(NULL,
+ FindTopmostLocalProcessWindowWithIgnore(250, 250, window3));
+ EXPECT_EQ(NULL,
+ FindTopmostLocalProcessWindowWithIgnore(150, 250, window3));
+ EXPECT_EQ(window1,
+ FindTopmostLocalProcessWindowWithIgnore(150, 195, window3));
+
+ XDestroyWindow(xdisplay(), xid2);
+}
+
+// Test that the minimized state is properly handled.
+TEST_F(X11TopmostWindowFinderTest, Minimized) {
+ XID xid = CreateAndShowXWindow(gfx::Rect(100, 100, 100, 100));
+
+ XID xids[] = { xid };
+ StackingClientListWaiter stack_waiter(xids, arraysize(xids));
+ stack_waiter.Wait();
+
+ EXPECT_EQ(xid, FindTopmostXWindowAt(150, 150));
+ {
+ MinimizeWaiter minimize_waiter(xid);
+ XIconifyWindow(xdisplay(), xid, 0);
+ minimize_waiter.Wait();
+ }
+ EXPECT_NE(xid, FindTopmostXWindowAt(150, 150));
+
+ XDestroyWindow(xdisplay(), xid);
+}
+
+// Test that non-rectangular windows are properly handled.
+TEST_F(X11TopmostWindowFinderTest, NonRectangular) {
+ if (!ui::IsShapeExtensionAvailable())
+ return;
+
+ scoped_ptr<Widget> widget(
+ CreateAndShowWidget(gfx::Rect(100, 100, 100, 100)));
+ XID xid = widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
+
+ SkRegion* region = new SkRegion;
+ region->op(SkIRect::MakeXYWH(0, 10, 10, 90), SkRegion::kUnion_Op);
+ region->op(SkIRect::MakeXYWH(10, 0, 90, 100), SkRegion::kUnion_Op);
+ // Widget takes ownership of |region|.
+ widget->SetShape(region);
+
+ XID xids[] = { xid };
+ StackingClientListWaiter stack_waiter(xids, arraysize(xids));
+ stack_waiter.Wait();
+
+ EXPECT_EQ(xid, FindTopmostXWindowAt(120, 120));
+ EXPECT_NE(xid, FindTopmostXWindowAt(105, 105));
+}
+
+// Test that the TopmostWindowFinder finds windows which belong to menus
+// (which may or may not belong to Chrome).
+TEST_F(X11TopmostWindowFinderTest, Menu) {
+ XID xid = CreateAndShowXWindow(gfx::Rect(100, 100, 100, 100));
+
+ XID root = DefaultRootWindow(xdisplay());
+ XSetWindowAttributes swa;
+ swa.override_redirect = True;
+ XID menu_xid = XCreateWindow(xdisplay(),
+ root,
+ 0, 0, 1, 1,
+ 0, // border width
+ CopyFromParent, // depth
+ InputOutput,
+ CopyFromParent, // visual
+ CWOverrideRedirect,
+ &swa);
+ {
+ PropertyChangeWaiter property_waiter(menu_xid, "_NET_WM_WINDOW_TYPE");
+ ui::SetAtomProperty(menu_xid,
+ "_NET_WM_WINDOW_TYPE",
+ "ATOM",
+ GetAtom("_NET_WM_WINDOW_TYPE_MENU"));
+ property_waiter.Wait();
+ }
+ ui::SetUseOSWindowFrame(menu_xid, false);
+ ShowAndSetXIDBounds(menu_xid, gfx::Rect(140, 110, 100, 100));
+
+ // |menu_xid| is never added to _NET_CLIENT_LIST_STACKING.
+ XID xids[] = { xid };
+ StackingClientListWaiter stack_waiter(xids, arraysize(xids));
+ stack_waiter.Wait();
+
+ EXPECT_EQ(menu_xid, FindTopmostXWindowAt(150, 120));
+ EXPECT_EQ(menu_xid, FindTopmostXWindowAt(210, 120));
+
+ XDestroyWindow(xdisplay(), xid);
+ XDestroyWindow(xdisplay(), menu_xid);
+}
+
+} // namespace views

Powered by Google App Engine
This is Rietveld 408576698