1 // Copyright 2017 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 "components/exo/client_controlled_shell_surface.h"
6
7 #include "ash/display/screen_orientation_controller.h"
8 #include "ash/frame/header_view.h"
9 #include "ash/frame/non_client_frame_view_ash.h"
10 #include "ash/frame/wide_frame_view.h"
11 #include "ash/public/cpp/caption_buttons/caption_button_model.h"
12 #include "ash/public/cpp/caption_buttons/frame_caption_button_container_view.h"
13 #include "ash/public/cpp/test/shell_test_api.h"
14 #include "ash/public/cpp/window_pin_type.h"
15 #include "ash/public/cpp/window_properties.h"
16 #include "ash/shell.h"
17 #include "ash/system/unified/unified_system_tray.h"
18 #include "ash/wm/drag_window_resizer.h"
19 #include "ash/wm/overview/overview_controller.h"
20 #include "ash/wm/splitview/split_view_controller.h"
21 #include "ash/wm/tablet_mode/tablet_mode_browser_window_drag_delegate.h"
22 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
23 #include "ash/wm/tablet_mode/tablet_mode_window_drag_delegate.h"
24 #include "ash/wm/tablet_mode/tablet_mode_window_resizer.h"
25 #include "ash/wm/window_positioning_utils.h"
26 #include "ash/wm/window_resizer.h"
27 #include "ash/wm/window_state.h"
28 #include "ash/wm/window_util.h"
29 #include "ash/wm/wm_event.h"
30 #include "ash/wm/workspace_controller_test_api.h"
31 #include "base/bind.h"
32 #include "base/run_loop.h"
33 #include "base/strings/utf_string_conversions.h"
34 #include "cc/paint/display_item_list.h"
35 #include "components/exo/buffer.h"
36 #include "components/exo/display.h"
37 #include "components/exo/pointer.h"
38 #include "components/exo/shell_surface_util.h"
39 #include "components/exo/sub_surface.h"
40 #include "components/exo/surface.h"
41 #include "components/exo/test/exo_test_base.h"
42 #include "components/exo/test/exo_test_helper.h"
43 #include "components/exo/wm_helper.h"
44 #include "third_party/skia/include/utils/SkNoDrawCanvas.h"
45 #include "ui/aura/client/aura_constants.h"
46 #include "ui/aura/env.h"
47 #include "ui/aura/window.h"
48 #include "ui/aura/window_event_dispatcher.h"
49 #include "ui/aura/window_targeter.h"
50 #include "ui/aura/window_tree_host.h"
51 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
52 #include "ui/compositor/scoped_layer_animation_settings.h"
53 #include "ui/compositor_extra/shadow.h"
54 #include "ui/display/display.h"
55 #include "ui/display/test/display_manager_test_api.h"
56 #include "ui/events/base_event_utils.h"
57 #include "ui/events/event_targeter.h"
58 #include "ui/events/test/event_generator.h"
59 #include "ui/views/paint_info.h"
60 #include "ui/views/widget/widget.h"
61 #include "ui/wm/core/shadow_controller.h"
62 #include "ui/wm/core/shadow_types.h"
63
64 namespace exo {
65 namespace {
66 using ClientControlledShellSurfaceTest = test::ExoTestBase;
67
HasBackdrop()68 bool HasBackdrop() {
69 ash::WorkspaceController* wc = ash::ShellTestApi().workspace_controller();
70 return !!ash::WorkspaceControllerTestApi(wc).GetBackdropWindow();
71 }
72
IsWidgetPinned(views::Widget * widget)73 bool IsWidgetPinned(views::Widget* widget) {
74 ash::WindowPinType type =
75 widget->GetNativeWindow()->GetProperty(ash::kWindowPinTypeKey);
76 return type == ash::WindowPinType::kPinned ||
77 type == ash::WindowPinType::kTrustedPinned;
78 }
79
GetShadowElevation(aura::Window * window)80 int GetShadowElevation(aura::Window* window) {
81 return window->GetProperty(wm::kShadowElevationKey);
82 }
83
EnableTabletMode(bool enable)84 void EnableTabletMode(bool enable) {
85 ash::Shell::Get()->tablet_mode_controller()->SetEnabledForTest(enable);
86 }
87
88 // A canvas that just logs when a text blob is drawn.
89 class TestCanvas : public SkNoDrawCanvas {
90 public:
TestCanvas()91 TestCanvas() : SkNoDrawCanvas(100, 100) {}
~TestCanvas()92 ~TestCanvas() override {}
93
onDrawTextBlob(const SkTextBlob *,SkScalar,SkScalar,const SkPaint &)94 void onDrawTextBlob(const SkTextBlob*,
95 SkScalar,
96 SkScalar,
97 const SkPaint&) override {
98 text_was_drawn_ = true;
99 }
100
text_was_drawn() const101 bool text_was_drawn() const { return text_was_drawn_; }
102
103 private:
104 bool text_was_drawn_ = false;
105
106 DISALLOW_COPY_AND_ASSIGN(TestCanvas);
107 };
108
109 } // namespace
110
TEST_F(ClientControlledShellSurfaceTest,SetPinned)111 TEST_F(ClientControlledShellSurfaceTest, SetPinned) {
112 gfx::Size buffer_size(256, 256);
113 std::unique_ptr<Buffer> buffer(
114 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
115
116 std::unique_ptr<Surface> surface(new Surface);
117 surface->Attach(buffer.get());
118 surface->Commit();
119
120 auto shell_surface(
121 exo_test_helper()->CreateClientControlledShellSurface(surface.get()));
122
123 shell_surface->SetPinned(ash::WindowPinType::kTrustedPinned);
124 EXPECT_TRUE(IsWidgetPinned(shell_surface->GetWidget()));
125
126 shell_surface->SetPinned(ash::WindowPinType::kNone);
127 EXPECT_FALSE(IsWidgetPinned(shell_surface->GetWidget()));
128
129 shell_surface->SetPinned(ash::WindowPinType::kPinned);
130 EXPECT_TRUE(IsWidgetPinned(shell_surface->GetWidget()));
131
132 shell_surface->SetPinned(ash::WindowPinType::kNone);
133 EXPECT_FALSE(IsWidgetPinned(shell_surface->GetWidget()));
134 }
135
TEST_F(ClientControlledShellSurfaceTest,SetSystemUiVisibility)136 TEST_F(ClientControlledShellSurfaceTest, SetSystemUiVisibility) {
137 gfx::Size buffer_size(256, 256);
138 std::unique_ptr<Buffer> buffer(
139 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
140 std::unique_ptr<Surface> surface(new Surface);
141 auto shell_surface =
142 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
143 surface->Attach(buffer.get());
144 surface->Commit();
145
146 shell_surface->SetSystemUiVisibility(true);
147 EXPECT_TRUE(
148 ash::WindowState::Get(shell_surface->GetWidget()->GetNativeWindow())
149 ->autohide_shelf_when_maximized_or_fullscreen());
150
151 shell_surface->SetSystemUiVisibility(false);
152 EXPECT_FALSE(
153 ash::WindowState::Get(shell_surface->GetWidget()->GetNativeWindow())
154 ->autohide_shelf_when_maximized_or_fullscreen());
155 }
156
TEST_F(ClientControlledShellSurfaceTest,SetTopInset)157 TEST_F(ClientControlledShellSurfaceTest, SetTopInset) {
158 gfx::Size buffer_size(64, 64);
159 std::unique_ptr<Buffer> buffer(
160 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
161 std::unique_ptr<Surface> surface(new Surface);
162 auto shell_surface =
163 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
164
165 surface->Attach(buffer.get());
166 surface->Commit();
167
168 aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
169 ASSERT_TRUE(window);
170 EXPECT_EQ(0, window->GetProperty(aura::client::kTopViewInset));
171 int top_inset_height = 20;
172 shell_surface->SetTopInset(top_inset_height);
173 surface->Commit();
174 EXPECT_EQ(top_inset_height, window->GetProperty(aura::client::kTopViewInset));
175 }
176
TEST_F(ClientControlledShellSurfaceTest,ModalWindowDefaultActive)177 TEST_F(ClientControlledShellSurfaceTest, ModalWindowDefaultActive) {
178 std::unique_ptr<Surface> surface(new Surface);
179 auto shell_surface =
180 exo_test_helper()->CreateClientControlledShellSurface(surface.get(),
181 /*is_modal=*/true);
182
183 gfx::Size desktop_size(640, 480);
184 std::unique_ptr<Buffer> desktop_buffer(
185 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(desktop_size)));
186 surface->Attach(desktop_buffer.get());
187 surface->SetInputRegion(gfx::Rect(10, 10, 100, 100));
188 ASSERT_FALSE(shell_surface->GetWidget());
189 shell_surface->SetSystemModal(true);
190 surface->Commit();
191
192 EXPECT_TRUE(ash::Shell::IsSystemModalWindowOpen());
193 EXPECT_TRUE(shell_surface->GetWidget()->IsActive());
194 }
195
TEST_F(ClientControlledShellSurfaceTest,UpdateModalWindow)196 TEST_F(ClientControlledShellSurfaceTest, UpdateModalWindow) {
197 std::unique_ptr<Surface> surface(new Surface);
198 auto shell_surface = exo_test_helper()->CreateClientControlledShellSurface(
199 surface.get(), /*is_modal=*/true);
200 gfx::Size desktop_size(640, 480);
201 std::unique_ptr<Buffer> desktop_buffer(
202 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(desktop_size)));
203 surface->Attach(desktop_buffer.get());
204 surface->SetInputRegion(cc::Region());
205 surface->Commit();
206
207 EXPECT_FALSE(ash::Shell::IsSystemModalWindowOpen());
208 EXPECT_FALSE(shell_surface->GetWidget()->IsActive());
209
210 // Creating a surface without input region should not make it modal.
211 std::unique_ptr<Display> display(new Display);
212 std::unique_ptr<Surface> child = display->CreateSurface();
213 gfx::Size buffer_size(128, 128);
214 std::unique_ptr<Buffer> child_buffer(
215 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
216 child->Attach(child_buffer.get());
217 std::unique_ptr<SubSurface> sub_surface(
218 display->CreateSubSurface(child.get(), surface.get()));
219 surface->SetSubSurfacePosition(child.get(), gfx::Point(10, 10));
220 child->Commit();
221 surface->Commit();
222 EXPECT_FALSE(ash::Shell::IsSystemModalWindowOpen());
223 EXPECT_FALSE(shell_surface->GetWidget()->IsActive());
224
225 // Making the surface opaque shouldn't make it modal either.
226 child->SetBlendMode(SkBlendMode::kSrc);
227 child->Commit();
228 surface->Commit();
229 EXPECT_FALSE(ash::Shell::IsSystemModalWindowOpen());
230 EXPECT_FALSE(shell_surface->GetWidget()->IsActive());
231
232 // Setting input regions won't make it modal either.
233 surface->SetInputRegion(gfx::Rect(10, 10, 100, 100));
234 surface->Commit();
235 EXPECT_FALSE(ash::Shell::IsSystemModalWindowOpen());
236 EXPECT_FALSE(shell_surface->GetWidget()->IsActive());
237
238 // Only SetSystemModal changes modality.
239 shell_surface->SetSystemModal(true);
240
241 EXPECT_TRUE(ash::Shell::IsSystemModalWindowOpen());
242 EXPECT_TRUE(shell_surface->GetWidget()->IsActive());
243
244 shell_surface->SetSystemModal(false);
245
246 EXPECT_FALSE(ash::Shell::IsSystemModalWindowOpen());
247 EXPECT_FALSE(shell_surface->GetWidget()->IsActive());
248
249 // If the non modal system window was active,
250 shell_surface->GetWidget()->Activate();
251 EXPECT_TRUE(shell_surface->GetWidget()->IsActive());
252
253 shell_surface->SetSystemModal(true);
254 EXPECT_TRUE(ash::Shell::IsSystemModalWindowOpen());
255 EXPECT_TRUE(shell_surface->GetWidget()->IsActive());
256
257 shell_surface->SetSystemModal(false);
258 EXPECT_FALSE(ash::Shell::IsSystemModalWindowOpen());
259 EXPECT_TRUE(shell_surface->GetWidget()->IsActive());
260 }
261
TEST_F(ClientControlledShellSurfaceTest,ModalWindowSetSystemModalBeforeCommit)262 TEST_F(ClientControlledShellSurfaceTest,
263 ModalWindowSetSystemModalBeforeCommit) {
264 std::unique_ptr<Surface> surface(new Surface);
265 auto shell_surface = exo_test_helper()->CreateClientControlledShellSurface(
266 surface.get(), /*is_modal=*/true);
267 gfx::Size desktop_size(640, 480);
268 std::unique_ptr<Buffer> desktop_buffer(
269 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(desktop_size)));
270 surface->Attach(desktop_buffer.get());
271 surface->SetInputRegion(cc::Region());
272
273 // Set SetSystemModal before any commit happens. Widget is not created at
274 // this time.
275 EXPECT_FALSE(shell_surface->GetWidget());
276 shell_surface->SetSystemModal(true);
277
278 surface->Commit();
279
280 // It is expected that modal window is shown.
281 EXPECT_TRUE(shell_surface->GetWidget());
282 EXPECT_TRUE(ash::Shell::IsSystemModalWindowOpen());
283
284 // Now widget is created and setting modal state should be applied
285 // immediately.
286 shell_surface->SetSystemModal(false);
287 EXPECT_FALSE(ash::Shell::IsSystemModalWindowOpen());
288 }
289
TEST_F(ClientControlledShellSurfaceTest,SurfaceShadow)290 TEST_F(ClientControlledShellSurfaceTest, SurfaceShadow) {
291 gfx::Size buffer_size(128, 128);
292 std::unique_ptr<Buffer> buffer(
293 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
294 std::unique_ptr<Surface> surface(new Surface);
295 auto shell_surface =
296 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
297 surface->Attach(buffer.get());
298 surface->Commit();
299
300 aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
301
302 // 1) Initial state, no shadow (SurfaceFrameType is NONE);
303 EXPECT_FALSE(wm::ShadowController::GetShadowForWindow(window));
304 std::unique_ptr<Display> display(new Display);
305
306 // 2) Just creating a sub surface won't create a shadow.
307 std::unique_ptr<Surface> child = display->CreateSurface();
308 std::unique_ptr<Buffer> child_buffer(
309 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
310 child->Attach(child_buffer.get());
311 std::unique_ptr<SubSurface> sub_surface(
312 display->CreateSubSurface(child.get(), surface.get()));
313 surface->Commit();
314
315 EXPECT_FALSE(wm::ShadowController::GetShadowForWindow(window));
316
317 // 3) Create a shadow.
318 surface->SetFrame(SurfaceFrameType::SHADOW);
319 shell_surface->SetShadowBounds(gfx::Rect(10, 10, 100, 100));
320 surface->Commit();
321 ui::Shadow* shadow = wm::ShadowController::GetShadowForWindow(window);
322 ASSERT_TRUE(shadow);
323 EXPECT_TRUE(shadow->layer()->visible());
324
325 gfx::Rect before = shadow->layer()->bounds();
326
327 // 4) Shadow bounds is independent of the sub surface.
328 gfx::Size new_buffer_size(256, 256);
329 std::unique_ptr<Buffer> new_child_buffer(
330 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(new_buffer_size)));
331 child->Attach(new_child_buffer.get());
332 child->Commit();
333 surface->Commit();
334
335 EXPECT_EQ(before, shadow->layer()->bounds());
336
337 // 4) Updating the widget's window bounds should not change the shadow bounds.
338 // TODO(oshima): The following scenario only worked with Xdg/ShellSurface,
339 // which never uses SetShadowBounds. This is broken with correct scenario, and
340 // will be fixed when the bounds control is delegated to the client.
341 //
342 // window->SetBounds(gfx::Rect(10, 10, 100, 100));
343 // EXPECT_EQ(before, shadow->layer()->bounds());
344
345 // 5) This should disable shadow.
346 shell_surface->SetShadowBounds(gfx::Rect());
347 surface->Commit();
348
349 EXPECT_EQ(wm::kShadowElevationNone, GetShadowElevation(window));
350 EXPECT_FALSE(shadow->layer()->visible());
351
352 // 6) This should enable non surface shadow again.
353 shell_surface->SetShadowBounds(gfx::Rect(10, 10, 100, 100));
354 surface->Commit();
355
356 EXPECT_EQ(wm::kShadowElevationDefault, GetShadowElevation(window));
357 EXPECT_TRUE(shadow->layer()->visible());
358 }
359
TEST_F(ClientControlledShellSurfaceTest,ShadowWithStateChange)360 TEST_F(ClientControlledShellSurfaceTest, ShadowWithStateChange) {
361 gfx::Size buffer_size(64, 64);
362 std::unique_ptr<Buffer> buffer(
363 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
364 std::unique_ptr<Surface> surface(new Surface);
365 auto shell_surface =
366 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
367
368 // Postion the widget at 10,10 so that we get non zero offset.
369 const gfx::Size content_size(100, 100);
370 const gfx::Rect original_bounds(gfx::Point(10, 10), content_size);
371 shell_surface->SetGeometry(original_bounds);
372 surface->Attach(buffer.get());
373 surface->SetFrame(SurfaceFrameType::SHADOW);
374 surface->Commit();
375
376 // In parent coordinates.
377 const gfx::Rect shadow_bounds(gfx::Point(-10, -10), content_size);
378
379 views::Widget* widget = shell_surface->GetWidget();
380 aura::Window* window = widget->GetNativeWindow();
381 ui::Shadow* shadow = wm::ShadowController::GetShadowForWindow(window);
382
383 shell_surface->SetShadowBounds(shadow_bounds);
384 surface->Commit();
385 EXPECT_EQ(wm::kShadowElevationDefault, GetShadowElevation(window));
386
387 EXPECT_TRUE(shadow->layer()->visible());
388 // Origin must be in sync.
389 EXPECT_EQ(shadow_bounds.origin(), shadow->content_bounds().origin());
390
391 const gfx::Rect work_area =
392 display::Screen::GetScreen()->GetPrimaryDisplay().work_area();
393 // Maximizing window hides the shadow.
394 widget->Maximize();
395 ASSERT_TRUE(widget->IsMaximized());
396 EXPECT_FALSE(shadow->layer()->visible());
397
398 shell_surface->SetShadowBounds(work_area);
399 surface->Commit();
400 EXPECT_FALSE(shadow->layer()->visible());
401
402 // Restoring bounds will re-enable shadow. It's content size is set to work
403 // area,/ thus not visible until new bounds is committed.
404 widget->Restore();
405 EXPECT_TRUE(shadow->layer()->visible());
406 EXPECT_EQ(work_area, shadow->content_bounds());
407
408 // The bounds is updated.
409 shell_surface->SetShadowBounds(shadow_bounds);
410 surface->Commit();
411 EXPECT_EQ(shadow_bounds, shadow->content_bounds());
412 }
413
TEST_F(ClientControlledShellSurfaceTest,ShadowWithTransform)414 TEST_F(ClientControlledShellSurfaceTest, ShadowWithTransform) {
415 gfx::Size buffer_size(64, 64);
416 std::unique_ptr<Buffer> buffer(
417 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
418 std::unique_ptr<Surface> surface(new Surface);
419 auto shell_surface =
420 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
421
422 // Postion the widget at 10,10 so that we get non zero offset.
423 const gfx::Size content_size(100, 100);
424 const gfx::Rect original_bounds(gfx::Point(10, 10), content_size);
425 shell_surface->SetGeometry(original_bounds);
426 surface->Attach(buffer.get());
427 surface->SetFrame(SurfaceFrameType::SHADOW);
428 surface->Commit();
429
430 aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
431 ui::Shadow* shadow = wm::ShadowController::GetShadowForWindow(window);
432
433 // In parent coordinates.
434 const gfx::Rect shadow_bounds(gfx::Point(-10, -10), content_size);
435
436 // Shadow bounds relative to its parent should not be affected by a transform.
437 gfx::Transform transform;
438 transform.Translate(50, 50);
439 window->SetTransform(transform);
440 shell_surface->SetShadowBounds(shadow_bounds);
441 surface->Commit();
442 EXPECT_TRUE(shadow->layer()->visible());
443 EXPECT_EQ(gfx::Rect(-10, -10, 100, 100), shadow->content_bounds());
444 }
445
TEST_F(ClientControlledShellSurfaceTest,ShadowStartMaximized)446 TEST_F(ClientControlledShellSurfaceTest, ShadowStartMaximized) {
447 gfx::Size buffer_size(256, 256);
448 std::unique_ptr<Buffer> buffer(
449 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
450
451 std::unique_ptr<Surface> surface(new Surface);
452
453 auto shell_surface =
454 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
455 shell_surface->SetMaximized();
456 surface->Attach(buffer.get());
457 surface->SetFrame(SurfaceFrameType::SHADOW);
458 surface->Commit();
459
460 views::Widget* widget = shell_surface->GetWidget();
461 aura::Window* window = widget->GetNativeWindow();
462
463 // There is no shadow when started in maximized state.
464 EXPECT_FALSE(wm::ShadowController::GetShadowForWindow(window));
465
466 // Sending a shadow bounds in maximized state won't create a shadow.
467 shell_surface->SetShadowBounds(gfx::Rect(10, 10, 100, 100));
468 surface->Commit();
469 EXPECT_FALSE(wm::ShadowController::GetShadowForWindow(window));
470
471 // Restore the window and make sure the shadow is created, visible and
472 // has the latest bounds.
473 widget->Restore();
474 ui::Shadow* shadow = wm::ShadowController::GetShadowForWindow(window);
475 ASSERT_TRUE(shadow);
476 EXPECT_TRUE(shadow->layer()->visible());
477 EXPECT_EQ(gfx::Rect(10, 10, 100, 100), shadow->content_bounds());
478 }
479
TEST_F(ClientControlledShellSurfaceTest,Frame)480 TEST_F(ClientControlledShellSurfaceTest, Frame) {
481 UpdateDisplay("800x600");
482
483 gfx::Size buffer_size(256, 256);
484 std::unique_ptr<Buffer> buffer(
485 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
486
487 int64_t display_id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
488 display::DisplayManager* display_manager =
489 ash::Shell::Get()->display_manager();
490
491 std::unique_ptr<Surface> surface(new Surface);
492
493 gfx::Rect client_bounds(20, 50, 300, 200);
494 gfx::Rect fullscreen_bounds(0, 0, 800, 600);
495 // The window bounds is the client bounds + frame size.
496 gfx::Rect normal_window_bounds(20, 18, 300, 232);
497
498 auto shell_surface =
499 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
500 shell_surface->SetSystemUiVisibility(true); // disable shelf.
501
502 surface->Attach(buffer.get());
503 shell_surface->SetGeometry(client_bounds);
504 surface->SetFrame(SurfaceFrameType::NORMAL);
505 surface->Commit();
506
507 views::Widget* widget = shell_surface->GetWidget();
508 ash::NonClientFrameViewAsh* frame_view =
509 static_cast<ash::NonClientFrameViewAsh*>(
510 widget->non_client_view()->frame_view());
511
512 // Normal state.
513 widget->LayoutRootViewIfNecessary();
514 EXPECT_TRUE(frame_view->GetVisible());
515 EXPECT_EQ(normal_window_bounds, widget->GetWindowBoundsInScreen());
516 EXPECT_EQ(client_bounds,
517 frame_view->GetClientBoundsForWindowBounds(normal_window_bounds));
518
519 // Maximized
520 shell_surface->SetMaximized();
521 shell_surface->SetGeometry(gfx::Rect(0, 0, 800, 568));
522 surface->Commit();
523
524 widget->LayoutRootViewIfNecessary();
525 EXPECT_TRUE(frame_view->GetVisible());
526 EXPECT_EQ(fullscreen_bounds, widget->GetWindowBoundsInScreen());
527 EXPECT_EQ(
528 gfx::Size(800, 568),
529 frame_view->GetClientBoundsForWindowBounds(fullscreen_bounds).size());
530
531 // With work area top insets.
532 display_manager->UpdateWorkAreaOfDisplay(display_id,
533 gfx::Insets(200, 0, 0, 0));
534 shell_surface->SetGeometry(gfx::Rect(0, 0, 800, 368));
535 surface->Commit();
536
537 widget->LayoutRootViewIfNecessary();
538 EXPECT_TRUE(frame_view->GetVisible());
539 EXPECT_EQ(gfx::Rect(0, 200, 800, 400), widget->GetWindowBoundsInScreen());
540
541 display_manager->UpdateWorkAreaOfDisplay(display_id, gfx::Insets(0, 0, 0, 0));
542
543 // AutoHide
544 surface->SetFrame(SurfaceFrameType::AUTOHIDE);
545 shell_surface->SetGeometry(fullscreen_bounds);
546 surface->Commit();
547
548 widget->LayoutRootViewIfNecessary();
549 EXPECT_TRUE(frame_view->GetVisible());
550 EXPECT_EQ(fullscreen_bounds, widget->GetWindowBoundsInScreen());
551 EXPECT_EQ(fullscreen_bounds,
552 frame_view->GetClientBoundsForWindowBounds(fullscreen_bounds));
553
554 // Fullscreen state.
555 shell_surface->SetFullscreen(true);
556 surface->Commit();
557
558 widget->LayoutRootViewIfNecessary();
559 EXPECT_TRUE(frame_view->GetVisible());
560 EXPECT_EQ(fullscreen_bounds, widget->GetWindowBoundsInScreen());
561 EXPECT_EQ(fullscreen_bounds,
562 frame_view->GetClientBoundsForWindowBounds(fullscreen_bounds));
563
564 // Updating frame, then window state should still update the frame state.
565 surface->SetFrame(SurfaceFrameType::NORMAL);
566 surface->Commit();
567
568 widget->LayoutRootViewIfNecessary();
569 EXPECT_FALSE(frame_view->GetHeaderView()->GetVisible());
570
571 shell_surface->SetMaximized();
572 surface->Commit();
573
574 widget->LayoutRootViewIfNecessary();
575 EXPECT_TRUE(frame_view->GetHeaderView()->GetVisible());
576
577 // Restore to normal state.
578 shell_surface->SetRestored();
579 shell_surface->SetGeometry(client_bounds);
580 surface->SetFrame(SurfaceFrameType::NORMAL);
581 surface->Commit();
582
583 widget->LayoutRootViewIfNecessary();
584 EXPECT_TRUE(frame_view->GetVisible());
585 EXPECT_EQ(normal_window_bounds, widget->GetWindowBoundsInScreen());
586 EXPECT_EQ(client_bounds,
587 frame_view->GetClientBoundsForWindowBounds(normal_window_bounds));
588
589 // No frame. The all bounds are same as client bounds.
590 shell_surface->SetRestored();
591 shell_surface->SetGeometry(client_bounds);
592 surface->SetFrame(SurfaceFrameType::NONE);
593 surface->Commit();
594
595 widget->LayoutRootViewIfNecessary();
596 EXPECT_FALSE(frame_view->GetVisible());
597 EXPECT_EQ(client_bounds, widget->GetWindowBoundsInScreen());
598 EXPECT_EQ(client_bounds,
599 frame_view->GetClientBoundsForWindowBounds(client_bounds));
600
601 // Test NONE -> AUTOHIDE -> NONE
602 shell_surface->SetMaximized();
603 shell_surface->SetGeometry(fullscreen_bounds);
604 surface->SetFrame(SurfaceFrameType::AUTOHIDE);
605 surface->Commit();
606
607 widget->LayoutRootViewIfNecessary();
608 EXPECT_TRUE(frame_view->GetVisible());
609 EXPECT_TRUE(frame_view->GetHeaderView()->in_immersive_mode());
610
611 surface->SetFrame(SurfaceFrameType::NONE);
612 surface->Commit();
613
614 widget->LayoutRootViewIfNecessary();
615 EXPECT_FALSE(frame_view->GetVisible());
616 EXPECT_FALSE(frame_view->GetHeaderView()->in_immersive_mode());
617 }
618
619 namespace {
620
621 class TestEventHandler : public ui::EventHandler {
622 public:
623 TestEventHandler() = default;
624 ~TestEventHandler() override = default;
625
626 // ui::EventHandler:
OnMouseEvent(ui::MouseEvent * event)627 void OnMouseEvent(ui::MouseEvent* event) override { received_event_ = true; }
628
received_event() const629 bool received_event() const { return received_event_; }
630
631 private:
632 bool received_event_ = false;
633 DISALLOW_COPY_AND_ASSIGN(TestEventHandler);
634 };
635
636 } // namespace
637
TEST_F(ClientControlledShellSurfaceTest,NoSynthesizedEventOnFrameChange)638 TEST_F(ClientControlledShellSurfaceTest, NoSynthesizedEventOnFrameChange) {
639 UpdateDisplay("800x600");
640
641 gfx::Size buffer_size(256, 256);
642 std::unique_ptr<Buffer> buffer(
643 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
644 std::unique_ptr<Surface> surface(new Surface);
645
646 gfx::Rect fullscreen_bounds(0, 0, 800, 600);
647
648 auto shell_surface =
649 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
650 surface->Attach(buffer.get());
651 surface->SetFrame(SurfaceFrameType::NORMAL);
652 surface->Commit();
653
654 // Maximized
655 shell_surface->SetMaximized();
656 shell_surface->SetGeometry(fullscreen_bounds);
657 surface->Commit();
658
659 // AutoHide
660 base::RunLoop().RunUntilIdle();
661 aura::Env* env = aura::Env::GetInstance();
662 gfx::Rect cropped_fullscreen_bounds(0, 0, 800, 400);
663 env->SetLastMouseLocation(gfx::Point(100, 30));
664 TestEventHandler handler;
665 env->AddPreTargetHandler(&handler);
666 surface->SetFrame(SurfaceFrameType::AUTOHIDE);
667 shell_surface->SetGeometry(cropped_fullscreen_bounds);
668 surface->Commit();
669 base::RunLoop().RunUntilIdle();
670 EXPECT_FALSE(handler.received_event());
671 env->RemovePreTargetHandler(&handler);
672 }
673
TEST_F(ClientControlledShellSurfaceTest,CompositorLockInRotation)674 TEST_F(ClientControlledShellSurfaceTest, CompositorLockInRotation) {
675 UpdateDisplay("800x600");
676 const gfx::Size buffer_size(800, 600);
677 std::unique_ptr<Buffer> buffer(
678 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
679 std::unique_ptr<Surface> surface(new Surface);
680 auto shell_surface =
681 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
682 ash::Shell* shell = ash::Shell::Get();
683 shell->tablet_mode_controller()->SetEnabledForTest(true);
684
685 // Start in maximized.
686 shell_surface->SetMaximized();
687 surface->Attach(buffer.get());
688 surface->Commit();
689
690 gfx::Rect maximum_bounds =
691 display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
692 shell_surface->SetGeometry(maximum_bounds);
693 shell_surface->SetOrientation(Orientation::LANDSCAPE);
694 surface->Commit();
695
696 ui::Compositor* compositor =
697 shell_surface->GetWidget()->GetNativeWindow()->layer()->GetCompositor();
698
699 EXPECT_FALSE(compositor->IsLocked());
700
701 UpdateDisplay("800x600/r");
702
703 EXPECT_TRUE(compositor->IsLocked());
704
705 shell_surface->SetOrientation(Orientation::PORTRAIT);
706 surface->Commit();
707 shell_surface->DidReceiveCompositorFrameAck();
708
709 EXPECT_FALSE(compositor->IsLocked());
710 }
711
712 // If system tray is shown by click. It should be activated if user presses tab
713 // key while shell surface is active.
TEST_F(ClientControlledShellSurfaceTest,KeyboardNavigationWithUnifiedSystemTray)714 TEST_F(ClientControlledShellSurfaceTest,
715 KeyboardNavigationWithUnifiedSystemTray) {
716 const gfx::Size buffer_size(800, 600);
717 std::unique_ptr<Buffer> buffer(
718 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
719 std::unique_ptr<Surface> surface(new Surface());
720 auto shell_surface =
721 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
722
723 surface->Attach(buffer.get());
724 surface->Commit();
725
726 EXPECT_TRUE(shell_surface->GetWidget()->IsActive());
727
728 // Show system tray by performing a gesture tap at tray.
729 ash::UnifiedSystemTray* system_tray = GetPrimaryUnifiedSystemTray();
730 ui::GestureEvent tap(0, 0, 0, base::TimeTicks(),
731 ui::GestureEventDetails(ui::ET_GESTURE_TAP));
732 system_tray->PerformAction(tap);
733 ASSERT_TRUE(system_tray->GetWidget());
734
735 // Confirm that system tray is not active at this time.
736 EXPECT_TRUE(shell_surface->GetWidget()->IsActive());
737 EXPECT_FALSE(system_tray->IsBubbleActive());
738
739 // Send tab key event.
740 ui::test::EventGenerator* event_generator = GetEventGenerator();
741 event_generator->PressKey(ui::VKEY_TAB, ui::EF_NONE);
742 event_generator->ReleaseKey(ui::VKEY_TAB, ui::EF_NONE);
743
744 // Confirm that system tray is activated.
745 EXPECT_FALSE(shell_surface->GetWidget()->IsActive());
746 EXPECT_TRUE(system_tray->IsBubbleActive());
747 }
748
TEST_F(ClientControlledShellSurfaceTest,Maximize)749 TEST_F(ClientControlledShellSurfaceTest, Maximize) {
750 gfx::Size buffer_size(256, 256);
751 std::unique_ptr<Buffer> buffer(
752 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
753 std::unique_ptr<Surface> surface(new Surface);
754 auto shell_surface(
755 exo_test_helper()->CreateClientControlledShellSurface(surface.get()));
756
757 surface->Attach(buffer.get());
758 surface->Commit();
759 EXPECT_FALSE(HasBackdrop());
760 shell_surface->SetMaximized();
761 EXPECT_FALSE(HasBackdrop());
762 surface->Commit();
763 EXPECT_TRUE(HasBackdrop());
764 EXPECT_TRUE(shell_surface->GetWidget()->IsMaximized());
765
766 // We always show backdrop because the window may be cropped.
767 display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
768 shell_surface->SetGeometry(display.bounds());
769 surface->Commit();
770 EXPECT_TRUE(HasBackdrop());
771
772 shell_surface->SetGeometry(gfx::Rect(0, 0, 100, display.bounds().height()));
773 surface->Commit();
774 EXPECT_TRUE(HasBackdrop());
775
776 shell_surface->SetGeometry(gfx::Rect(0, 0, display.bounds().width(), 100));
777 surface->Commit();
778 EXPECT_TRUE(HasBackdrop());
779
780 // Toggle maximize.
781 ash::WMEvent maximize_event(ash::WM_EVENT_TOGGLE_MAXIMIZE);
782 aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
783
784 ash::WindowState::Get(window)->OnWMEvent(&maximize_event);
785 EXPECT_FALSE(shell_surface->GetWidget()->IsMaximized());
786 EXPECT_FALSE(HasBackdrop());
787
788 ash::WindowState::Get(window)->OnWMEvent(&maximize_event);
789 EXPECT_TRUE(shell_surface->GetWidget()->IsMaximized());
790 EXPECT_TRUE(HasBackdrop());
791 }
792
TEST_F(ClientControlledShellSurfaceTest,Restore)793 TEST_F(ClientControlledShellSurfaceTest, Restore) {
794 gfx::Size buffer_size(256, 256);
795 std::unique_ptr<Buffer> buffer(
796 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
797 std::unique_ptr<Surface> surface(new Surface);
798 auto shell_surface(
799 exo_test_helper()->CreateClientControlledShellSurface(surface.get()));
800
801 surface->Attach(buffer.get());
802 surface->Commit();
803 EXPECT_FALSE(HasBackdrop());
804 // Note: Remove contents to avoid issues with maximize animations in tests.
805 shell_surface->SetMaximized();
806 EXPECT_FALSE(HasBackdrop());
807 surface->Commit();
808 EXPECT_TRUE(HasBackdrop());
809
810 shell_surface->SetRestored();
811 EXPECT_TRUE(HasBackdrop());
812 surface->Commit();
813 EXPECT_FALSE(HasBackdrop());
814 }
815
TEST_F(ClientControlledShellSurfaceTest,SetFullscreen)816 TEST_F(ClientControlledShellSurfaceTest, SetFullscreen) {
817 gfx::Size buffer_size(256, 256);
818 std::unique_ptr<Buffer> buffer(
819 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
820 std::unique_ptr<Surface> surface(new Surface);
821 auto shell_surface(
822 exo_test_helper()->CreateClientControlledShellSurface(surface.get()));
823
824 shell_surface->SetFullscreen(true);
825 surface->Attach(buffer.get());
826 surface->Commit();
827 EXPECT_TRUE(HasBackdrop());
828
829 // We always show backdrop becaues the window can be cropped.
830 display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
831 shell_surface->SetGeometry(display.bounds());
832 surface->Commit();
833 EXPECT_TRUE(HasBackdrop());
834
835 shell_surface->SetGeometry(gfx::Rect(0, 0, 100, display.bounds().height()));
836 surface->Commit();
837 EXPECT_TRUE(HasBackdrop());
838
839 shell_surface->SetGeometry(gfx::Rect(0, 0, display.bounds().width(), 100));
840 surface->Commit();
841 EXPECT_TRUE(HasBackdrop());
842
843 shell_surface->SetFullscreen(false);
844 surface->Commit();
845 EXPECT_FALSE(HasBackdrop());
846 EXPECT_NE(GetContext()->bounds().ToString(),
847 shell_surface->GetWidget()->GetWindowBoundsInScreen().ToString());
848 }
849
TEST_F(ClientControlledShellSurfaceTest,ToggleFullscreen)850 TEST_F(ClientControlledShellSurfaceTest, ToggleFullscreen) {
851 gfx::Size buffer_size(256, 256);
852 std::unique_ptr<Buffer> buffer(
853 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
854 std::unique_ptr<Surface> surface(new Surface);
855 auto shell_surface(
856 exo_test_helper()->CreateClientControlledShellSurface(surface.get()));
857
858 surface->Attach(buffer.get());
859 surface->Commit();
860 EXPECT_FALSE(HasBackdrop());
861
862 shell_surface->SetMaximized();
863 surface->Commit();
864 EXPECT_TRUE(HasBackdrop());
865
866 ash::WMEvent event(ash::WM_EVENT_TOGGLE_FULLSCREEN);
867 aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
868
869 // Enter fullscreen mode.
870 ash::WindowState::Get(window)->OnWMEvent(&event);
871 EXPECT_TRUE(HasBackdrop());
872
873 // Leave fullscreen mode.
874 ash::WindowState::Get(window)->OnWMEvent(&event);
875 EXPECT_TRUE(HasBackdrop());
876 }
877
TEST_F(ClientControlledShellSurfaceTest,DefaultDeviceScaleFactorForcedScaleFactor)878 TEST_F(ClientControlledShellSurfaceTest,
879 DefaultDeviceScaleFactorForcedScaleFactor) {
880 double scale = 1.5;
881 display::Display::SetForceDeviceScaleFactor(scale);
882
883 int64_t display_id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
884 display::Display::SetInternalDisplayId(display_id);
885
886 gfx::Size buffer_size(64, 64);
887 std::unique_ptr<Buffer> buffer(
888 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
889 std::unique_ptr<Surface> surface(new Surface);
890 auto shell_surface(
891 exo_test_helper()->CreateClientControlledShellSurface(surface.get()));
892
893 surface->Attach(buffer.get());
894 surface->Commit();
895 gfx::Transform transform;
896 transform.Scale(1.0 / scale, 1.0 / scale);
897
898 EXPECT_EQ(
899 transform.ToString(),
900 shell_surface->host_window()->layer()->GetTargetTransform().ToString());
901 }
902
TEST_F(ClientControlledShellSurfaceTest,DefaultDeviceScaleFactorFromDisplayManager)903 TEST_F(ClientControlledShellSurfaceTest,
904 DefaultDeviceScaleFactorFromDisplayManager) {
905 int64_t display_id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
906 display::Display::SetInternalDisplayId(display_id);
907 gfx::Size size(1920, 1080);
908
909 display::DisplayManager* display_manager =
910 ash::Shell::Get()->display_manager();
911
912 double scale = 1.25;
913 display::ManagedDisplayMode mode(size, 60.f, false /* overscan */,
914 true /*native*/, scale);
915
916 display::ManagedDisplayInfo::ManagedDisplayModeList mode_list;
917 mode_list.push_back(mode);
918
919 display::ManagedDisplayInfo native_display_info(display_id, "test", false);
920 native_display_info.SetManagedDisplayModes(mode_list);
921
922 native_display_info.SetBounds(gfx::Rect(size));
923 native_display_info.set_device_scale_factor(scale);
924
925 std::vector<display::ManagedDisplayInfo> display_info_list;
926 display_info_list.push_back(native_display_info);
927
928 display_manager->OnNativeDisplaysChanged(display_info_list);
929 display_manager->UpdateInternalManagedDisplayModeListForTest();
930
931 gfx::Size buffer_size(64, 64);
932 std::unique_ptr<Buffer> buffer(
933 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
934 std::unique_ptr<Surface> surface(new Surface);
935 auto shell_surface(
936 exo_test_helper()->CreateClientControlledShellSurface(surface.get()));
937
938 surface->Attach(buffer.get());
939 surface->Commit();
940
941 gfx::Transform transform;
942 transform.Scale(1.0 / scale, 1.0 / scale);
943
944 EXPECT_EQ(
945 transform.ToString(),
946 shell_surface->host_window()->layer()->GetTargetTransform().ToString());
947 }
948
TEST_F(ClientControlledShellSurfaceTest,MouseAndTouchTarget)949 TEST_F(ClientControlledShellSurfaceTest, MouseAndTouchTarget) {
950 gfx::Size buffer_size(256, 256);
951 std::unique_ptr<Buffer> buffer(
952 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
953 std::unique_ptr<Surface> surface(new Surface);
954 auto shell_surface(
955 exo_test_helper()->CreateClientControlledShellSurface(surface.get()));
956
957 const gfx::Rect original_bounds(0, 0, 256, 256);
958 shell_surface->SetGeometry(original_bounds);
959 surface->Attach(buffer.get());
960 surface->Commit();
961
962 EXPECT_TRUE(shell_surface->CanResize());
963
964 aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
965 aura::Window* root = window->GetRootWindow();
966 ui::EventTargeter* targeter =
967 root->GetHost()->dispatcher()->GetDefaultEventTargeter();
968
969 gfx::Point mouse_location(256 + 5, 150);
970
971 ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, mouse_location, mouse_location,
972 ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
973 EXPECT_EQ(window, targeter->FindTargetForEvent(root, &mouse));
974
975 // Move 20px further away. Touch event can hit the window but
976 // mouse event will not.
977 gfx::Point touch_location(256 + 25, 150);
978 ui::MouseEvent touch(ui::ET_TOUCH_PRESSED, touch_location, touch_location,
979 ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
980 EXPECT_EQ(window, targeter->FindTargetForEvent(root, &touch));
981
982 ui::MouseEvent mouse_with_touch_loc(ui::ET_MOUSE_MOVED, touch_location,
983 touch_location, ui::EventTimeForNow(),
984 ui::EF_NONE, ui::EF_NONE);
985 EXPECT_FALSE(window->Contains(static_cast<aura::Window*>(
986 targeter->FindTargetForEvent(root, &mouse_with_touch_loc))));
987
988 // Touching futher away shouldn't hit the window.
989 gfx::Point no_touch_location(256 + 35, 150);
990 ui::MouseEvent no_touch(ui::ET_TOUCH_PRESSED, no_touch_location,
991 no_touch_location, ui::EventTimeForNow(), ui::EF_NONE,
992 ui::EF_NONE);
993 EXPECT_FALSE(window->Contains(static_cast<aura::Window*>(
994 targeter->FindTargetForEvent(root, &no_touch))));
995 }
996
997 // The shell surface in SystemModal container should be unresizable.
TEST_F(ClientControlledShellSurfaceTest,ShellSurfaceInSystemModalIsUnresizable)998 TEST_F(ClientControlledShellSurfaceTest,
999 ShellSurfaceInSystemModalIsUnresizable) {
1000 gfx::Size buffer_size(256, 256);
1001 std::unique_ptr<Buffer> buffer(
1002 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
1003 std::unique_ptr<Surface> surface(new Surface);
1004 auto shell_surface =
1005 exo_test_helper()->CreateClientControlledShellSurface(surface.get(),
1006 /*is_modal=*/true);
1007 surface->Attach(buffer.get());
1008 surface->Commit();
1009
1010 EXPECT_FALSE(shell_surface->GetWidget()->widget_delegate()->CanResize());
1011 }
1012
1013 // The shell surface in SystemModal container should not become target
1014 // at the edge.
TEST_F(ClientControlledShellSurfaceTest,ShellSurfaceInSystemModalHitTest)1015 TEST_F(ClientControlledShellSurfaceTest, ShellSurfaceInSystemModalHitTest) {
1016 std::unique_ptr<Surface> surface(new Surface);
1017 auto shell_surface =
1018 exo_test_helper()->CreateClientControlledShellSurface(surface.get(),
1019 /*is_modal=*/true);
1020 display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
1021
1022 gfx::Size desktop_size(640, 480);
1023 std::unique_ptr<Buffer> desktop_buffer(
1024 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(desktop_size)));
1025 surface->Attach(desktop_buffer.get());
1026 surface->SetInputRegion(gfx::Rect(0, 0, 0, 0));
1027 shell_surface->SetGeometry(display.bounds());
1028 surface->Commit();
1029
1030 EXPECT_FALSE(shell_surface->GetWidget()->widget_delegate()->CanResize());
1031 aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
1032 aura::Window* root = window->GetRootWindow();
1033
1034 ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(100, 0),
1035 gfx::Point(100, 0), ui::EventTimeForNow(), 0, 0);
1036 aura::WindowTargeter targeter;
1037 aura::Window* found =
1038 static_cast<aura::Window*>(targeter.FindTargetForEvent(root, &event));
1039 EXPECT_FALSE(window->Contains(found));
1040 }
1041
1042 // Test the snap functionalities in splitscreen in tablet mode.
TEST_F(ClientControlledShellSurfaceTest,SnapWindowInSplitViewModeTest)1043 TEST_F(ClientControlledShellSurfaceTest, SnapWindowInSplitViewModeTest) {
1044 UpdateDisplay("807x607");
1045 ash::Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
1046
1047 const gfx::Size buffer_size(800, 600);
1048 std::unique_ptr<Buffer> buffer1(
1049 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
1050 std::unique_ptr<Surface> surface1(new Surface);
1051 auto shell_surface1 =
1052 exo_test_helper()->CreateClientControlledShellSurface(surface1.get());
1053 // Start in maximized.
1054 shell_surface1->SetGeometry(gfx::Rect(0, 0, 800, 600));
1055 shell_surface1->SetMaximized();
1056 surface1->Attach(buffer1.get());
1057 surface1->Commit();
1058
1059 aura::Window* window1 = shell_surface1->GetWidget()->GetNativeWindow();
1060 ash::WindowState* window_state1 = ash::WindowState::Get(window1);
1061 ash::ClientControlledState* state1 = static_cast<ash::ClientControlledState*>(
1062 ash::WindowState::TestApi::GetStateImpl(window_state1));
1063 EXPECT_EQ(window_state1->GetStateType(), ash::WindowStateType::kMaximized);
1064
1065 // Snap window to left.
1066 ash::SplitViewController* split_view_controller =
1067 ash::SplitViewController::Get(ash::Shell::GetPrimaryRootWindow());
1068 split_view_controller->SnapWindow(window1, ash::SplitViewController::LEFT);
1069 state1->set_bounds_locally(true);
1070 window1->SetBounds(split_view_controller->GetSnappedWindowBoundsInScreen(
1071 ash::SplitViewController::LEFT, window1));
1072 state1->set_bounds_locally(false);
1073 EXPECT_EQ(window_state1->GetStateType(), ash::WindowStateType::kLeftSnapped);
1074 EXPECT_EQ(shell_surface1->GetWidget()->GetWindowBoundsInScreen(),
1075 split_view_controller->GetSnappedWindowBoundsInScreen(
1076 ash::SplitViewController::LEFT,
1077 shell_surface1->GetWidget()->GetNativeWindow()));
1078 EXPECT_TRUE(HasBackdrop());
1079 split_view_controller->EndSplitView();
1080
1081 // Snap window to right.
1082 split_view_controller->SnapWindow(window1, ash::SplitViewController::RIGHT);
1083 state1->set_bounds_locally(true);
1084 window1->SetBounds(split_view_controller->GetSnappedWindowBoundsInScreen(
1085 ash::SplitViewController::RIGHT, window1));
1086 state1->set_bounds_locally(false);
1087 EXPECT_EQ(window_state1->GetStateType(), ash::WindowStateType::kRightSnapped);
1088 EXPECT_EQ(shell_surface1->GetWidget()->GetWindowBoundsInScreen(),
1089 split_view_controller->GetSnappedWindowBoundsInScreen(
1090 ash::SplitViewController::RIGHT,
1091 shell_surface1->GetWidget()->GetNativeWindow()));
1092 EXPECT_TRUE(HasBackdrop());
1093 }
1094
1095 // The shell surface in SystemModal container should not become target
1096 // at the edge.
TEST_F(ClientControlledShellSurfaceTest,ClientIniatedResize)1097 TEST_F(ClientControlledShellSurfaceTest, ClientIniatedResize) {
1098 std::unique_ptr<Surface> surface(new Surface);
1099 auto shell_surface =
1100 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
1101 display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
1102
1103 gfx::Size window_size(100, 100);
1104 std::unique_ptr<Buffer> desktop_buffer(
1105 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(window_size)));
1106 surface->Attach(desktop_buffer.get());
1107 shell_surface->SetGeometry(gfx::Rect(window_size));
1108 surface->Commit();
1109 EXPECT_TRUE(shell_surface->GetWidget()->widget_delegate()->CanResize());
1110 shell_surface->StartDrag(HTTOP, gfx::PointF(0, 0));
1111
1112 aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
1113 // Client cannot start drag if mouse isn't pressed.
1114 ash::WindowState* window_state = ash::WindowState::Get(window);
1115 ASSERT_FALSE(window_state->is_dragged());
1116
1117 // Client can start drag only when the mouse is pressed on the widget.
1118 ui::test::EventGenerator* event_generator = GetEventGenerator();
1119 event_generator->MoveMouseToCenterOf(window);
1120 event_generator->PressLeftButton();
1121 shell_surface->StartDrag(HTTOP, gfx::PointF(0, 0));
1122 ASSERT_TRUE(window_state->is_dragged());
1123 event_generator->ReleaseLeftButton();
1124 ASSERT_FALSE(window_state->is_dragged());
1125
1126 // Press pressed outside of the window.
1127 event_generator->MoveMouseTo(gfx::Point(200, 50));
1128 event_generator->PressLeftButton();
1129 shell_surface->StartDrag(HTTOP, gfx::PointF(0, 0));
1130 ASSERT_FALSE(window_state->is_dragged());
1131 }
1132
1133 namespace {
1134
1135 // This class is only meant to used by CloseWindowWhenDraggingTest.
1136 // When a ClientControlledShellSurface is destroyed, its natvie window will be
1137 // hidden first and at that time its window delegate should have been properly
1138 // reset.
1139 class ShellSurfaceWindowObserver : public aura::WindowObserver {
1140 public:
ShellSurfaceWindowObserver(aura::Window * window)1141 explicit ShellSurfaceWindowObserver(aura::Window* window)
1142 : window_(window),
1143 has_delegate_(ash::WindowState::Get(window)->HasDelegate()) {
1144 window_->AddObserver(this);
1145 }
~ShellSurfaceWindowObserver()1146 ~ShellSurfaceWindowObserver() override {
1147 if (window_) {
1148 window_->RemoveObserver(this);
1149 window_ = nullptr;
1150 }
1151 }
1152
has_delegate() const1153 bool has_delegate() const { return has_delegate_; }
1154
1155 // aura::WindowObserver:
OnWindowVisibilityChanged(aura::Window * window,bool visible)1156 void OnWindowVisibilityChanged(aura::Window* window, bool visible) override {
1157 DCHECK_EQ(window_, window);
1158
1159 if (!visible) {
1160 has_delegate_ = ash::WindowState::Get(window_)->HasDelegate();
1161 window_->RemoveObserver(this);
1162 window_ = nullptr;
1163 }
1164 }
1165
1166 private:
1167 aura::Window* window_;
1168 bool has_delegate_;
1169
1170 DISALLOW_COPY_AND_ASSIGN(ShellSurfaceWindowObserver);
1171 };
1172
1173 } // namespace
1174
1175 // Test that when a shell surface is destroyed during its dragging, its window
1176 // delegate should be reset properly.
TEST_F(ClientControlledShellSurfaceTest,CloseWindowWhenDraggingTest)1177 TEST_F(ClientControlledShellSurfaceTest, CloseWindowWhenDraggingTest) {
1178 gfx::Size buffer_size(256, 256);
1179 std::unique_ptr<Buffer> buffer(
1180 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
1181 std::unique_ptr<Surface> surface(new Surface());
1182 auto shell_surface =
1183 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
1184
1185 const gfx::Rect original_bounds(0, 0, 256, 256);
1186 shell_surface->SetGeometry(original_bounds);
1187 surface->Attach(buffer.get());
1188 surface->Commit();
1189
1190 // Press on the edge of the window and start dragging.
1191 gfx::Point touch_location(256, 150);
1192 ui::test::EventGenerator* event_generator = GetEventGenerator();
1193 event_generator->MoveTouch(touch_location);
1194 event_generator->PressTouch();
1195
1196 aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
1197 EXPECT_TRUE(ash::WindowState::Get(window)->is_dragged());
1198 auto observer = std::make_unique<ShellSurfaceWindowObserver>(window);
1199 EXPECT_TRUE(observer->has_delegate());
1200
1201 // Destroy the window.
1202 shell_surface.reset();
1203 EXPECT_FALSE(observer->has_delegate());
1204 }
1205
1206 namespace {
1207
1208 class ClientControlledShellSurfaceDragTest : public test::ExoTestBase {
1209 public:
1210 ClientControlledShellSurfaceDragTest() = default;
1211 ~ClientControlledShellSurfaceDragTest() override = default;
1212
1213 // Sends a gesture scroll sequence to TabletModeAppWindowDragController.
SendGestureEvents(aura::Window * window,const gfx::Point & location,bool fling=false,float velocity=0.f)1214 void SendGestureEvents(aura::Window* window,
1215 const gfx::Point& location,
1216 bool fling = false,
1217 float velocity = 0.f) {
1218 ash::WindowState* window_state = ash::WindowState::Get(window);
1219 window_state->CreateDragDetails(gfx::PointF(0, 0), HTCLIENT,
1220 ::wm::WINDOW_MOVE_SOURCE_TOUCH);
1221 std::unique_ptr<ash::TabletModeWindowResizer> controller_ =
1222 std::make_unique<ash::TabletModeWindowResizer>(
1223 window_state,
1224 std::make_unique<ash::TabletModeBrowserWindowDragDelegate>());
1225 controller_->drag_delegate_for_testing()
1226 ->set_drag_start_deadline_for_testing(base::Time::Now());
1227 controller_->Drag(gfx::PointF(location), 0);
1228 if (fling) {
1229 ui::GestureEventDetails details =
1230 ui::GestureEventDetails(ui::ET_SCROLL_FLING_START, 0, velocity);
1231 ui::GestureEvent event =
1232 ui::GestureEvent(location.x(), location.y(), ui::EF_NONE,
1233 base::TimeTicks::Now(), details);
1234 ui::Event::DispatcherApi(&event).set_target(window);
1235 controller_->FlingOrSwipe(&event);
1236 } else {
1237 controller_->CompleteDrag();
1238 }
1239 ash::WindowState::Get(window)->DeleteDragDetails();
1240 }
1241
1242 private:
1243 DISALLOW_COPY_AND_ASSIGN(ClientControlledShellSurfaceDragTest);
1244 };
1245
1246 } // namespace
1247
1248 // Test the functionalities of dragging a window from top in tablet mode.
TEST_F(ClientControlledShellSurfaceDragTest,DragWindowFromTopInTabletMode)1249 TEST_F(ClientControlledShellSurfaceDragTest, DragWindowFromTopInTabletMode) {
1250 UpdateDisplay("800x600");
1251 ash::Shell* shell = ash::Shell::Get();
1252 shell->tablet_mode_controller()->SetEnabledForTest(true);
1253 std::unique_ptr<Surface> surface(new Surface());
1254 const gfx::Size window_size(800, 552);
1255 std::unique_ptr<Buffer> buffer(
1256 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(window_size)));
1257 auto shell_surface =
1258 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
1259 shell_surface->SetMaximized();
1260 surface->Attach(buffer.get());
1261 shell_surface->SetGeometry(gfx::Rect(window_size));
1262 surface->Commit();
1263
1264 aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
1265 ASSERT_TRUE(ash::WindowState::Get(window)->IsMaximized());
1266 surface->SetFrame(SurfaceFrameType::AUTOHIDE);
1267 surface->Commit();
1268
1269 // Drag the window by a small amount of distance will maximize the window
1270 // again.
1271 SendGestureEvents(window, gfx::Point(0, 10));
1272 EXPECT_TRUE(ash::WindowState::Get(window)->IsMaximized());
1273 EXPECT_FALSE(shell->overview_controller()->InOverviewSession());
1274
1275 // FLING the window not inisde preview area with large enough y veloicty
1276 // (larger than kFlingToOverviewThreshold) will drop the window into overview.
1277 SendGestureEvents(
1278 window, gfx::Point(400, 10), /*fling=*/true,
1279 ash::TabletModeWindowDragDelegate::kFlingToOverviewThreshold + 10.f);
1280 ASSERT_TRUE(shell->overview_controller()->InOverviewSession());
1281 EXPECT_TRUE(
1282 shell->overview_controller()->overview_session()->IsWindowInOverview(
1283 window));
1284
1285 // Drag the window long enough (pass one fourth of the screen vertical
1286 // height) to snap the window to splitscreen.
1287 shell->overview_controller()->EndOverview();
1288 SendGestureEvents(window, gfx::Point(0, 210));
1289 EXPECT_EQ(ash::WindowState::Get(window)->GetStateType(),
1290 ash::WindowStateType::kLeftSnapped);
1291 }
1292
1293 namespace {
1294
1295 class ClientControlledShellSurfaceDisplayTest : public test::ExoTestBase {
1296 public:
1297 ClientControlledShellSurfaceDisplayTest() = default;
1298 ~ClientControlledShellSurfaceDisplayTest() override = default;
1299
CreateDragWindowResizer(aura::Window * window,const gfx::Point & point_in_parent,int window_component)1300 static ash::WindowResizer* CreateDragWindowResizer(
1301 aura::Window* window,
1302 const gfx::Point& point_in_parent,
1303 int window_component) {
1304 return ash::CreateWindowResizer(window, gfx::PointF(point_in_parent),
1305 window_component,
1306 ::wm::WINDOW_MOVE_SOURCE_MOUSE)
1307 .release();
1308 }
1309
bounds_change_count() const1310 int bounds_change_count() const { return bounds_change_count_; }
1311
requested_bounds() const1312 const std::vector<gfx::Rect>& requested_bounds() const {
1313 return requested_bounds_;
1314 }
1315
requested_display_ids() const1316 const std::vector<int64_t>& requested_display_ids() const {
1317 return requested_display_ids_;
1318 }
1319
OnBoundsChangeEvent(ClientControlledShellSurface * shell_surface,ash::WindowStateType current_state,ash::WindowStateType requested_state,int64_t display_id,const gfx::Rect & bounds_in_display,bool is_resize,int bounds_change)1320 void OnBoundsChangeEvent(ClientControlledShellSurface* shell_surface,
1321 ash::WindowStateType current_state,
1322 ash::WindowStateType requested_state,
1323 int64_t display_id,
1324 const gfx::Rect& bounds_in_display,
1325 bool is_resize,
1326 int bounds_change) {
1327 bounds_change_count_++;
1328 requested_bounds_.push_back(bounds_in_display);
1329 requested_display_ids_.push_back(display_id);
1330 }
1331
Reset()1332 void Reset() {
1333 bounds_change_count_ = 0;
1334 requested_bounds_.clear();
1335 requested_display_ids_.clear();
1336 }
1337
CalculateDragPoint(const ash::WindowResizer & resizer,int delta_x,int delta_y)1338 gfx::PointF CalculateDragPoint(const ash::WindowResizer& resizer,
1339 int delta_x,
1340 int delta_y) {
1341 gfx::PointF location = resizer.GetInitialLocation();
1342 location.set_x(location.x() + delta_x);
1343 location.set_y(location.y() + delta_y);
1344 return location;
1345 }
1346
1347 private:
1348 int bounds_change_count_ = 0;
1349 std::vector<gfx::Rect> requested_bounds_;
1350 std::vector<int64_t> requested_display_ids_;
1351
1352 DISALLOW_COPY_AND_ASSIGN(ClientControlledShellSurfaceDisplayTest);
1353 };
1354
1355 } // namespace
1356
TEST_F(ClientControlledShellSurfaceDisplayTest,MoveToAnotherDisplayByDrag)1357 TEST_F(ClientControlledShellSurfaceDisplayTest, MoveToAnotherDisplayByDrag) {
1358 UpdateDisplay("800x600,800x600");
1359 aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
1360 std::unique_ptr<Surface> surface(new Surface);
1361 auto shell_surface =
1362 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
1363
1364 gfx::Size window_size(200, 200);
1365 std::unique_ptr<Buffer> desktop_buffer(
1366 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(window_size)));
1367 surface->Attach(desktop_buffer.get());
1368
1369 display::Display primary_display =
1370 display::Screen::GetScreen()->GetPrimaryDisplay();
1371 gfx::Rect initial_bounds(-150, 10, 200, 200);
1372 shell_surface->SetBounds(primary_display.id(), initial_bounds);
1373 surface->Commit();
1374 shell_surface->GetWidget()->Show();
1375
1376 EXPECT_EQ(initial_bounds,
1377 shell_surface->GetWidget()->GetWindowBoundsInScreen());
1378
1379 aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
1380 EXPECT_EQ(root_windows[0], window->GetRootWindow());
1381 // Prevent snapping |window|. It only distracts from the purpose of the test.
1382 // TODO: Remove this code after adding functionality where the mouse has to
1383 // dwell in the snap region before the dragged window can get snapped.
1384 window->SetProperty(aura::client::kResizeBehaviorKey,
1385 aura::client::kResizeBehaviorNone);
1386 ASSERT_FALSE(ash::WindowState::Get(window)->CanSnap());
1387
1388 std::unique_ptr<ash::WindowResizer> resizer(
1389 CreateDragWindowResizer(window, gfx::Point(), HTCAPTION));
1390
1391 // Drag the pointer to the right. Once it reaches the right edge of the
1392 // primary display, it warps to the secondary.
1393 display::Display secondary_display =
1394 display::Screen::GetScreen()->GetDisplayNearestWindow(root_windows[1]);
1395 // TODO(crbug.com/990589): Unit tests should be able to simulate mouse input
1396 // without having to call |CursorManager::SetDisplay|.
1397 ash::Shell::Get()->cursor_manager()->SetDisplay(secondary_display);
1398 resizer->Drag(CalculateDragPoint(*resizer, 800, 0), 0);
1399
1400 shell_surface->set_bounds_changed_callback(base::BindRepeating(
1401 &ClientControlledShellSurfaceDisplayTest::OnBoundsChangeEvent,
1402 base::Unretained(this), base::Unretained(shell_surface.get())));
1403 resizer->CompleteDrag();
1404
1405 EXPECT_EQ(root_windows[1], window->GetRootWindow());
1406 // TODO(oshima): We currently generate bounds change twice,
1407 // first when reparented, then set bounds. Chagne wm::SetBoundsInScreen
1408 // to simply request WM_EVENT_SET_BOUND with target display id.
1409 ASSERT_EQ(2, bounds_change_count());
1410 // Bounds is local to 2nd display.
1411 EXPECT_EQ(gfx::Rect(-150, 10, 200, 200), requested_bounds()[0]);
1412 EXPECT_EQ(gfx::Rect(-150, 10, 200, 200), requested_bounds()[1]);
1413
1414 EXPECT_EQ(secondary_display.id(), requested_display_ids()[0]);
1415 EXPECT_EQ(secondary_display.id(), requested_display_ids()[1]);
1416 }
1417
TEST_F(ClientControlledShellSurfaceDisplayTest,MoveToAnotherDisplayByShortcut)1418 TEST_F(ClientControlledShellSurfaceDisplayTest,
1419 MoveToAnotherDisplayByShortcut) {
1420 UpdateDisplay("400x600,800x600");
1421 aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
1422 std::unique_ptr<Surface> surface(new Surface);
1423 auto shell_surface =
1424 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
1425
1426 gfx::Size window_size(200, 200);
1427 std::unique_ptr<Buffer> desktop_buffer(
1428 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(window_size)));
1429 surface->Attach(desktop_buffer.get());
1430
1431 display::Display primary_display =
1432 display::Screen::GetScreen()->GetPrimaryDisplay();
1433
1434 gfx::Rect initial_bounds(-174, 10, 200, 200);
1435 shell_surface->SetBounds(primary_display.id(), initial_bounds);
1436 surface->Commit();
1437 shell_surface->GetWidget()->Show();
1438
1439 EXPECT_EQ(initial_bounds,
1440 shell_surface->GetWidget()->GetWindowBoundsInScreen());
1441
1442 aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
1443 EXPECT_EQ(root_windows[0], window->GetRootWindow());
1444
1445 shell_surface->set_bounds_changed_callback(base::BindRepeating(
1446 &ClientControlledShellSurfaceDisplayTest::OnBoundsChangeEvent,
1447 base::Unretained(this), base::Unretained(shell_surface.get())));
1448
1449 display::Display secondary_display =
1450 display::Screen::GetScreen()->GetDisplayNearestWindow(root_windows[1]);
1451
1452 EXPECT_TRUE(
1453 ash::window_util::MoveWindowToDisplay(window, secondary_display.id()));
1454
1455 ASSERT_EQ(1, bounds_change_count());
1456 EXPECT_EQ(gfx::Rect(-174, 10, 200, 200), requested_bounds()[0]);
1457 EXPECT_EQ(secondary_display.id(), requested_display_ids()[0]);
1458
1459 gfx::Rect secondary_position(700, 10, 200, 200);
1460 shell_surface->SetBounds(secondary_display.id(), secondary_position);
1461 surface->Commit();
1462 EXPECT_EQ(gfx::Rect(1100, 10, 200, 200), window->GetBoundsInScreen());
1463
1464 Reset();
1465
1466 // Moving to the outside of another display.
1467 EXPECT_TRUE(
1468 ash::window_util::MoveWindowToDisplay(window, primary_display.id()));
1469 ASSERT_EQ(1, bounds_change_count());
1470 // Should fit in the primary display.
1471 EXPECT_EQ(gfx::Rect(375, 10, 200, 200), requested_bounds()[0]);
1472 EXPECT_EQ(primary_display.id(), requested_display_ids()[0]);
1473 }
1474
TEST_F(ClientControlledShellSurfaceTest,CaptionButtonModel)1475 TEST_F(ClientControlledShellSurfaceTest, CaptionButtonModel) {
1476 std::unique_ptr<Surface> surface(new Surface);
1477 auto shell_surface =
1478 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
1479
1480 std::unique_ptr<Buffer> desktop_buffer(
1481 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(gfx::Size(64, 64))));
1482 surface->Attach(desktop_buffer.get());
1483 shell_surface->SetGeometry(gfx::Rect(0, 0, 64, 64));
1484 surface->Commit();
1485
1486 constexpr views::CaptionButtonIcon kAllButtons[] = {
1487 views::CAPTION_BUTTON_ICON_MINIMIZE,
1488 views::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE,
1489 views::CAPTION_BUTTON_ICON_CLOSE,
1490 views::CAPTION_BUTTON_ICON_BACK,
1491 views::CAPTION_BUTTON_ICON_MENU,
1492 };
1493 constexpr uint32_t kAllButtonMask =
1494 1 << views::CAPTION_BUTTON_ICON_MINIMIZE |
1495 1 << views::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE |
1496 1 << views::CAPTION_BUTTON_ICON_CLOSE |
1497 1 << views::CAPTION_BUTTON_ICON_BACK |
1498 1 << views::CAPTION_BUTTON_ICON_MENU;
1499
1500 ash::NonClientFrameViewAsh* frame_view =
1501 static_cast<ash::NonClientFrameViewAsh*>(
1502 shell_surface->GetWidget()->non_client_view()->frame_view());
1503 ash::FrameCaptionButtonContainerView* container =
1504 static_cast<ash::HeaderView*>(frame_view->GetHeaderView())
1505 ->caption_button_container();
1506
1507 // Visible
1508 for (auto visible : kAllButtons) {
1509 uint32_t visible_buttons = 1 << visible;
1510 shell_surface->SetFrameButtons(visible_buttons, 0);
1511 const ash::CaptionButtonModel* model = container->model();
1512 for (auto not_visible : kAllButtons) {
1513 if (not_visible != visible)
1514 EXPECT_FALSE(model->IsVisible(not_visible));
1515 }
1516 EXPECT_TRUE(model->IsVisible(visible));
1517 EXPECT_FALSE(model->IsEnabled(visible));
1518 }
1519
1520 // Enable
1521 for (auto enabled : kAllButtons) {
1522 uint32_t enabled_buttons = 1 << enabled;
1523 shell_surface->SetFrameButtons(kAllButtonMask, enabled_buttons);
1524 const ash::CaptionButtonModel* model = container->model();
1525 for (auto not_enabled : kAllButtons) {
1526 if (not_enabled != enabled)
1527 EXPECT_FALSE(model->IsEnabled(not_enabled));
1528 }
1529 EXPECT_TRUE(model->IsEnabled(enabled));
1530 EXPECT_TRUE(model->IsVisible(enabled));
1531 }
1532
1533 // Zoom mode
1534 EXPECT_FALSE(container->model()->InZoomMode());
1535 shell_surface->SetFrameButtons(
1536 kAllButtonMask | 1 << views::CAPTION_BUTTON_ICON_ZOOM, kAllButtonMask);
1537 EXPECT_TRUE(container->model()->InZoomMode());
1538 }
1539
1540 // Makes sure that the "extra title" is respected by the window frame. When not
1541 // set, there should be no text in the window frame, but the window's name
1542 // should still be set (for overview mode, accessibility, etc.). When the debug
1543 // text is set, the window frame should paint it.
TEST_F(ClientControlledShellSurfaceTest,SetExtraTitle)1544 TEST_F(ClientControlledShellSurfaceTest, SetExtraTitle) {
1545 std::unique_ptr<Buffer> buffer(
1546 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(gfx::Size(640, 64))));
1547 std::unique_ptr<Surface> surface(new Surface);
1548 auto shell_surface =
1549 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
1550 surface->Attach(buffer.get());
1551 surface->Commit();
1552 shell_surface->GetWidget()->Show();
1553
1554 const base::string16 window_title(base::ASCIIToUTF16("title"));
1555 shell_surface->SetTitle(window_title);
1556 const aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
1557 EXPECT_EQ(window_title, window->GetTitle());
1558 EXPECT_FALSE(
1559 shell_surface->GetWidget()->widget_delegate()->ShouldShowWindowTitle());
1560
1561 // Paints the frame and returns whether text was drawn. Unforunately the text
1562 // is a blob so its actual value can't be detected.
1563 auto paint_does_draw_text = [&shell_surface]() {
1564 TestCanvas canvas;
1565 shell_surface->OnSetFrame(SurfaceFrameType::NORMAL);
1566 ash::NonClientFrameViewAsh* frame_view =
1567 static_cast<ash::NonClientFrameViewAsh*>(
1568 shell_surface->GetWidget()->non_client_view()->frame_view());
1569 frame_view->SetVisible(true);
1570 // Paint to a layer so we can pass a root PaintInfo.
1571 frame_view->GetHeaderView()->SetPaintToLayer();
1572 gfx::Rect bounds(100, 100);
1573 auto list = base::MakeRefCounted<cc::DisplayItemList>();
1574 frame_view->GetHeaderView()->Paint(views::PaintInfo::CreateRootPaintInfo(
1575 ui::PaintContext(list.get(), 1.f, bounds, false), bounds.size()));
1576 list->Finalize();
1577 list->Raster(&canvas);
1578 return canvas.text_was_drawn();
1579 };
1580
1581 EXPECT_FALSE(paint_does_draw_text());
1582 EXPECT_FALSE(
1583 shell_surface->GetWidget()->widget_delegate()->ShouldShowWindowTitle());
1584
1585 // Setting the extra title/debug text won't change the window's title, but it
1586 // will be drawn by the frame header.
1587 shell_surface->SetExtraTitle(base::ASCIIToUTF16("extra"));
1588 EXPECT_EQ(window_title, window->GetTitle());
1589 EXPECT_TRUE(paint_does_draw_text());
1590 EXPECT_FALSE(
1591 shell_surface->GetWidget()->widget_delegate()->ShouldShowWindowTitle());
1592 }
1593
TEST_F(ClientControlledShellSurfaceTest,WideFrame)1594 TEST_F(ClientControlledShellSurfaceTest, WideFrame) {
1595 std::unique_ptr<Surface> surface(new Surface);
1596 auto shell_surface =
1597 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
1598
1599 std::unique_ptr<Buffer> desktop_buffer(
1600 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(gfx::Size(64, 64))));
1601 surface->Attach(desktop_buffer.get());
1602 surface->SetInputRegion(gfx::Rect(0, 0, 64, 64));
1603 shell_surface->SetGeometry(gfx::Rect(0, 0, 64, 64));
1604 shell_surface->SetMaximized();
1605 surface->SetFrame(SurfaceFrameType::NORMAL);
1606 surface->Commit();
1607
1608 auto* wide_frame = shell_surface->wide_frame_for_test();
1609 ASSERT_TRUE(wide_frame);
1610 EXPECT_FALSE(wide_frame->header_view()->in_immersive_mode());
1611
1612 // Check targeter is still CustomWindowTargeter.
1613 aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
1614
1615 ASSERT_TRUE(window->parent());
1616
1617 auto* custom_targeter = window->targeter();
1618 gfx::Point mouse_location(1, 50);
1619
1620 auto* root = window->GetRootWindow();
1621 aura::WindowTargeter targeter;
1622 aura::Window* target;
1623 {
1624 ui::MouseEvent event(ui::ET_MOUSE_MOVED, mouse_location, mouse_location,
1625 ui::EventTimeForNow(), 0, 0);
1626 target =
1627 static_cast<aura::Window*>(targeter.FindTargetForEvent(root, &event));
1628 }
1629 EXPECT_EQ(surface->window(), target);
1630
1631 // Disable input region and the targeter no longer find the surface.
1632 surface->SetInputRegion(gfx::Rect(0, 0, 0, 0));
1633 surface->Commit();
1634 {
1635 ui::MouseEvent event(ui::ET_MOUSE_MOVED, mouse_location, mouse_location,
1636 ui::EventTimeForNow(), 0, 0);
1637 target =
1638 static_cast<aura::Window*>(targeter.FindTargetForEvent(root, &event));
1639 }
1640 EXPECT_NE(surface->window(), target);
1641
1642 // Test AUTOHIDE -> NORMAL
1643 surface->SetFrame(SurfaceFrameType::AUTOHIDE);
1644 surface->Commit();
1645 EXPECT_TRUE(wide_frame->header_view()->in_immersive_mode());
1646
1647 surface->SetFrame(SurfaceFrameType::NORMAL);
1648 surface->Commit();
1649 EXPECT_FALSE(wide_frame->header_view()->in_immersive_mode());
1650
1651 EXPECT_EQ(custom_targeter, window->targeter());
1652
1653 // Test AUTOHIDE -> NONE
1654 surface->SetFrame(SurfaceFrameType::AUTOHIDE);
1655 surface->Commit();
1656 EXPECT_TRUE(wide_frame->header_view()->in_immersive_mode());
1657
1658 // Switching to NONE means no frame so it should delete wide frame.
1659 surface->SetFrame(SurfaceFrameType::NONE);
1660 surface->Commit();
1661 EXPECT_FALSE(shell_surface->wide_frame_for_test());
1662 {
1663 ui::MouseEvent event(ui::ET_MOUSE_MOVED, mouse_location, mouse_location,
1664 ui::EventTimeForNow(), 0, 0);
1665 target =
1666 static_cast<aura::Window*>(targeter.FindTargetForEvent(root, &event));
1667 }
1668 EXPECT_NE(surface->window(), target);
1669
1670 // Unmaximize it and the frame should be normal.
1671 shell_surface->SetRestored();
1672 surface->Commit();
1673
1674 EXPECT_FALSE(shell_surface->wide_frame_for_test());
1675 {
1676 ui::MouseEvent event(ui::ET_MOUSE_MOVED, mouse_location, mouse_location,
1677 ui::EventTimeForNow(), 0, 0);
1678 target =
1679 static_cast<aura::Window*>(targeter.FindTargetForEvent(root, &event));
1680 }
1681 EXPECT_NE(surface->window(), target);
1682
1683 // Re-enable input region and the targeter should find the surface again.
1684 surface->SetInputRegion(gfx::Rect(0, 0, 64, 64));
1685 surface->Commit();
1686 {
1687 ui::MouseEvent event(ui::ET_MOUSE_MOVED, mouse_location, mouse_location,
1688 ui::EventTimeForNow(), 0, 0);
1689 target =
1690 static_cast<aura::Window*>(targeter.FindTargetForEvent(root, &event));
1691 }
1692 EXPECT_EQ(surface->window(), target);
1693 }
1694
TEST_F(ClientControlledShellSurfaceTest,NoFrameOnModalContainer)1695 TEST_F(ClientControlledShellSurfaceTest, NoFrameOnModalContainer) {
1696 std::unique_ptr<Surface> surface(new Surface);
1697 auto shell_surface =
1698 exo_test_helper()->CreateClientControlledShellSurface(surface.get(),
1699 /*is_modal=*/true);
1700
1701 std::unique_ptr<Buffer> desktop_buffer(
1702 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(gfx::Size(64, 64))));
1703 surface->Attach(desktop_buffer.get());
1704 surface->SetFrame(SurfaceFrameType::NORMAL);
1705 surface->Commit();
1706 EXPECT_FALSE(shell_surface->frame_enabled());
1707 surface->SetFrame(SurfaceFrameType::AUTOHIDE);
1708 surface->Commit();
1709 EXPECT_FALSE(shell_surface->frame_enabled());
1710 }
1711
TEST_F(ClientControlledShellSurfaceTest,SetGeometryReparentsToDisplayOnFirstCommit)1712 TEST_F(ClientControlledShellSurfaceTest,
1713 SetGeometryReparentsToDisplayOnFirstCommit) {
1714 UpdateDisplay("100x100,100x100");
1715
1716 gfx::Size buffer_size(64, 64);
1717 std::unique_ptr<Buffer> buffer(
1718 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
1719
1720 const auto* screen = display::Screen::GetScreen();
1721
1722 {
1723 std::unique_ptr<Surface> surface(new Surface);
1724 auto shell_surface =
1725 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
1726
1727 gfx::Rect geometry(16, 16, 32, 32);
1728 shell_surface->SetGeometry(geometry);
1729 surface->Attach(buffer.get());
1730 surface->Commit();
1731 EXPECT_EQ(geometry, shell_surface->GetWidget()->GetWindowBoundsInScreen());
1732
1733 display::Display primary_display = screen->GetPrimaryDisplay();
1734 display::Display display = screen->GetDisplayNearestWindow(
1735 shell_surface->GetWidget()->GetNativeWindow());
1736 EXPECT_EQ(primary_display.id(), display.id());
1737 }
1738
1739 {
1740 std::unique_ptr<Surface> surface(new Surface);
1741 auto shell_surface =
1742 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
1743
1744 gfx::Rect geometry(116, 16, 32, 32);
1745 shell_surface->SetGeometry(geometry);
1746 surface->Attach(buffer.get());
1747 surface->Commit();
1748 EXPECT_EQ(geometry, shell_surface->GetWidget()->GetWindowBoundsInScreen());
1749
1750 auto root_windows = ash::Shell::GetAllRootWindows();
1751 display::Display secondary_display =
1752 screen->GetDisplayNearestWindow(root_windows[1]);
1753 display::Display display = screen->GetDisplayNearestWindow(
1754 shell_surface->GetWidget()->GetNativeWindow());
1755 EXPECT_EQ(secondary_display.id(), display.id());
1756 }
1757 }
1758
TEST_F(ClientControlledShellSurfaceTest,SetBoundsReparentsToDisplay)1759 TEST_F(ClientControlledShellSurfaceTest, SetBoundsReparentsToDisplay) {
1760 UpdateDisplay("100x100,100+0-100x100");
1761
1762 gfx::Size buffer_size(64, 64);
1763 std::unique_ptr<Buffer> buffer(
1764 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
1765
1766 const auto* screen = display::Screen::GetScreen();
1767
1768 std::unique_ptr<Surface> surface(new Surface);
1769 auto shell_surface =
1770 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
1771
1772 display::Display primary_display = screen->GetPrimaryDisplay();
1773 gfx::Rect geometry(16, 16, 32, 32);
1774
1775 // Move to primary display with bounds inside display.
1776 shell_surface->SetBounds(primary_display.id(), geometry);
1777 surface->Attach(buffer.get());
1778 surface->Commit();
1779 EXPECT_EQ(geometry, shell_surface->GetWidget()->GetWindowBoundsInScreen());
1780
1781 display::Display display = screen->GetDisplayNearestWindow(
1782 shell_surface->GetWidget()->GetNativeWindow());
1783 EXPECT_EQ(primary_display.id(), display.id());
1784
1785 auto root_windows = ash::Shell::GetAllRootWindows();
1786 display::Display secondary_display =
1787 screen->GetDisplayNearestWindow(root_windows[1]);
1788
1789 // Move to secondary display with bounds inside display.
1790 shell_surface->SetBounds(secondary_display.id(), geometry);
1791 surface->Commit();
1792 EXPECT_EQ(gfx::Rect(116, 16, 32, 32),
1793 shell_surface->GetWidget()->GetWindowBoundsInScreen());
1794
1795 display = screen->GetDisplayNearestWindow(
1796 shell_surface->GetWidget()->GetNativeWindow());
1797 EXPECT_EQ(secondary_display.id(), display.id());
1798
1799 // Move to primary display with bounds outside display.
1800 geometry.set_origin({-100, 0});
1801 shell_surface->SetBounds(primary_display.id(), geometry);
1802 surface->Commit();
1803 EXPECT_EQ(gfx::Rect(-6, 0, 32, 32),
1804 shell_surface->GetWidget()->GetWindowBoundsInScreen());
1805
1806 display = screen->GetDisplayNearestWindow(
1807 shell_surface->GetWidget()->GetNativeWindow());
1808 EXPECT_EQ(primary_display.id(), display.id());
1809
1810 // Move to secondary display with bounds outside display.
1811 shell_surface->SetBounds(secondary_display.id(), geometry);
1812 surface->Commit();
1813 EXPECT_EQ(gfx::Rect(94, 0, 32, 32),
1814 shell_surface->GetWidget()->GetWindowBoundsInScreen());
1815
1816 display = screen->GetDisplayNearestWindow(
1817 shell_surface->GetWidget()->GetNativeWindow());
1818 EXPECT_EQ(secondary_display.id(), display.id());
1819 }
1820
1821 // Set orientation lock to a window.
TEST_F(ClientControlledShellSurfaceTest,SetOrientationLock)1822 TEST_F(ClientControlledShellSurfaceTest, SetOrientationLock) {
1823 display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
1824 .SetFirstDisplayAsInternalDisplay();
1825
1826 EnableTabletMode(true);
1827 ash::ScreenOrientationController* controller =
1828 ash::Shell::Get()->screen_orientation_controller();
1829
1830 gfx::Size buffer_size(256, 256);
1831 std::unique_ptr<Buffer> buffer(
1832 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
1833 std::unique_ptr<Surface> surface(new Surface);
1834
1835 auto shell_surface =
1836 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
1837 surface->Attach(buffer.get());
1838 shell_surface->SetMaximized();
1839 surface->Commit();
1840
1841 shell_surface->SetOrientationLock(
1842 ash::OrientationLockType::kLandscapePrimary);
1843 EXPECT_TRUE(controller->rotation_locked());
1844 display::Display display(display::Screen::GetScreen()->GetPrimaryDisplay());
1845 gfx::Size displaySize = display.size();
1846 EXPECT_GT(displaySize.width(), displaySize.height());
1847
1848 shell_surface->SetOrientationLock(ash::OrientationLockType::kAny);
1849 EXPECT_FALSE(controller->rotation_locked());
1850
1851 EnableTabletMode(false);
1852 }
1853
TEST_F(ClientControlledShellSurfaceTest,SetClientAccessibilityId)1854 TEST_F(ClientControlledShellSurfaceTest, SetClientAccessibilityId) {
1855 gfx::Size buffer_size(64, 64);
1856 std::unique_ptr<Buffer> buffer(
1857 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
1858 std::unique_ptr<Surface> surface(new Surface);
1859 auto shell_surface =
1860 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
1861
1862 EXPECT_FALSE(shell_surface->GetWidget());
1863 shell_surface->SetClientAccessibilityId(0);
1864
1865 surface->Attach(buffer.get());
1866 surface->Commit();
1867 aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
1868 EXPECT_EQ(0, *GetShellClientAccessibilityId(window));
1869 shell_surface->SetClientAccessibilityId(1);
1870 EXPECT_EQ(1, *GetShellClientAccessibilityId(window));
1871
1872 shell_surface->SetClientAccessibilityId(-1);
1873 EXPECT_FALSE(GetShellClientAccessibilityId(window));
1874 }
1875
1876 // Tests adjust bounds locally should also request remote client bounds update.
TEST_F(ClientControlledShellSurfaceTest,AdjustBoundsLocally)1877 TEST_F(ClientControlledShellSurfaceTest, AdjustBoundsLocally) {
1878 UpdateDisplay("800x600");
1879 std::unique_ptr<Buffer> buffer(
1880 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(gfx::Size(64, 64))));
1881 std::unique_ptr<Surface> surface(new Surface);
1882 auto shell_surface =
1883 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
1884 gfx::Rect requested_bounds;
1885 shell_surface->set_bounds_changed_callback(base::BindRepeating(
1886 [](gfx::Rect* dst, ash::WindowStateType current_state,
1887 ash::WindowStateType requested_state, int64_t display_id,
1888 const gfx::Rect& bounds, bool is_resize,
1889 int bounds_change) { *dst = bounds; },
1890 base::Unretained(&requested_bounds)));
1891 surface->Attach(buffer.get());
1892 surface->Commit();
1893
1894 gfx::Rect client_bounds(900, 0, 200, 300);
1895 shell_surface->SetGeometry(client_bounds);
1896 surface->Commit();
1897
1898 views::Widget* widget = shell_surface->GetWidget();
1899 EXPECT_EQ(gfx::Rect(774, 0, 200, 300), widget->GetWindowBoundsInScreen());
1900 EXPECT_EQ(gfx::Rect(774, 0, 200, 300), requested_bounds);
1901
1902 // Receiving the same bounds shouldn't try to update the bounds again.
1903 requested_bounds.SetRect(0, 0, 0, 0);
1904 shell_surface->SetGeometry(client_bounds);
1905 surface->Commit();
1906
1907 EXPECT_TRUE(requested_bounds.IsEmpty());
1908 }
1909
TEST_F(ClientControlledShellSurfaceTest,SnappedInTabletMode)1910 TEST_F(ClientControlledShellSurfaceTest, SnappedInTabletMode) {
1911 gfx::Size buffer_size(256, 256);
1912 std::unique_ptr<Buffer> buffer(
1913 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
1914 std::unique_ptr<Surface> surface(new Surface);
1915 auto shell_surface(
1916 exo_test_helper()->CreateClientControlledShellSurface(surface.get()));
1917 shell_surface->SetGeometry(gfx::Rect(buffer_size));
1918 surface->Attach(buffer.get());
1919 surface->Commit();
1920 shell_surface->GetWidget()->Show();
1921 auto* window = shell_surface->GetWidget()->GetNativeWindow();
1922 auto* window_state = ash::WindowState::Get(window);
1923
1924 EnableTabletMode(true);
1925
1926 ash::WMEvent event(ash::WM_EVENT_SNAP_LEFT);
1927 window_state->OnWMEvent(&event);
1928 EXPECT_EQ(window_state->GetStateType(), ash::WindowStateType::kLeftSnapped);
1929
1930 ash::NonClientFrameViewAsh* frame_view =
1931 static_cast<ash::NonClientFrameViewAsh*>(
1932 shell_surface->GetWidget()->non_client_view()->frame_view());
1933 // Snapped window can also use auto hide.
1934 surface->SetFrame(SurfaceFrameType::AUTOHIDE);
1935 EXPECT_TRUE(frame_view->GetVisible());
1936 EXPECT_TRUE(frame_view->GetHeaderView()->in_immersive_mode());
1937 }
1938
TEST_F(ClientControlledShellSurfaceTest,PipWindowCannotBeActivated)1939 TEST_F(ClientControlledShellSurfaceTest, PipWindowCannotBeActivated) {
1940 const gfx::Size buffer_size(256, 256);
1941 std::unique_ptr<Buffer> buffer(
1942 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
1943 std::unique_ptr<Surface> surface(new Surface());
1944 auto shell_surface =
1945 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
1946
1947 surface->Attach(buffer.get());
1948 surface->Commit();
1949
1950 EXPECT_TRUE(shell_surface->GetWidget()->IsActive());
1951 EXPECT_TRUE(shell_surface->GetWidget()->CanActivate());
1952
1953 // Entering PIP should unactivate the window and make the widget
1954 // unactivatable.
1955 shell_surface->SetPip();
1956 surface->Commit();
1957
1958 EXPECT_FALSE(shell_surface->GetWidget()->IsActive());
1959 EXPECT_FALSE(shell_surface->GetWidget()->CanActivate());
1960
1961 // Leaving PIP should make it activatable again.
1962 shell_surface->SetRestored();
1963 surface->Commit();
1964
1965 EXPECT_TRUE(shell_surface->GetWidget()->CanActivate());
1966 }
1967
TEST_F(ClientControlledShellSurfaceDisplayTest,NoBoundsChangeEventInMinimized)1968 TEST_F(ClientControlledShellSurfaceDisplayTest,
1969 NoBoundsChangeEventInMinimized) {
1970 gfx::Size buffer_size(100, 100);
1971 std::unique_ptr<Buffer> buffer(
1972 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
1973 std::unique_ptr<Surface> surface(new Surface);
1974 auto shell_surface(
1975 exo_test_helper()->CreateClientControlledShellSurface(surface.get()));
1976 surface->Attach(buffer.get());
1977 shell_surface->SetGeometry(gfx::Rect(buffer_size));
1978 surface->Commit();
1979
1980 shell_surface->set_bounds_changed_callback(base::BindRepeating(
1981 &ClientControlledShellSurfaceDisplayTest::OnBoundsChangeEvent,
1982 base::Unretained(this), base::Unretained(shell_surface.get())));
1983 ASSERT_EQ(0, bounds_change_count());
1984 auto* window_state =
1985 ash::WindowState::Get(shell_surface->GetWidget()->GetNativeWindow());
1986 int64_t display_id = window_state->GetDisplay().id();
1987
1988 shell_surface->OnBoundsChangeEvent(ash::WindowStateType::kNormal,
1989 ash::WindowStateType::kNormal, display_id,
1990 gfx::Rect(10, 10, 100, 100), 0);
1991 ASSERT_EQ(1, bounds_change_count());
1992
1993 EXPECT_FALSE(shell_surface->GetWidget()->IsMinimized());
1994
1995 shell_surface->SetMinimized();
1996 surface->Commit();
1997
1998 EXPECT_TRUE(shell_surface->GetWidget()->IsMinimized());
1999 shell_surface->OnBoundsChangeEvent(ash::WindowStateType::kMinimized,
2000 ash::WindowStateType::kMinimized,
2001 display_id, gfx::Rect(0, 0, 100, 100), 0);
2002 ASSERT_EQ(1, bounds_change_count());
2003
2004 // Send bounds change when exiting minmized.
2005 shell_surface->OnBoundsChangeEvent(ash::WindowStateType::kMinimized,
2006 ash::WindowStateType::kNormal, display_id,
2007 gfx::Rect(0, 0, 100, 100), 0);
2008 ASSERT_EQ(2, bounds_change_count());
2009
2010 // Snapped, in clamshell mode.
2011 ash::NonClientFrameViewAsh* frame_view =
2012 static_cast<ash::NonClientFrameViewAsh*>(
2013 shell_surface->GetWidget()->non_client_view()->frame_view());
2014 surface->SetFrame(SurfaceFrameType::NORMAL);
2015 surface->Commit();
2016 shell_surface->OnBoundsChangeEvent(ash::WindowStateType::kMinimized,
2017 ash::WindowStateType::kRightSnapped,
2018 display_id, gfx::Rect(0, 0, 100, 100), 0);
2019 EXPECT_EQ(3, bounds_change_count());
2020 EXPECT_EQ(
2021 frame_view->GetClientBoundsForWindowBounds(gfx::Rect(0, 0, 100, 100)),
2022 requested_bounds().back());
2023 EXPECT_NE(gfx::Rect(0, 0, 100, 100), requested_bounds().back());
2024
2025 // Snapped, in tablet mode.
2026 EnableTabletMode(true);
2027 shell_surface->OnBoundsChangeEvent(ash::WindowStateType::kMinimized,
2028 ash::WindowStateType::kRightSnapped,
2029 display_id, gfx::Rect(0, 0, 100, 100), 0);
2030 EXPECT_EQ(4, bounds_change_count());
2031 EXPECT_EQ(gfx::Rect(0, 0, 100, 100), requested_bounds().back());
2032 }
2033
TEST_F(ClientControlledShellSurfaceTest,SetPipWindowBoundsAnimates)2034 TEST_F(ClientControlledShellSurfaceTest, SetPipWindowBoundsAnimates) {
2035 const gfx::Size buffer_size(256, 256);
2036 std::unique_ptr<Buffer> buffer(
2037 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
2038 std::unique_ptr<Surface> surface(new Surface());
2039 auto shell_surface =
2040 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
2041 shell_surface->SetGeometry(gfx::Rect(buffer_size));
2042 surface->Attach(buffer.get());
2043 surface->Commit();
2044 shell_surface->SetPip();
2045 surface->Commit();
2046 shell_surface->GetWidget()->Show();
2047
2048 ui::ScopedAnimationDurationScaleMode animation_scale_mode(
2049 ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
2050 aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
2051 EXPECT_EQ(gfx::Rect(8, 8, 256, 256), window->layer()->GetTargetBounds());
2052 EXPECT_EQ(gfx::Rect(8, 8, 256, 256), window->layer()->bounds());
2053 window->SetBounds(gfx::Rect(10, 10, 256, 256));
2054 EXPECT_EQ(gfx::Rect(8, 10, 256, 256), window->layer()->GetTargetBounds());
2055 EXPECT_EQ(gfx::Rect(8, 8, 256, 256), window->layer()->bounds());
2056 }
2057
TEST_F(ClientControlledShellSurfaceTest,PipWindowDragDoesNotAnimate)2058 TEST_F(ClientControlledShellSurfaceTest, PipWindowDragDoesNotAnimate) {
2059 const gfx::Size buffer_size(256, 256);
2060 std::unique_ptr<Buffer> buffer(
2061 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
2062 std::unique_ptr<Surface> surface(new Surface());
2063 auto shell_surface =
2064 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
2065 shell_surface->SetGeometry(gfx::Rect(buffer_size));
2066 surface->Attach(buffer.get());
2067 surface->Commit();
2068 shell_surface->SetPip();
2069 surface->Commit();
2070 shell_surface->GetWidget()->Show();
2071
2072 aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
2073 EXPECT_EQ(gfx::Rect(8, 8, 256, 256), window->layer()->GetTargetBounds());
2074 EXPECT_EQ(gfx::Rect(8, 8, 256, 256), window->layer()->bounds());
2075 ui::ScopedAnimationDurationScaleMode animation_scale_mode(
2076 ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
2077 std::unique_ptr<ash::WindowResizer> resizer(ash::CreateWindowResizer(
2078 window, gfx::PointF(), HTCAPTION, ::wm::WINDOW_MOVE_SOURCE_MOUSE));
2079 resizer->Drag(gfx::PointF(10, 10), 0);
2080 EXPECT_EQ(gfx::Rect(18, 18, 256, 256), window->layer()->GetTargetBounds());
2081 EXPECT_EQ(gfx::Rect(18, 18, 256, 256), window->layer()->bounds());
2082 resizer->CompleteDrag();
2083 }
2084
TEST_F(ClientControlledShellSurfaceTest,PipWindowDragDoesNotAnimateWithExtraCommit)2085 TEST_F(ClientControlledShellSurfaceTest,
2086 PipWindowDragDoesNotAnimateWithExtraCommit) {
2087 const gfx::Size buffer_size(256, 256);
2088 std::unique_ptr<Buffer> buffer(
2089 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
2090 std::unique_ptr<Surface> surface(new Surface());
2091 auto shell_surface =
2092 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
2093 shell_surface->SetGeometry(gfx::Rect(buffer_size));
2094 surface->Attach(buffer.get());
2095 surface->Commit();
2096 shell_surface->SetPip();
2097 surface->Commit();
2098 shell_surface->GetWidget()->Show();
2099
2100 // Making an extra commit may set the next bounds change animation type
2101 // wrongly.
2102 surface->Commit();
2103
2104 aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
2105 EXPECT_EQ(gfx::Rect(8, 8, 256, 256), window->layer()->GetTargetBounds());
2106 EXPECT_EQ(gfx::Rect(8, 8, 256, 256), window->layer()->bounds());
2107 ui::ScopedAnimationDurationScaleMode animation_scale_mode(
2108 ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
2109 std::unique_ptr<ash::WindowResizer> resizer(ash::CreateWindowResizer(
2110 window, gfx::PointF(), HTCAPTION, ::wm::WINDOW_MOVE_SOURCE_MOUSE));
2111 resizer->Drag(gfx::PointF(10, 10), 0);
2112 EXPECT_EQ(gfx::Rect(18, 18, 256, 256), window->layer()->GetTargetBounds());
2113 EXPECT_EQ(gfx::Rect(18, 18, 256, 256), window->layer()->bounds());
2114 EXPECT_FALSE(window->layer()->GetAnimator()->is_animating());
2115 resizer->CompleteDrag();
2116 }
2117
TEST_F(ClientControlledShellSurfaceTest,ExpandingPipInTabletModeEndsSplitView)2118 TEST_F(ClientControlledShellSurfaceTest,
2119 ExpandingPipInTabletModeEndsSplitView) {
2120 EnableTabletMode(true);
2121
2122 ash::SplitViewController* split_view_controller =
2123 ash::SplitViewController::Get(ash::Shell::GetPrimaryRootWindow());
2124 EXPECT_FALSE(split_view_controller->InSplitViewMode());
2125
2126 // Create a PIP window:
2127 const gfx::Size buffer_size(256, 256);
2128 std::unique_ptr<Buffer> buffer(
2129 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
2130 std::unique_ptr<Surface> surface(new Surface());
2131 auto shell_surface =
2132 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
2133
2134 surface->Attach(buffer.get());
2135 surface->Commit();
2136 shell_surface->SetPip();
2137 surface->Commit();
2138 shell_surface->GetWidget()->Show();
2139
2140 auto window_left = CreateTestWindow();
2141 auto window_right = CreateTestWindow();
2142
2143 split_view_controller->SnapWindow(
2144 window_left.get(), ash::SplitViewController::SnapPosition::LEFT);
2145 split_view_controller->SnapWindow(
2146 window_right.get(), ash::SplitViewController::SnapPosition::RIGHT);
2147 EXPECT_TRUE(split_view_controller->InSplitViewMode());
2148
2149 // Should end split view.
2150 shell_surface->SetRestored();
2151 surface->Commit();
2152 EXPECT_FALSE(split_view_controller->InSplitViewMode());
2153 }
2154
TEST_F(ClientControlledShellSurfaceTest,DismissingPipInTabletModeDoesNotEndSplitView)2155 TEST_F(ClientControlledShellSurfaceTest,
2156 DismissingPipInTabletModeDoesNotEndSplitView) {
2157 EnableTabletMode(true);
2158
2159 ash::SplitViewController* split_view_controller =
2160 ash::SplitViewController::Get(ash::Shell::GetPrimaryRootWindow());
2161 EXPECT_FALSE(split_view_controller->InSplitViewMode());
2162
2163 // Create a PIP window:
2164 const gfx::Size buffer_size(256, 256);
2165 std::unique_ptr<Buffer> buffer(
2166 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
2167 std::unique_ptr<Surface> surface(new Surface());
2168 auto shell_surface =
2169 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
2170
2171 surface->Attach(buffer.get());
2172 surface->Commit();
2173 shell_surface->SetPip();
2174 surface->Commit();
2175 shell_surface->GetWidget()->Show();
2176
2177 auto window_left = CreateTestWindow();
2178 auto window_right = CreateTestWindow();
2179
2180 split_view_controller->SnapWindow(
2181 window_left.get(), ash::SplitViewController::SnapPosition::LEFT);
2182 split_view_controller->SnapWindow(
2183 window_right.get(), ash::SplitViewController::SnapPosition::RIGHT);
2184 EXPECT_TRUE(split_view_controller->InSplitViewMode());
2185
2186 // Should not end split-view.
2187 shell_surface->SetMinimized();
2188 surface->Commit();
2189 EXPECT_TRUE(split_view_controller->InSplitViewMode());
2190 }
2191
TEST_F(ClientControlledShellSurfaceTest,DoNotReplayWindowStateRequest)2192 TEST_F(ClientControlledShellSurfaceTest, DoNotReplayWindowStateRequest) {
2193 gfx::Size buffer_size(64, 64);
2194 std::unique_ptr<Buffer> buffer(
2195 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
2196 std::unique_ptr<Surface> surface(new Surface);
2197 auto shell_surface =
2198 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
2199
2200 shell_surface->set_state_changed_callback(
2201 base::BindRepeating([](ash::WindowStateType, ash::WindowStateType) {
2202 // This callback must not be called when a widget is created.
2203 EXPECT_TRUE(false);
2204 }));
2205
2206 shell_surface->SetMinimized();
2207 surface->Attach(buffer.get());
2208 surface->Commit();
2209 }
2210
TEST_F(ClientControlledShellSurfaceDisplayTest,RequestBoundsChangeOnceWithStateTransition)2211 TEST_F(ClientControlledShellSurfaceDisplayTest,
2212 RequestBoundsChangeOnceWithStateTransition) {
2213 gfx::Size buffer_size(64, 64);
2214 auto buffer = std::make_unique<Buffer>(
2215 exo_test_helper()->CreateGpuMemoryBuffer(buffer_size));
2216 auto surface = std::make_unique<Surface>();
2217 auto shell_surface =
2218 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
2219
2220 surface->Attach(buffer.get());
2221 surface->Commit();
2222
2223 auto* widget = shell_surface->GetWidget();
2224 const gfx::Rect original_bounds(gfx::Point(20, 20), buffer_size);
2225 shell_surface->SetGeometry(original_bounds);
2226 widget->Restore();
2227 surface->Commit();
2228
2229 shell_surface->set_bounds_changed_callback(base::BindRepeating(
2230 &ClientControlledShellSurfaceDisplayTest::OnBoundsChangeEvent,
2231 base::Unretained(this), base::Unretained(shell_surface.get())));
2232
2233 shell_surface->SetPip();
2234 surface->Commit();
2235
2236 ASSERT_EQ(1, bounds_change_count());
2237 }
2238
TEST_F(ClientControlledShellSurfaceTest,DoNotSavePipBoundsAcrossMultiplePipTransition)2239 TEST_F(ClientControlledShellSurfaceTest,
2240 DoNotSavePipBoundsAcrossMultiplePipTransition) {
2241 // Create a PIP window:
2242 const gfx::Size content_size(100, 100);
2243 auto buffer = std::make_unique<Buffer>(
2244 exo_test_helper()->CreateGpuMemoryBuffer(content_size));
2245
2246 auto surface = std::make_unique<Surface>();
2247 auto shell_surface =
2248 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
2249 surface->Attach(buffer.get());
2250 surface->Commit();
2251 aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
2252
2253 const gfx::Rect original_bounds(gfx::Point(8, 10), content_size);
2254 shell_surface->SetGeometry(original_bounds);
2255 shell_surface->SetPip();
2256 surface->Commit();
2257 EXPECT_EQ(gfx::Rect(8, 10, 100, 100), window->bounds());
2258 shell_surface->GetWidget()->Show();
2259
2260 const gfx::Rect moved_bounds(gfx::Point(8, 20), content_size);
2261 shell_surface->SetGeometry(moved_bounds);
2262 surface->Commit();
2263 EXPECT_EQ(gfx::Rect(8, 20, 100, 100), window->bounds());
2264
2265 shell_surface->SetRestored();
2266 surface->Commit();
2267 shell_surface->SetGeometry(original_bounds);
2268 shell_surface->SetPip();
2269 surface->Commit();
2270 EXPECT_EQ(gfx::Rect(8, 10, 100, 100), window->bounds());
2271
2272 shell_surface->SetGeometry(moved_bounds);
2273 surface->Commit();
2274 EXPECT_EQ(gfx::Rect(8, 20, 100, 100), window->bounds());
2275
2276 shell_surface->SetRestored();
2277 surface->Commit();
2278 shell_surface->SetGeometry(moved_bounds);
2279 shell_surface->SetPip();
2280 surface->Commit();
2281 EXPECT_EQ(gfx::Rect(8, 20, 100, 100), window->bounds());
2282 }
2283
TEST_F(ClientControlledShellSurfaceTest,DoNotApplyCollisionDetectionWhileDragged)2284 TEST_F(ClientControlledShellSurfaceTest,
2285 DoNotApplyCollisionDetectionWhileDragged) {
2286 const gfx::Size buffer_size(256, 256);
2287 std::unique_ptr<Buffer> buffer(
2288 new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
2289 std::unique_ptr<Surface> surface(new Surface());
2290 auto shell_surface =
2291 exo_test_helper()->CreateClientControlledShellSurface(surface.get());
2292
2293 surface->Attach(buffer.get());
2294 shell_surface->SetGeometry(gfx::Rect(gfx::Point(8, 50), buffer_size));
2295 shell_surface->SetPip();
2296 surface->Commit();
2297 aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
2298 ash::WindowState* window_state = ash::WindowState::Get(window);
2299 EXPECT_EQ(gfx::Rect(8, 50, 256, 256), window->bounds());
2300
2301 // Ensure that the collision detection logic is not applied during drag move.
2302 ui::test::EventGenerator* event_generator = GetEventGenerator();
2303 event_generator->MoveMouseToCenterOf(window);
2304 event_generator->PressLeftButton();
2305 shell_surface->StartDrag(HTTOP, gfx::PointF(0, 0));
2306 ASSERT_TRUE(window_state->is_dragged());
2307 shell_surface->SetGeometry(gfx::Rect(gfx::Point(20, 50), buffer_size));
2308 surface->Commit();
2309 EXPECT_EQ(gfx::Rect(20, 50, 256, 256), window->bounds());
2310 }
2311
2312 } // namespace exo
2313