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

Side by Side 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, 7 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/views/widget/desktop_aura/x11_topmost_window_finder.h"
6
7 #include <vector>
8
9 #include "base/memory/scoped_ptr.h"
10 #include "base/run_loop.h"
11 #include "third_party/skia/include/core/SkRect.h"
12 #include "third_party/skia/include/core/SkRegion.h"
13 #include "ui/aura/window.h"
14 #include "ui/aura/window_tree_host.h"
15 #include "ui/events/platform/platform_event_dispatcher.h"
16 #include "ui/events/platform/platform_event_source.h"
17 #include "ui/gfx/path.h"
18 #include "ui/gfx/x/x11_atom_cache.h"
19 #include "ui/views/test/views_test_base.h"
20 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
21 #include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
22 #include "ui/views/widget/widget.h"
23
24 #include <X11/extensions/shape.h>
25 #include <X11/Xlib.h>
26 #include <X11/Xregion.h>
27
28 namespace views {
29
30 namespace {
31
32 // Blocks till the value of |property| on |window| changes.
33 class PropertyChangeWaiter : public ui::PlatformEventDispatcher {
34 public:
35 PropertyChangeWaiter(XID window, const char* property);
36 virtual ~PropertyChangeWaiter();
37
38 // Blocks till the value of |property_| changes.
39 void Wait();
40
41 protected:
42 // Returns whether the run loop can exit.
43 virtual bool ShouldKeepOnWaiting(XEvent* event);
44
45 XID xwindow() const {
46 return x_window_;
47 }
48
49 private:
50 // ui::PlatformEventDispatcher:
51 virtual bool CanDispatchEvent(const ui::PlatformEvent& event) OVERRIDE;
52 virtual uint32_t DispatchEvent(const ui::PlatformEvent& event) OVERRIDE;
53
54 XID x_window_;
55 const char* property_;
56
57 // Whether Wait() should block.
58 bool wait_;
59
60 // Ends the run loop.
61 base::Closure quit_closure_;
62
63 // The event mask to be restored upon PropertyChangeWaiter's destruction.
64 long old_event_mask_;
65
66 scoped_ptr<ui::X11AtomCache> atom_cache_;
67
68 DISALLOW_COPY_AND_ASSIGN(PropertyChangeWaiter);
69 };
70
71 PropertyChangeWaiter::PropertyChangeWaiter(XID window, const char* property)
72 : x_window_(window),
73 property_(property),
74 wait_(true),
75 old_event_mask_(0) {
76 Display* display = gfx::GetXDisplay();
77
78 // Ensure that we are listening to PropertyNotify events for |window|. This
79 // is not the case for windows created via CreateAndShowXWindow().
80 XWindowAttributes attributes;
81 XGetWindowAttributes(display, x_window_, &attributes);
82 old_event_mask_ = attributes.your_event_mask;
83 XSelectInput(display, x_window_, old_event_mask_ | PropertyChangeMask);
84
85 const char* kAtomsToCache[] = { property, NULL };
86 atom_cache_.reset(new ui::X11AtomCache(display, kAtomsToCache));
87 ui::PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this);
88 }
89
90 PropertyChangeWaiter::~PropertyChangeWaiter() {
91 XSelectInput(gfx::GetXDisplay(), x_window_, old_event_mask_);
92 ui::PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this);
93 }
94
95 void PropertyChangeWaiter::Wait() {
96 if (!wait_)
97 return;
98
99 // PropertyChangeWaiter (or one of its subclasses) may be constructed after
100 // the property change has occurred.
101 if (!ShouldKeepOnWaiting(NULL)) {
102 wait_ = false;
103 return;
104 }
105
106 base::RunLoop run_loop;
107 quit_closure_ = run_loop.QuitClosure();
108 run_loop.Run();
109 }
110
111 bool PropertyChangeWaiter::ShouldKeepOnWaiting(XEvent* event) {
112 // Stop waiting once we get a property change.
113 return event != NULL;
114 }
115
116 bool PropertyChangeWaiter::CanDispatchEvent(const ui::PlatformEvent& event) {
117 return event->type == PropertyNotify &&
118 event->xproperty.window == x_window_ &&
119 event->xproperty.atom == atom_cache_->GetAtom(property_);
120 }
121
122 uint32_t PropertyChangeWaiter::DispatchEvent(const ui::PlatformEvent& event) {
123 if (wait_ && !ShouldKeepOnWaiting(event)) {
124 wait_ = false;
125 if (!quit_closure_.is_null())
126 quit_closure_.Run();
127 }
128 return ui::POST_DISPATCH_NONE;
129 }
130
131 // Waits till |window| is minimized.
132 class MinimizeWaiter : public PropertyChangeWaiter {
133 public:
134 explicit MinimizeWaiter(XID window);
135 virtual ~MinimizeWaiter();
136
137 private:
138 // PropertyChangeWaiter:
139 virtual bool ShouldKeepOnWaiting(XEvent* event) OVERRIDE;
140
141 scoped_ptr<ui::X11AtomCache> atom_cache_;
142
143 DISALLOW_COPY_AND_ASSIGN(MinimizeWaiter);
144 };
145
146 MinimizeWaiter::MinimizeWaiter(XID window)
147 : PropertyChangeWaiter(window, "_NET_WM_STATE") {
148 const char* kAtomsToCache[] = { "_NET_WM_STATE_HIDDEN", NULL };
149 atom_cache_.reset(new ui::X11AtomCache(gfx::GetXDisplay(), kAtomsToCache));
150 }
151
152 MinimizeWaiter::~MinimizeWaiter() {
153 }
154
155 bool MinimizeWaiter::ShouldKeepOnWaiting(XEvent* event) {
156 std::vector<Atom> wm_states;
157 if (ui::GetAtomArrayProperty(xwindow(), "_NET_WM_STATE", &wm_states)) {
158 std::vector<Atom>::iterator it = std::find(
159 wm_states.begin(),
160 wm_states.end(),
161 atom_cache_->GetAtom("_NET_WM_STATE_HIDDEN"));
162 return it == wm_states.end();
163 }
164 return true;
165 }
166
167 // Waits till |_NET_CLIENT_LIST_STACKING| is updated to include
168 // |expected_windows|.
169 class StackingClientListWaiter : public PropertyChangeWaiter {
170 public:
171 StackingClientListWaiter(XID* expected_windows, size_t count);
172 virtual ~StackingClientListWaiter();
173
174 private:
175 // PropertyChangeWaiter:
176 virtual bool ShouldKeepOnWaiting(XEvent* event) OVERRIDE;
177
178 std::vector<XID> expected_windows_;
179
180 DISALLOW_COPY_AND_ASSIGN(StackingClientListWaiter);
181 };
182
183 StackingClientListWaiter::StackingClientListWaiter(XID* expected_windows,
184 size_t count)
185 : PropertyChangeWaiter(ui::GetX11RootWindow(),
186 "_NET_CLIENT_LIST_STACKING"),
187 expected_windows_(expected_windows, expected_windows + count) {
188 }
189
190 StackingClientListWaiter::~StackingClientListWaiter() {
191 }
192
193 bool StackingClientListWaiter::ShouldKeepOnWaiting(XEvent* event) {
194 std::vector<XID> stack;
195 ui::GetXWindowStack(ui::GetX11RootWindow(), &stack);
196 for (size_t i = 0; i < expected_windows_.size(); ++i) {
197 std::vector<XID>::iterator it = std::find(
198 stack.begin(), stack.end(), expected_windows_[i]);
199 if (it == stack.end())
200 return true;
201 }
202 return false;
203 }
204
205 } // namespace
206
207 class X11TopmostWindowFinderTest : public ViewsTestBase {
208 public:
209 X11TopmostWindowFinderTest() {
210 }
211
212 virtual ~X11TopmostWindowFinderTest() {
213 }
214
215 // Creates and shows a Widget with |bounds|. The caller takes ownership of
216 // the returned widget.
217 Widget* CreateAndShowWidget(const gfx::Rect& bounds) WARN_UNUSED_RESULT {
218 Widget* toplevel = new Widget;
219 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
220 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
221 params.native_widget = new DesktopNativeWidgetAura(toplevel);
222 params.bounds = bounds;
223 params.remove_standard_frame = true;
224 toplevel->Init(params);
225 toplevel->Show();
226 return toplevel;
227 }
228
229 // Creates and shows an X window with |bounds|.
230 XID CreateAndShowXWindow(const gfx::Rect& bounds) {
231 XID root = DefaultRootWindow(xdisplay());
232 XID xid = XCreateSimpleWindow(xdisplay(),
233 root,
234 1, 1, 1, 1,
235 0, // border_width
236 0, // border
237 0); // background
238 ui::SetUseOSWindowFrame(xid, false);
239 ShowAndSetXIDBounds(xid, bounds);
240 return xid;
241 }
242
243 // Shows |xid| and sets its bounds.
244 void ShowAndSetXIDBounds(XID xid, const gfx::Rect& bounds) {
245 XMapWindow(xdisplay(), xid);
246
247 XWindowChanges changes = {0};
248 changes.x = bounds.x();
249 changes.y = bounds.y();
250 changes.width = bounds.width();
251 changes.height = bounds.height();
252 XConfigureWindow(xdisplay(),
253 xid,
254 CWX | CWY | CWWidth | CWHeight,
255 &changes);
256 }
257
258 // Returns the atom associated with |name|.
259 Atom GetAtom(const char* name) {
260 return XInternAtom(gfx::GetXDisplay(), name, false);
261 }
262
263 Display* xdisplay() {
264 return gfx::GetXDisplay();
265 }
266
267 // Returns the topmost X window at the passed in screen position.
268 XID FindTopmostXWindowAt(int screen_x, int screen_y) {
269 X11TopmostWindowFinder finder;
270 return finder.FindWindowAt(gfx::Point(screen_x, screen_y));
271 }
272
273 // Returns the topmost aura::Window at the passed in screen position. Returns
274 // NULL if the topmost window does not have an associated aura::Window.
275 aura::Window* FindTopmostLocalProcessWindowAt(int screen_x, int screen_y) {
276 X11TopmostWindowFinder finder;
277 return finder.FindLocalProcessWindowAt(gfx::Point(screen_x, screen_y),
278 std::set<aura::Window*>());
279 }
280
281 // Returns the topmost aura::Window at the passed in screen position ignoring
282 // |ignore_window|. Returns NULL if the topmost window does not have an
283 // associated aura::Window.
284 aura::Window* FindTopmostLocalProcessWindowWithIgnore(
285 int screen_x,
286 int screen_y,
287 aura::Window* ignore_window) {
288 std::set<aura::Window*> ignore;
289 ignore.insert(ignore_window);
290 X11TopmostWindowFinder finder;
291 return finder.FindLocalProcessWindowAt(gfx::Point(screen_x, screen_y),
292 ignore);
293 }
294
295 virtual void SetUp() OVERRIDE {
296 // Make X11 synchronous for our display connection. This does force the
297 // window manager to behave synchronously.
298 XSynchronize(xdisplay(), True);
299 ViewsTestBase::SetUp();
300
301 // Ensure that the X11DesktopHandler exists. The X11DesktopHandler is
302 // necessary to properly track menu windows.
303 X11DesktopHandler::get();
304 }
305
306 virtual void TearDown() OVERRIDE {
307 ViewsTestBase::TearDown();
308 XSynchronize(xdisplay(), False);
309 }
310
311 private:
312 DISALLOW_COPY_AND_ASSIGN(X11TopmostWindowFinderTest);
313 };
314
315 TEST_F(X11TopmostWindowFinderTest, Basic) {
316 // Avoid positioning test windows at 0x0 because window managers often have a
317 // panel/launcher along one of the screen edges and do not allow windows to
318 // position themselves to overlap the panel/launcher.
319 scoped_ptr<Widget> widget1(
320 CreateAndShowWidget(gfx::Rect(100, 100, 200, 100)));
321 aura::Window* window1 = widget1->GetNativeWindow();
322 XID xid1 = window1->GetHost()->GetAcceleratedWidget();
323
324 XID xid2 = CreateAndShowXWindow(gfx::Rect(200, 100, 100, 200));
325
326 scoped_ptr<Widget> widget3(
327 CreateAndShowWidget(gfx::Rect(100, 190, 200, 110)));
328 aura::Window* window3 = widget3->GetNativeWindow();
329 XID xid3 = window3->GetHost()->GetAcceleratedWidget();
330
331 XID xids[] = { xid1, xid2, xid3 };
332 StackingClientListWaiter waiter(xids, arraysize(xids));
333 waiter.Wait();
334
335 EXPECT_EQ(xid1, FindTopmostXWindowAt(150, 150));
336 EXPECT_EQ(window1, FindTopmostLocalProcessWindowAt(150, 150));
337
338 EXPECT_EQ(xid2, FindTopmostXWindowAt(250, 150));
339 EXPECT_EQ(NULL, FindTopmostLocalProcessWindowAt(250, 150));
340
341 EXPECT_EQ(xid3, FindTopmostXWindowAt(250, 250));
342 EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(250, 250));
343
344 EXPECT_EQ(xid3, FindTopmostXWindowAt(150, 250));
345 EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(150, 250));
346
347 EXPECT_EQ(xid3, FindTopmostXWindowAt(150, 195));
348 EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(150, 195));
349
350 EXPECT_NE(xid1, FindTopmostXWindowAt(1000, 1000));
351 EXPECT_NE(xid2, FindTopmostXWindowAt(1000, 1000));
352 EXPECT_EQ(NULL, FindTopmostLocalProcessWindowAt(1000, 1000));
353
354 EXPECT_EQ(window1,
355 FindTopmostLocalProcessWindowWithIgnore(150, 150, window3));
356 EXPECT_EQ(NULL,
357 FindTopmostLocalProcessWindowWithIgnore(250, 250, window3));
358 EXPECT_EQ(NULL,
359 FindTopmostLocalProcessWindowWithIgnore(150, 250, window3));
360 EXPECT_EQ(window1,
361 FindTopmostLocalProcessWindowWithIgnore(150, 195, window3));
362
363 XDestroyWindow(xdisplay(), xid2);
364 }
365
366 // Test that the minimized state is properly handled.
367 TEST_F(X11TopmostWindowFinderTest, Minimized) {
368 XID xid = CreateAndShowXWindow(gfx::Rect(100, 100, 100, 100));
369
370 XID xids[] = { xid };
371 StackingClientListWaiter stack_waiter(xids, arraysize(xids));
372 stack_waiter.Wait();
373
374 EXPECT_EQ(xid, FindTopmostXWindowAt(150, 150));
375 {
376 MinimizeWaiter minimize_waiter(xid);
377 XIconifyWindow(xdisplay(), xid, 0);
378 minimize_waiter.Wait();
379 }
380 EXPECT_NE(xid, FindTopmostXWindowAt(150, 150));
381
382 XDestroyWindow(xdisplay(), xid);
383 }
384
385 // Test that non-rectangular windows are properly handled.
386 TEST_F(X11TopmostWindowFinderTest, NonRectangular) {
387 if (!ui::IsShapeExtensionAvailable())
388 return;
389
390 scoped_ptr<Widget> widget(
391 CreateAndShowWidget(gfx::Rect(100, 100, 100, 100)));
392 XID xid = widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
393
394 SkRegion* region = new SkRegion;
395 region->op(SkIRect::MakeXYWH(0, 10, 10, 90), SkRegion::kUnion_Op);
396 region->op(SkIRect::MakeXYWH(10, 0, 90, 100), SkRegion::kUnion_Op);
397 // Widget takes ownership of |region|.
398 widget->SetShape(region);
399
400 XID xids[] = { xid };
401 StackingClientListWaiter stack_waiter(xids, arraysize(xids));
402 stack_waiter.Wait();
403
404 EXPECT_EQ(xid, FindTopmostXWindowAt(120, 120));
405 EXPECT_NE(xid, FindTopmostXWindowAt(105, 105));
406 }
407
408 // Test that the TopmostWindowFinder finds windows which belong to menus
409 // (which may or may not belong to Chrome).
410 TEST_F(X11TopmostWindowFinderTest, Menu) {
411 XID xid = CreateAndShowXWindow(gfx::Rect(100, 100, 100, 100));
412
413 XID root = DefaultRootWindow(xdisplay());
414 XSetWindowAttributes swa;
415 swa.override_redirect = True;
416 XID menu_xid = XCreateWindow(xdisplay(),
417 root,
418 0, 0, 1, 1,
419 0, // border width
420 CopyFromParent, // depth
421 InputOutput,
422 CopyFromParent, // visual
423 CWOverrideRedirect,
424 &swa);
425 {
426 PropertyChangeWaiter property_waiter(menu_xid, "_NET_WM_WINDOW_TYPE");
427 ui::SetAtomProperty(menu_xid,
428 "_NET_WM_WINDOW_TYPE",
429 "ATOM",
430 GetAtom("_NET_WM_WINDOW_TYPE_MENU"));
431 property_waiter.Wait();
432 }
433 ui::SetUseOSWindowFrame(menu_xid, false);
434 ShowAndSetXIDBounds(menu_xid, gfx::Rect(140, 110, 100, 100));
435
436 // |menu_xid| is never added to _NET_CLIENT_LIST_STACKING.
437 XID xids[] = { xid };
438 StackingClientListWaiter stack_waiter(xids, arraysize(xids));
439 stack_waiter.Wait();
440
441 EXPECT_EQ(menu_xid, FindTopmostXWindowAt(150, 120));
442 EXPECT_EQ(menu_xid, FindTopmostXWindowAt(210, 120));
443
444 XDestroyWindow(xdisplay(), xid);
445 XDestroyWindow(xdisplay(), menu_xid);
446 }
447
448 } // namespace views
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698