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/platform_window/x11/x11_topmost_window_finder.h"
6
7 #include <stddef.h>
8
9 #include <algorithm>
10 #include <memory>
11 #include <vector>
12
13 #include "base/stl_util.h"
14 #include "build/build_config.h"
15 #include "third_party/skia/include/core/SkRect.h"
16 #include "ui/aura/window.h"
17 #include "ui/aura/window_tree_host.h"
18 #include "ui/base/x/test/x11_property_change_waiter.h"
19 #include "ui/base/x/x11_util.h"
20 #include "ui/events/platform/x11/x11_event_source.h"
21 #include "ui/gfx/native_widget_types.h"
22 #include "ui/gfx/x/connection.h"
23 #include "ui/gfx/x/event.h"
24 #include "ui/gfx/x/shape.h"
25 #include "ui/gfx/x/x11_atom_cache.h"
26 #include "ui/gfx/x/x11_path.h"
27 #include "ui/gfx/x/xproto.h"
28 #include "ui/views/test/widget_test.h"
29 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
30 #include "ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h"
31 #include "ui/views/widget/widget.h"
32
33 #if defined(USE_OZONE)
34 #include "ui/base/ui_base_features.h"
35 #include "ui/ozone/public/ozone_platform.h"
36 #endif
37
38 namespace views {
39
40 namespace {
41
42 // Waits till |window| is minimized.
43 class MinimizeWaiter : public ui::X11PropertyChangeWaiter {
44 public:
MinimizeWaiter(x11::Window window)45 explicit MinimizeWaiter(x11::Window window)
46 : ui::X11PropertyChangeWaiter(window, "_NET_WM_STATE") {}
47
48 ~MinimizeWaiter() override = default;
49
50 private:
51 // ui::X11PropertyChangeWaiter:
ShouldKeepOnWaiting(x11::Event * event)52 bool ShouldKeepOnWaiting(x11::Event* event) override {
53 std::vector<x11::Atom> wm_states;
54 if (ui::GetAtomArrayProperty(xwindow(), "_NET_WM_STATE", &wm_states)) {
55 return !base::Contains(wm_states, gfx::GetAtom("_NET_WM_STATE_HIDDEN"));
56 }
57 return true;
58 }
59
60 DISALLOW_COPY_AND_ASSIGN(MinimizeWaiter);
61 };
62
63 // Waits till |_NET_CLIENT_LIST_STACKING| is updated to include
64 // |expected_windows|.
65 class StackingClientListWaiter : public ui::X11PropertyChangeWaiter {
66 public:
StackingClientListWaiter(x11::Window * expected_windows,size_t count)67 StackingClientListWaiter(x11::Window* expected_windows, size_t count)
68 : ui::X11PropertyChangeWaiter(ui::GetX11RootWindow(),
69 "_NET_CLIENT_LIST_STACKING"),
70 expected_windows_(expected_windows, expected_windows + count) {}
71
72 ~StackingClientListWaiter() override = default;
73
74 // X11PropertyChangeWaiter:
Wait()75 void Wait() override {
76 // StackingClientListWaiter may be created after
77 // _NET_CLIENT_LIST_STACKING already contains |expected_windows|.
78 if (!ShouldKeepOnWaiting(nullptr))
79 return;
80
81 ui::X11PropertyChangeWaiter::Wait();
82 }
83
84 private:
85 // ui::X11PropertyChangeWaiter:
ShouldKeepOnWaiting(x11::Event * event)86 bool ShouldKeepOnWaiting(x11::Event* event) override {
87 std::vector<x11::Window> stack;
88 ui::GetXWindowStack(ui::GetX11RootWindow(), &stack);
89 return !std::all_of(
90 expected_windows_.cbegin(), expected_windows_.cend(),
91 [&stack](x11::Window window) { return base::Contains(stack, window); });
92 }
93
94 std::vector<x11::Window> expected_windows_;
95
96 DISALLOW_COPY_AND_ASSIGN(StackingClientListWaiter);
97 };
98
IconifyWindow(x11::Connection * connection,x11::Window window)99 void IconifyWindow(x11::Connection* connection, x11::Window window) {
100 ui::SendClientMessage(window, ui::GetX11RootWindow(),
101 gfx::GetAtom("WM_CHANGE_STATE"),
102 {ui::WM_STATE_ICONIC, 0, 0, 0, 0});
103 }
104
105 } // namespace
106
107 class X11TopmostWindowFinderTest : public test::DesktopWidgetTestInteractive {
108 public:
109 X11TopmostWindowFinderTest() = default;
110 ~X11TopmostWindowFinderTest() override = default;
111
112 // DesktopWidgetTestInteractive
SetUp()113 void SetUp() override {
114 #if defined(USE_OZONE)
115 // Run tests only for X11 (ozone or not Ozone).
116 if (features::IsUsingOzonePlatform() &&
117 std::strcmp(ui::OzonePlatform::GetInstance()->GetPlatformName(),
118 "x11") != 0) {
119 // SetUp still is required to be run. Otherwise, ViewsTestBase CHECKs in
120 // the dtor.
121 DesktopWidgetTestInteractive::SetUp();
122 GTEST_SKIP();
123 }
124 #endif
125 // Make X11 synchronous for our display connection. This does not force the
126 // window manager to behave synchronously.
127 connection()->SynchronizeForTest(true);
128 DesktopWidgetTestInteractive::SetUp();
129 }
130
TearDown()131 void TearDown() override {
132 if (!IsSkipped())
133 connection()->SynchronizeForTest(false);
134 DesktopWidgetTestInteractive::TearDown();
135 }
136
137 // Creates and shows a Widget with |bounds|. The caller takes ownership of
138 // the returned widget.
CreateAndShowWidget(const gfx::Rect & bounds)139 std::unique_ptr<Widget> CreateAndShowWidget(const gfx::Rect& bounds) {
140 std::unique_ptr<Widget> toplevel(new Widget);
141 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
142 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
143 params.native_widget = new DesktopNativeWidgetAura(toplevel.get());
144 params.bounds = bounds;
145 params.remove_standard_frame = true;
146 toplevel->Init(std::move(params));
147 toplevel->Show();
148 return toplevel;
149 }
150
151 // Creates and shows an X window with |bounds|.
CreateAndShowXWindow(const gfx::Rect & bounds)152 x11::Window CreateAndShowXWindow(const gfx::Rect& bounds) {
153 x11::Window root = ui::GetX11RootWindow();
154 auto window = connection()->GenerateId<x11::Window>();
155 connection()->CreateWindow({
156 .wid = window,
157 .parent = root,
158 .width = 1,
159 .height = 1,
160 });
161
162 ui::SetUseOSWindowFrame(window, false);
163 ShowAndSetXWindowBounds(window, bounds);
164 return window;
165 }
166
167 // Shows |window| and sets its bounds.
ShowAndSetXWindowBounds(x11::Window window,const gfx::Rect & bounds)168 void ShowAndSetXWindowBounds(x11::Window window, const gfx::Rect& bounds) {
169 connection()->MapWindow({window});
170
171 connection()->ConfigureWindow({
172 .window = window,
173 .x = bounds.x(),
174 .y = bounds.y(),
175 .width = bounds.width(),
176 .height = bounds.height(),
177 });
178 }
179
connection()180 x11::Connection* connection() { return x11::Connection::Get(); }
181
182 // Returns the topmost X window at the passed in screen position.
FindTopmostXWindowAt(int screen_x,int screen_y)183 x11::Window FindTopmostXWindowAt(int screen_x, int screen_y) {
184 ui::X11TopmostWindowFinder finder;
185 return finder.FindWindowAt(gfx::Point(screen_x, screen_y));
186 }
187
188 // Returns the topmost aura::Window at the passed in screen position. Returns
189 // NULL if the topmost window does not have an associated aura::Window.
FindTopmostLocalProcessWindowAt(int screen_x,int screen_y)190 aura::Window* FindTopmostLocalProcessWindowAt(int screen_x, int screen_y) {
191 ui::X11TopmostWindowFinder finder;
192 auto widget = static_cast<gfx::AcceleratedWidget>(
193 finder.FindLocalProcessWindowAt(gfx::Point(screen_x, screen_y), {}));
194 return widget != gfx::kNullAcceleratedWidget
195 ? DesktopWindowTreeHostPlatform::GetContentWindowForWidget(
196 widget)
197 : nullptr;
198 }
199
200 // Returns the topmost aura::Window at the passed in screen position ignoring
201 // |ignore_window|. Returns NULL if the topmost window does not have an
202 // associated aura::Window.
FindTopmostLocalProcessWindowWithIgnore(int screen_x,int screen_y,aura::Window * ignore_window)203 aura::Window* FindTopmostLocalProcessWindowWithIgnore(
204 int screen_x,
205 int screen_y,
206 aura::Window* ignore_window) {
207 std::set<gfx::AcceleratedWidget> ignore;
208 ignore.insert(ignore_window->GetHost()->GetAcceleratedWidget());
209 ui::X11TopmostWindowFinder finder;
210 auto widget =
211 static_cast<gfx::AcceleratedWidget>(finder.FindLocalProcessWindowAt(
212 gfx::Point(screen_x, screen_y), ignore));
213 return widget != gfx::kNullAcceleratedWidget
214 ? DesktopWindowTreeHostPlatform::GetContentWindowForWidget(
215 widget)
216 : nullptr;
217 }
218
219 private:
220 DISALLOW_COPY_AND_ASSIGN(X11TopmostWindowFinderTest);
221 };
222
TEST_F(X11TopmostWindowFinderTest,Basic)223 TEST_F(X11TopmostWindowFinderTest, Basic) {
224 // Avoid positioning test windows at 0x0 because window managers often have a
225 // panel/launcher along one of the screen edges and do not allow windows to
226 // position themselves to overlap the panel/launcher.
227 std::unique_ptr<Widget> widget1(
228 CreateAndShowWidget(gfx::Rect(100, 100, 200, 100)));
229 aura::Window* window1 = widget1->GetNativeWindow();
230 x11::Window x11_window1 =
231 static_cast<x11::Window>(window1->GetHost()->GetAcceleratedWidget());
232
233 x11::Window x11_window2 = CreateAndShowXWindow(gfx::Rect(200, 100, 100, 200));
234
235 std::unique_ptr<Widget> widget3(
236 CreateAndShowWidget(gfx::Rect(100, 190, 200, 110)));
237 aura::Window* window3 = widget3->GetNativeWindow();
238 x11::Window x11_window3 =
239 static_cast<x11::Window>(window3->GetHost()->GetAcceleratedWidget());
240
241 x11::Window windows[] = {x11_window1, x11_window2, x11_window3};
242 StackingClientListWaiter waiter(windows, base::size(windows));
243 waiter.Wait();
244 ui::X11EventSource::GetInstance()->DispatchXEvents();
245
246 EXPECT_EQ(x11_window1, FindTopmostXWindowAt(150, 150));
247 EXPECT_EQ(window1, FindTopmostLocalProcessWindowAt(150, 150));
248
249 EXPECT_EQ(x11_window2, FindTopmostXWindowAt(250, 150));
250 EXPECT_FALSE(FindTopmostLocalProcessWindowAt(250, 150));
251
252 EXPECT_EQ(x11_window3, FindTopmostXWindowAt(250, 250));
253 EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(250, 250));
254
255 EXPECT_EQ(x11_window3, FindTopmostXWindowAt(150, 250));
256 EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(150, 250));
257
258 EXPECT_EQ(x11_window3, FindTopmostXWindowAt(150, 195));
259 EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(150, 195));
260
261 EXPECT_NE(x11_window1, FindTopmostXWindowAt(1000, 1000));
262 EXPECT_NE(x11_window2, FindTopmostXWindowAt(1000, 1000));
263 EXPECT_NE(x11_window3, FindTopmostXWindowAt(1000, 1000));
264 EXPECT_FALSE(FindTopmostLocalProcessWindowAt(1000, 1000));
265
266 EXPECT_EQ(window1,
267 FindTopmostLocalProcessWindowWithIgnore(150, 150, window3));
268 EXPECT_FALSE(FindTopmostLocalProcessWindowWithIgnore(250, 250, window3));
269 EXPECT_FALSE(FindTopmostLocalProcessWindowWithIgnore(150, 250, window3));
270 EXPECT_EQ(window1,
271 FindTopmostLocalProcessWindowWithIgnore(150, 195, window3));
272
273 connection()->DestroyWindow({x11_window2});
274 }
275
276 // Test that the minimized state is properly handled.
TEST_F(X11TopmostWindowFinderTest,Minimized)277 TEST_F(X11TopmostWindowFinderTest, Minimized) {
278 std::unique_ptr<Widget> widget1(
279 CreateAndShowWidget(gfx::Rect(100, 100, 100, 100)));
280 aura::Window* window1 = widget1->GetNativeWindow();
281 x11::Window x11_window1 =
282 static_cast<x11::Window>(window1->GetHost()->GetAcceleratedWidget());
283 x11::Window x11_window2 = CreateAndShowXWindow(gfx::Rect(300, 100, 100, 100));
284
285 x11::Window windows[] = {x11_window1, x11_window2};
286 StackingClientListWaiter stack_waiter(windows, base::size(windows));
287 stack_waiter.Wait();
288 ui::X11EventSource::GetInstance()->DispatchXEvents();
289
290 EXPECT_EQ(x11_window1, FindTopmostXWindowAt(150, 150));
291 {
292 MinimizeWaiter minimize_waiter(x11_window1);
293 IconifyWindow(connection(), x11_window1);
294 minimize_waiter.Wait();
295 }
296 EXPECT_NE(x11_window1, FindTopmostXWindowAt(150, 150));
297 EXPECT_NE(x11_window2, FindTopmostXWindowAt(150, 150));
298
299 // Repeat test for an X window which does not belong to a views::Widget
300 // because the code path is different.
301 EXPECT_EQ(x11_window2, FindTopmostXWindowAt(350, 150));
302 {
303 MinimizeWaiter minimize_waiter(x11_window2);
304 IconifyWindow(connection(), x11_window2);
305 minimize_waiter.Wait();
306 }
307 EXPECT_NE(x11_window1, FindTopmostXWindowAt(350, 150));
308 EXPECT_NE(x11_window2, FindTopmostXWindowAt(350, 150));
309
310 connection()->DestroyWindow({x11_window2});
311 }
312
313 // Test that non-rectangular windows are properly handled.
TEST_F(X11TopmostWindowFinderTest,NonRectangular)314 TEST_F(X11TopmostWindowFinderTest, NonRectangular) {
315 if (!ui::IsShapeExtensionAvailable())
316 return;
317
318 std::unique_ptr<Widget> widget1(
319 CreateAndShowWidget(gfx::Rect(100, 100, 100, 100)));
320 x11::Window window1 = static_cast<x11::Window>(
321 widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
322 auto shape1 = std::make_unique<Widget::ShapeRects>();
323 shape1->emplace_back(0, 10, 10, 90);
324 shape1->emplace_back(10, 0, 90, 100);
325 widget1->SetShape(std::move(shape1));
326
327 SkRegion skregion2;
328 skregion2.op(SkIRect::MakeXYWH(0, 10, 10, 90), SkRegion::kUnion_Op);
329 skregion2.op(SkIRect::MakeXYWH(10, 0, 90, 100), SkRegion::kUnion_Op);
330 x11::Window window2 = CreateAndShowXWindow(gfx::Rect(300, 100, 100, 100));
331 auto region2 = gfx::CreateRegionFromSkRegion(skregion2);
332 x11::Connection::Get()->shape().Rectangles({
333 .operation = x11::Shape::So::Set,
334 .destination_kind = x11::Shape::Sk::Bounding,
335 .ordering = x11::ClipOrdering::YXBanded,
336 .destination_window = window2,
337 .rectangles = *region2,
338 });
339 x11::Window windows[] = {window1, window2};
340 StackingClientListWaiter stack_waiter(windows, base::size(windows));
341 stack_waiter.Wait();
342 ui::X11EventSource::GetInstance()->DispatchXEvents();
343
344 EXPECT_EQ(window1, FindTopmostXWindowAt(105, 120));
345 EXPECT_NE(window1, FindTopmostXWindowAt(105, 105));
346 EXPECT_NE(window2, FindTopmostXWindowAt(105, 105));
347
348 // Repeat test for an X window which does not belong to a views::Widget
349 // because the code path is different.
350 EXPECT_EQ(window2, FindTopmostXWindowAt(305, 120));
351 EXPECT_NE(window1, FindTopmostXWindowAt(305, 105));
352 EXPECT_NE(window2, FindTopmostXWindowAt(305, 105));
353
354 connection()->DestroyWindow({window2});
355 }
356
357 // Test that a window with an empty shape are properly handled.
TEST_F(X11TopmostWindowFinderTest,NonRectangularEmptyShape)358 TEST_F(X11TopmostWindowFinderTest, NonRectangularEmptyShape) {
359 if (!ui::IsShapeExtensionAvailable())
360 return;
361
362 std::unique_ptr<Widget> widget1(
363 CreateAndShowWidget(gfx::Rect(100, 100, 100, 100)));
364 x11::Window window1 = static_cast<x11::Window>(
365 widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
366 auto shape1 = std::make_unique<Widget::ShapeRects>();
367 shape1->emplace_back();
368 // Widget takes ownership of |shape1|.
369 widget1->SetShape(std::move(shape1));
370
371 x11::Window windows[] = {window1};
372 StackingClientListWaiter stack_waiter(windows, base::size(windows));
373 stack_waiter.Wait();
374 ui::X11EventSource::GetInstance()->DispatchXEvents();
375
376 EXPECT_NE(window1, FindTopmostXWindowAt(105, 105));
377 }
378
379 // Test that setting a Null shape removes the shape.
380 // crbug.com/955316: flaky on Linux
381 #if defined(OS_LINUX) || defined(OS_CHROMEOS)
382 #define MAYBE_NonRectangularNullShape DISABLED_NonRectangularNullShape
383 #else
384 #define MAYBE_NonRectangularNullShape NonRectangularNullShape
385 #endif
TEST_F(X11TopmostWindowFinderTest,MAYBE_NonRectangularNullShape)386 TEST_F(X11TopmostWindowFinderTest, MAYBE_NonRectangularNullShape) {
387 if (!ui::IsShapeExtensionAvailable())
388 return;
389
390 std::unique_ptr<Widget> widget1(
391 CreateAndShowWidget(gfx::Rect(100, 100, 100, 100)));
392 x11::Window window1 = static_cast<x11::Window>(
393 widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
394 auto shape1 = std::make_unique<Widget::ShapeRects>();
395 shape1->emplace_back();
396 widget1->SetShape(std::move(shape1));
397
398 // Remove the shape - this is now just a normal window.
399 widget1->SetShape(nullptr);
400
401 x11::Window windows[] = {window1};
402 StackingClientListWaiter stack_waiter(windows, base::size(windows));
403 stack_waiter.Wait();
404 ui::X11EventSource::GetInstance()->DispatchXEvents();
405
406 EXPECT_EQ(window1, FindTopmostXWindowAt(105, 105));
407 }
408
409 // Test that the TopmostWindowFinder finds windows which belong to menus
410 // (which may or may not belong to Chrome).
411 //
412 // Flakes (https://crbug.com/955316)
TEST_F(X11TopmostWindowFinderTest,DISABLED_Menu)413 TEST_F(X11TopmostWindowFinderTest, DISABLED_Menu) {
414 x11::Window window = CreateAndShowXWindow(gfx::Rect(100, 100, 100, 100));
415
416 x11::Window root = ui::GetX11RootWindow();
417 auto menu_window = connection()->GenerateId<x11::Window>();
418 connection()->CreateWindow({
419 .wid = menu_window,
420 .parent = root,
421 .width = 1,
422 .height = 1,
423 .c_class = x11::WindowClass::CopyFromParent,
424 .override_redirect = x11::Bool32(true),
425 });
426
427 ui::SetAtomProperty(menu_window, "_NET_WM_WINDOW_TYPE", "ATOM",
428 gfx::GetAtom("_NET_WM_WINDOW_TYPE_MENU"));
429
430 ui::SetUseOSWindowFrame(menu_window, false);
431 ShowAndSetXWindowBounds(menu_window, gfx::Rect(140, 110, 100, 100));
432 ui::X11EventSource::GetInstance()->DispatchXEvents();
433
434 // |menu_window| is never added to _NET_CLIENT_LIST_STACKING.
435 x11::Window windows[] = {window};
436 StackingClientListWaiter stack_waiter(windows, base::size(windows));
437 stack_waiter.Wait();
438
439 EXPECT_EQ(window, FindTopmostXWindowAt(110, 110));
440 EXPECT_EQ(menu_window, FindTopmostXWindowAt(150, 120));
441 EXPECT_EQ(menu_window, FindTopmostXWindowAt(210, 120));
442
443 connection()->DestroyWindow({window});
444 connection()->DestroyWindow({menu_window});
445 }
446
447 } // namespace views
448