Chromium Code Reviews| Index: ui/views/widget/desktop_aura/desktop_window_tree_host_x11_unittest.cc |
| diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11_unittest.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..3e473ea43b2ac3034b46fbe5dc9584e47588a431 |
| --- /dev/null |
| +++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11_unittest.cc |
| @@ -0,0 +1,318 @@ |
| +// Copyright 2014 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include <vector> |
| + |
| +// Include views_test_base.h first because the definition of None in X.h |
| +// conflicts with the definition of None in gtest-type-util.h |
|
sadrul
2014/05/14 02:49:05
In a few places, we include the X11 headers, follo
pkotwicz
2014/05/14 04:07:25
A couple of the header files include Xlib.h. (x11_
sadrul
2014/05/14 04:11:50
Perhaps forward declare XEvent instead, and use XE
|
| +#include "ui/views/test/views_test_base.h" |
| + |
| +#include "base/memory/scoped_ptr.h" |
| +#include "base/run_loop.h" |
| +#include "ui/aura/window.h" |
| +#include "ui/aura/window_tree_host.h" |
| +#include "ui/base/hit_test.h" |
| +#include "ui/base/x/x11_util.h" |
| +#include "ui/events/platform/platform_event_dispatcher.h" |
| +#include "ui/events/platform/platform_event_source.h" |
| +#include "ui/events/platform/scoped_event_dispatcher.h" |
| +#include "ui/events/platform/x11/x11_event_source.h" |
| +#include "ui/gfx/path.h" |
| +#include "ui/gfx/point.h" |
| +#include "ui/gfx/rect.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/widget_delegate.h" |
| +#include "ui/views/window/non_client_view.h" |
| + |
| +#include <X11/extensions/shape.h> |
| +#include <X11/Xlib.h> |
| + |
| +namespace views { |
| + |
| +namespace { |
| + |
| +// Blocks till |window| becomes maximized. |
| +class MaximizeWaiter : public ui::PlatformEventDispatcher { |
| + public: |
| + explicit MaximizeWaiter(XID window) |
| + : xwindow_(window), |
| + wait_(true) { |
| + const char* kAtomsToCache[] = { |
| + "_NET_WM_STATE", |
| + "_NET_WM_STATE_MAXIMIZED_VERT", |
| + NULL |
| + }; |
| + atom_cache_.reset(new ui::X11AtomCache(gfx::GetXDisplay(), kAtomsToCache)); |
| + |
| + // Override the dispatcher so that we get events before |
| + // DesktopWindowTreeHostX11 does. We must do this because |
| + // DesktopWindowTreeHostX11 stops propagation. |
| + dispatcher_ = ui::PlatformEventSource::GetInstance()-> |
| + OverrideDispatcher(this).Pass(); |
| + } |
| + |
| + virtual ~MaximizeWaiter() { |
| + } |
| + |
| + void Wait() { |
| + if (wait_) { |
| + base::RunLoop run_loop; |
| + quit_closure_ = run_loop.QuitClosure(); |
| + run_loop.Run(); |
| + } |
| + dispatcher_.reset(); |
| + } |
| + |
| + virtual bool CanDispatchEvent(const ui::PlatformEvent& event) OVERRIDE { |
| + NOTREACHED(); |
| + return true; |
| + } |
| + |
| + virtual uint32_t DispatchEvent(const ui::PlatformEvent& event) OVERRIDE { |
| + if (event->type != PropertyNotify || |
| + event->xproperty.window != xwindow_ || |
| + event->xproperty.atom != atom_cache_->GetAtom("_NET_WM_STATE")) { |
| + return ui::POST_DISPATCH_PERFORM_DEFAULT; |
| + } |
| + |
| + std::vector<Atom> wm_states; |
| + if (!ui::GetAtomArrayProperty(xwindow_, "_NET_WM_STATE", &wm_states)) |
| + return ui::POST_DISPATCH_PERFORM_DEFAULT; |
| + |
| + std::vector<Atom>::iterator it = std::find( |
| + wm_states.begin(), |
| + wm_states.end(), |
| + atom_cache_->GetAtom("_NET_WM_STATE_MAXIMIZED_VERT")); |
| + if (it == wm_states.end()) |
| + return ui::POST_DISPATCH_PERFORM_DEFAULT; |
| + |
| + wait_ = false; |
| + if (!quit_closure_.is_null()) |
| + quit_closure_.Run(); |
| + return ui::POST_DISPATCH_PERFORM_DEFAULT; |
| + } |
| + |
| + // The window we are waiting to get maximized. |
| + XID xwindow_; |
| + |
| + // Whether Wait() should block. |
| + bool wait_; |
| + |
| + // Ends the run loop. |
| + base::Closure quit_closure_; |
| + |
| + scoped_ptr<ui::ScopedEventDispatcher> dispatcher_; |
| + |
| + scoped_ptr<ui::X11AtomCache> atom_cache_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(MaximizeWaiter); |
| +}; |
| + |
| +// A NonClientFrameView with a window mask with the bottom right corner cut out. |
| +class ShapedNonClientFrameView : public NonClientFrameView { |
| + public: |
| + explicit ShapedNonClientFrameView() { |
| + } |
| + |
| + virtual ~ShapedNonClientFrameView() { |
| + } |
| + |
| + // NonClientFrameView: |
| + virtual gfx::Rect GetBoundsForClientView() const OVERRIDE { |
| + return bounds(); |
| + } |
| + virtual gfx::Rect GetWindowBoundsForClientBounds( |
| + const gfx::Rect& client_bounds) const OVERRIDE { |
| + return client_bounds; |
| + } |
| + virtual int NonClientHitTest(const gfx::Point& point) OVERRIDE { |
| + return HTNOWHERE; |
| + } |
| + virtual void GetWindowMask(const gfx::Size& size, |
| + gfx::Path* window_mask) OVERRIDE { |
| + int right = size.width(); |
| + int bottom = size.height(); |
| + |
| + window_mask->moveTo(0, 0); |
| + window_mask->lineTo(0, bottom); |
| + window_mask->lineTo(right, bottom); |
| + window_mask->lineTo(right, 10); |
| + window_mask->lineTo(right - 10, 10); |
| + window_mask->lineTo(right - 10, 0); |
| + window_mask->close(); |
| + } |
| + virtual void ResetWindowControls() OVERRIDE { |
| + } |
| + virtual void UpdateWindowIcon() OVERRIDE { |
| + } |
| + virtual void UpdateWindowTitle() OVERRIDE { |
| + } |
| + |
| + private: |
| + DISALLOW_COPY_AND_ASSIGN(ShapedNonClientFrameView); |
| +}; |
| + |
| +class ShapedWidgetDelegate : public WidgetDelegateView { |
| + public: |
| + ShapedWidgetDelegate() { |
| + } |
| + |
| + virtual ~ShapedWidgetDelegate() { |
| + } |
| + |
| + // WidgetDelegateView: |
| + virtual NonClientFrameView* CreateNonClientFrameView( |
| + Widget* widget) OVERRIDE { |
| + return new ShapedNonClientFrameView; |
| + } |
| + |
| + private: |
| + DISALLOW_COPY_AND_ASSIGN(ShapedWidgetDelegate); |
| +}; |
| + |
| +// Creates a widget of size 100x100. |
| +scoped_ptr<Widget> CreateWidget(WidgetDelegate* delegate) { |
| + scoped_ptr<Widget> widget(new Widget); |
| + Widget::InitParams params(Widget::InitParams::TYPE_WINDOW); |
| + params.delegate = delegate; |
| + params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
| + params.remove_standard_frame = true; |
| + params.native_widget = new DesktopNativeWidgetAura(widget.get()); |
| + params.bounds = gfx::Rect(100, 100, 100, 100); |
| + widget->Init(params); |
| + return widget.Pass(); |
| +} |
| + |
| +// Returns the list of rectangles which describe |xid|'s bounding region via the |
| +// X shape extension. |
| +std::vector<gfx::Rect> GetShapeRects(XID xid) { |
| + int dummy; |
| + int shape_rects_size; |
| + XRectangle* shape_rects = XShapeGetRectangles(gfx::GetXDisplay(), |
| + xid, |
| + ShapeBounding, |
| + &shape_rects_size, |
| + &dummy); |
| + |
| + std::vector<gfx::Rect> shape_vector; |
| + for (int i = 0; i < shape_rects_size; ++i) { |
| + shape_vector.push_back(gfx::Rect( |
| + shape_rects[i].x, |
| + shape_rects[i].y, |
| + shape_rects[i].width, |
| + shape_rects[i].height)); |
| + } |
| + XFree(shape_rects); |
| + return shape_vector; |
| +} |
| + |
| +// Returns true if one of |rects| contains point (x,y). |
| +bool ShapeRectContainsPoint(std::vector<gfx::Rect> shape_rects, |
|
sadrul
2014/05/14 02:49:05
const &
|
| + int x, |
| + int y) { |
| + gfx::Point point(x, y); |
| + for (size_t i = 0; i < shape_rects.size(); ++i) { |
| + if (shape_rects[i].Contains(point)) |
| + return true; |
| + } |
| + return false; |
| +} |
| + |
| +} // namespace |
| + |
| +typedef ViewsTestBase DesktopWindowTreeHostX11Test; |
| + |
| +// Tests that the shape is properly set on the x window. |
| +TEST_F(DesktopWindowTreeHostX11Test, Shape) { |
| + if (!ui::IsShapeExtensionAvailable()) |
| + return; |
| + |
| + XSynchronize(gfx::GetXDisplay(), True); |
|
sadrul
2014/05/14 02:49:05
Interesting!
|
| + |
| + // 1) Test setting the window shape via the NonClientFrameView. This technique |
| + // is used to get rounded corners on Chrome windows when not using the native |
| + // window frame. |
| + scoped_ptr<Widget> widget1 = CreateWidget(new ShapedWidgetDelegate()); |
| + widget1->Show(); |
| + XID xid1 = widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget(); |
| + ui::X11EventSource::GetInstance()->DispatchXEvents(); |
|
sadrul
2014/05/14 02:49:05
I assume this is expected to dispatch the events f
pkotwicz
2014/05/14 04:07:25
Yes. This has the important side effect of ensurin
|
| + |
| + std::vector<gfx::Rect> shape_rects = GetShapeRects(xid1); |
| + ASSERT_FALSE(shape_rects.empty()); |
| + EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 85, 5)); |
| + EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 95, 5)); |
| + EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15)); |
| + EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 105, 15)); |
| + |
| + // Changing widget's size should update the shape. |
| + widget1->SetBounds(gfx::Rect(100, 100, 200, 200)); |
| + ui::X11EventSource::GetInstance()->DispatchXEvents(); |
| + |
| + shape_rects = GetShapeRects(xid1); |
| + ASSERT_FALSE(shape_rects.empty()); |
| + EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 85, 5)); |
| + EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 5)); |
| + EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 185, 5)); |
| + EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 195, 5)); |
| + EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 195, 15)); |
| + EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 205, 15)); |
| + |
| + // The shape should be changed to a rectangle which fills the entire screen |
| + // when |widget1| is maximized. |
| + { |
| + MaximizeWaiter waiter(xid1); |
| + widget1->Maximize(); |
| + waiter.Wait(); |
| + } |
| + |
| + // xvfb does not support Xrandr so we cannot check the maximized window's |
| + // bounds. |
| + gfx::Rect maximized_bounds; |
| + ui::GetWindowRect(xid1, &maximized_bounds); |
| + |
| + shape_rects = GetShapeRects(xid1); |
| + ASSERT_FALSE(shape_rects.empty()); |
| + EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, |
| + maximized_bounds.width() - 1, |
| + 5)); |
| + EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, |
| + maximized_bounds.width() - 1, |
| + 15)); |
| + |
| + // 2) Test setting the window shape via Widget::SetShape(). |
| + gfx::Path shape2; |
| + shape2.moveTo(10, 0); |
| + shape2.lineTo(10, 10); |
| + shape2.lineTo(0, 10); |
| + shape2.lineTo(0, 100); |
| + shape2.lineTo(100, 100); |
| + shape2.lineTo(100, 0); |
| + shape2.close(); |
| + |
| + scoped_ptr<Widget> widget2(CreateWidget(NULL)); |
| + widget2->Show(); |
| + widget2->SetShape(shape2.CreateNativeRegion()); |
| + XID xid2 = widget2->GetNativeWindow()->GetHost()->GetAcceleratedWidget(); |
| + ui::X11EventSource::GetInstance()->DispatchXEvents(); |
| + |
| + shape_rects = GetShapeRects(xid2); |
| + ASSERT_FALSE(shape_rects.empty()); |
| + EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 5, 5)); |
| + EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 15, 5)); |
| + EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15)); |
| + EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 105, 15)); |
| + |
| + // Changing the widget's size should not affect the shape. |
| + widget2->SetBounds(gfx::Rect(100, 100, 200, 200)); |
| + shape_rects = GetShapeRects(xid2); |
| + ASSERT_FALSE(shape_rects.empty()); |
| + EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 5, 5)); |
| + EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 15, 5)); |
| + EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15)); |
| + EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 105, 15)); |
| +} |
| + |
| +} // namespace views |