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