| 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
|
|
|