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 "chrome/browser/ui/views/apps/shaped_app_window_targeter.h" 6 7 #include "apps/ui/views/app_window_frame_view.h" 8 #include "chrome/browser/ui/views/apps/chrome_native_app_window_views.h" 9 #include "ui/aura/test/aura_test_base.h" 10 #include "ui/aura/window.h" 11 #include "ui/aura/window_event_dispatcher.h" 12 #include "ui/views/controls/webview/webview.h" 13 #include "ui/wm/core/default_activation_client.h" 14 #include "ui/wm/core/easy_resize_window_targeter.h" 15 16 class ShapedAppWindowTargeterTest : public aura::test::AuraTestBase { 17 public: 18 ShapedAppWindowTargeterTest() 19 : web_view_(NULL) { 20 } 21 22 virtual ~ShapedAppWindowTargeterTest() {} 23 24 views::Widget* widget() { return widget_.get(); } 25 26 extensions::NativeAppWindow* app_window() { return &app_window_; } 27 ChromeNativeAppWindowViews* app_window_views() { return &app_window_; } 28 29 protected: 30 virtual void SetUp() OVERRIDE { 31 aura::test::AuraTestBase::SetUp(); 32 new wm::DefaultActivationClient(root_window()); 33 widget_.reset(new views::Widget); 34 views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW); 35 params.remove_standard_frame = true; 36 params.bounds = gfx::Rect(30, 30, 100, 100); 37 params.context = root_window(); 38 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 39 widget_->Init(params); 40 41 app_window_.set_web_view_for_testing(&web_view_); 42 app_window_.set_window_for_testing(widget_.get()); 43 44 widget_->Show(); 45 } 46 47 virtual void TearDown() OVERRIDE { 48 widget_.reset(); 49 aura::test::AuraTestBase::TearDown(); 50 } 51 52 private: 53 views::WebView web_view_; 54 scoped_ptr<views::Widget> widget_; 55 ChromeNativeAppWindowViews app_window_; 56 57 DISALLOW_COPY_AND_ASSIGN(ShapedAppWindowTargeterTest); 58 }; 59 60 TEST_F(ShapedAppWindowTargeterTest, HitTestBasic) { 61 aura::Window* window = widget()->GetNativeWindow(); 62 { 63 // Without any custom shapes, the event should be targeted correctly to the 64 // window. 65 ui::MouseEvent move(ui::ET_MOUSE_MOVED, 66 gfx::Point(40, 40), gfx::Point(40, 40), 67 ui::EF_NONE, ui::EF_NONE); 68 ui::EventDispatchDetails details = 69 event_processor()->OnEventFromSource(&move); 70 ASSERT_FALSE(details.dispatcher_destroyed); 71 EXPECT_EQ(window, move.target()); 72 } 73 74 scoped_ptr<SkRegion> region(new SkRegion); 75 region->op(SkIRect::MakeXYWH(40, 0, 20, 100), SkRegion::kUnion_Op); 76 region->op(SkIRect::MakeXYWH(0, 40, 100, 20), SkRegion::kUnion_Op); 77 app_window()->UpdateShape(region.Pass()); 78 { 79 // With the custom shape, the events that don't fall within the custom shape 80 // will go through to the root window. 81 ui::MouseEvent move(ui::ET_MOUSE_MOVED, 82 gfx::Point(40, 40), gfx::Point(40, 40), 83 ui::EF_NONE, ui::EF_NONE); 84 ui::EventDispatchDetails details = 85 event_processor()->OnEventFromSource(&move); 86 ASSERT_FALSE(details.dispatcher_destroyed); 87 EXPECT_EQ(root_window(), move.target()); 88 89 // But events within the shape will still reach the window. 90 ui::MouseEvent move2(ui::ET_MOUSE_MOVED, 91 gfx::Point(80, 80), gfx::Point(80, 80), 92 ui::EF_NONE, ui::EF_NONE); 93 details = event_processor()->OnEventFromSource(&move2); 94 ASSERT_FALSE(details.dispatcher_destroyed); 95 EXPECT_EQ(window, move2.target()); 96 } 97 } 98 99 TEST_F(ShapedAppWindowTargeterTest, HitTestOnlyForShapedWindow) { 100 // Install a window-targeter on the root window that allows a window to 101 // receive events outside of its bounds. Verify that this window-targeter is 102 // active unless the window has a custom shape. 103 gfx::Insets inset(-30, -30, -30, -30); 104 root_window()->SetEventTargeter(scoped_ptr<ui::EventTargeter>( 105 new wm::EasyResizeWindowTargeter(root_window(), inset, inset))); 106 107 aura::Window* window = widget()->GetNativeWindow(); 108 { 109 // Without any custom shapes, an event within the window bounds should be 110 // targeted correctly to the window. 111 ui::MouseEvent move(ui::ET_MOUSE_MOVED, 112 gfx::Point(40, 40), gfx::Point(40, 40), 113 ui::EF_NONE, ui::EF_NONE); 114 ui::EventDispatchDetails details = 115 event_processor()->OnEventFromSource(&move); 116 ASSERT_FALSE(details.dispatcher_destroyed); 117 EXPECT_EQ(window, move.target()); 118 } 119 { 120 // Without any custom shapes, an event that falls just outside the window 121 // bounds should also be targeted correctly to the window, because of the 122 // targeter installed on the root-window. 123 ui::MouseEvent move(ui::ET_MOUSE_MOVED, 124 gfx::Point(10, 10), gfx::Point(10, 10), 125 ui::EF_NONE, ui::EF_NONE); 126 ui::EventDispatchDetails details = 127 event_processor()->OnEventFromSource(&move); 128 ASSERT_FALSE(details.dispatcher_destroyed); 129 EXPECT_EQ(window, move.target()); 130 } 131 132 scoped_ptr<SkRegion> region(new SkRegion); 133 region->op(SkIRect::MakeXYWH(40, 0, 20, 100), SkRegion::kUnion_Op); 134 region->op(SkIRect::MakeXYWH(0, 40, 100, 20), SkRegion::kUnion_Op); 135 app_window()->UpdateShape(region.Pass()); 136 { 137 // With the custom shape, the events that don't fall within the custom shape 138 // will go through to the root window. 139 ui::MouseEvent move(ui::ET_MOUSE_MOVED, 140 gfx::Point(10, 10), gfx::Point(10, 10), 141 ui::EF_NONE, ui::EF_NONE); 142 ui::EventDispatchDetails details = 143 event_processor()->OnEventFromSource(&move); 144 ASSERT_FALSE(details.dispatcher_destroyed); 145 EXPECT_EQ(root_window(), move.target()); 146 } 147 148 // Remove the custom shape. This should restore the behaviour of targeting the 149 // app window for events just outside its bounds. 150 app_window()->UpdateShape(scoped_ptr<SkRegion>()); 151 { 152 ui::MouseEvent move(ui::ET_MOUSE_MOVED, 153 gfx::Point(10, 10), gfx::Point(10, 10), 154 ui::EF_NONE, ui::EF_NONE); 155 ui::EventDispatchDetails details = 156 event_processor()->OnEventFromSource(&move); 157 ASSERT_FALSE(details.dispatcher_destroyed); 158 EXPECT_EQ(window, move.target()); 159 } 160 } 161 162 // Tests targeting of events on a window with an EasyResizeWindowTargeter 163 // installed on its container. 164 TEST_F(ShapedAppWindowTargeterTest, ResizeInsetsWithinBounds) { 165 aura::Window* window = widget()->GetNativeWindow(); 166 { 167 // An event in the center of the window should always have 168 // |window| as its target. 169 ui::MouseEvent move(ui::ET_MOUSE_MOVED, 170 gfx::Point(80, 80), gfx::Point(80, 80), 171 ui::EF_NONE, ui::EF_NONE); 172 ui::EventDispatchDetails details = 173 event_processor()->OnEventFromSource(&move); 174 ASSERT_FALSE(details.dispatcher_destroyed); 175 EXPECT_EQ(window, move.target()); 176 } 177 { 178 // Without an EasyResizeTargeter on the container, an event 179 // inside the window and within 5px of an edge should have 180 // |window| as its target. 181 ui::MouseEvent move(ui::ET_MOUSE_MOVED, 182 gfx::Point(32, 37), gfx::Point(32, 37), 183 ui::EF_NONE, ui::EF_NONE); 184 ui::EventDispatchDetails details = 185 event_processor()->OnEventFromSource(&move); 186 ASSERT_FALSE(details.dispatcher_destroyed); 187 EXPECT_EQ(window, move.target()); 188 } 189 190 #if !defined(OS_CHROMEOS) 191 // The non standard app frame has a easy resize targetter installed. 192 scoped_ptr<apps::AppWindowFrameView> frame( 193 app_window_views()->CreateNonStandardAppFrame()); 194 { 195 // Ensure that the window has an event targeter (there should be an 196 // EasyResizeWindowTargeter installed). 197 EXPECT_TRUE(static_cast<ui::EventTarget*>(window)->GetEventTargeter()); 198 } 199 { 200 // An event in the center of the window should always have 201 // |window| as its target. 202 // TODO(mgiuca): This isn't really testing anything (note that it has the 203 // same expectation as the border case below). In the real environment, the 204 // target will actually be the RenderWidgetHostViewAura's window that is the 205 // child of the child of |window|, whereas in the border case it *will* be 206 // |window|. However, since this test environment does not have a 207 // RenderWidgetHostViewAura, we cannot differentiate the two cases. Fix 208 // the test environment so that the test can assert that non-border events 209 // bubble down to a child of |window|. 210 ui::MouseEvent move(ui::ET_MOUSE_MOVED, 211 gfx::Point(80, 80), gfx::Point(80, 80), 212 ui::EF_NONE, ui::EF_NONE); 213 ui::EventDispatchDetails details = 214 event_processor()->OnEventFromSource(&move); 215 ASSERT_FALSE(details.dispatcher_destroyed); 216 EXPECT_EQ(window, move.target()); 217 } 218 { 219 // With an EasyResizeTargeter on the container, an event 220 // inside the window and within 5px of an edge should have 221 // |window| as its target. 222 ui::MouseEvent move(ui::ET_MOUSE_MOVED, 223 gfx::Point(32, 37), gfx::Point(32, 37), 224 ui::EF_NONE, ui::EF_NONE); 225 ui::EventDispatchDetails details = 226 event_processor()->OnEventFromSource(&move); 227 ASSERT_FALSE(details.dispatcher_destroyed); 228 EXPECT_EQ(window, move.target()); 229 } 230 #endif // defined (OS_CHROMEOS) 231 } 232