1 // Copyright 2013 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 "ash/wm/immersive_fullscreen_controller.h" 6 7 #include "ash/display/display_manager.h" 8 #include "ash/display/mouse_cursor_event_filter.h" 9 #include "ash/root_window_controller.h" 10 #include "ash/shelf/shelf_layout_manager.h" 11 #include "ash/shelf/shelf_types.h" 12 #include "ash/shell.h" 13 #include "ash/test/ash_test_base.h" 14 #include "ash/wm/window_state.h" 15 #include "ui/aura/client/aura_constants.h" 16 #include "ui/aura/client/cursor_client.h" 17 #include "ui/aura/env.h" 18 #include "ui/aura/test/test_window_delegate.h" 19 #include "ui/aura/window.h" 20 #include "ui/aura/window_event_dispatcher.h" 21 #include "ui/events/event_utils.h" 22 #include "ui/events/test/event_generator.h" 23 #include "ui/events/test/test_event_handler.h" 24 #include "ui/gfx/animation/slide_animation.h" 25 #include "ui/views/bubble/bubble_delegate.h" 26 #include "ui/views/controls/native/native_view_host.h" 27 #include "ui/views/view.h" 28 #include "ui/views/widget/widget.h" 29 30 namespace ash { 31 32 namespace { 33 34 class MockImmersiveFullscreenControllerDelegate 35 : public ImmersiveFullscreenController::Delegate { 36 public: 37 MockImmersiveFullscreenControllerDelegate(views::View* top_container_view) 38 : top_container_view_(top_container_view), 39 enabled_(false), 40 visible_fraction_(1) { 41 } 42 virtual ~MockImmersiveFullscreenControllerDelegate() {} 43 44 // ImmersiveFullscreenController::Delegate overrides: 45 virtual void OnImmersiveRevealStarted() OVERRIDE { 46 enabled_ = true; 47 visible_fraction_ = 0; 48 } 49 virtual void OnImmersiveRevealEnded() OVERRIDE { 50 visible_fraction_ = 0; 51 } 52 virtual void OnImmersiveFullscreenExited() OVERRIDE { 53 enabled_ = false; 54 visible_fraction_ = 1; 55 } 56 virtual void SetVisibleFraction(double visible_fraction) OVERRIDE { 57 visible_fraction_ = visible_fraction; 58 } 59 virtual std::vector<gfx::Rect> GetVisibleBoundsInScreen() const OVERRIDE { 60 std::vector<gfx::Rect> bounds_in_screen; 61 bounds_in_screen.push_back(top_container_view_->GetBoundsInScreen()); 62 return bounds_in_screen; 63 } 64 65 bool is_enabled() const { 66 return enabled_; 67 } 68 69 double visible_fraction() const { 70 return visible_fraction_; 71 } 72 73 private: 74 views::View* top_container_view_; 75 bool enabled_; 76 double visible_fraction_; 77 78 DISALLOW_COPY_AND_ASSIGN(MockImmersiveFullscreenControllerDelegate); 79 }; 80 81 class ConsumeEventHandler : public ui::test::TestEventHandler { 82 public: 83 ConsumeEventHandler() {} 84 virtual ~ConsumeEventHandler() {} 85 86 private: 87 virtual void OnEvent(ui::Event* event) OVERRIDE { 88 ui::test::TestEventHandler::OnEvent(event); 89 if (event->cancelable()) 90 event->SetHandled(); 91 } 92 93 DISALLOW_COPY_AND_ASSIGN(ConsumeEventHandler); 94 }; 95 96 } // namespace 97 98 ///////////////////////////////////////////////////////////////////////////// 99 100 class ImmersiveFullscreenControllerTest : public ash::test::AshTestBase { 101 public: 102 enum Modality { 103 MODALITY_MOUSE, 104 MODALITY_GESTURE_TAP, 105 MODALITY_GESTURE_SCROLL 106 }; 107 108 ImmersiveFullscreenControllerTest() 109 : widget_(NULL), 110 top_container_(NULL), 111 content_view_(NULL) {} 112 virtual ~ImmersiveFullscreenControllerTest() {} 113 114 ImmersiveFullscreenController* controller() { 115 return controller_.get(); 116 } 117 118 views::NativeViewHost* content_view() { 119 return content_view_; 120 } 121 122 views::View* top_container() { 123 return top_container_; 124 } 125 126 views::Widget* widget() { return widget_; } 127 128 aura::Window* window() { 129 return widget_->GetNativeWindow(); 130 } 131 132 MockImmersiveFullscreenControllerDelegate* delegate() { 133 return delegate_.get(); 134 } 135 136 // Access to private data from the controller. 137 bool top_edge_hover_timer_running() const { 138 return controller_->top_edge_hover_timer_.IsRunning(); 139 } 140 int mouse_x_when_hit_top() const { 141 return controller_->mouse_x_when_hit_top_in_screen_; 142 } 143 144 // ash::test::AshTestBase overrides: 145 virtual void SetUp() OVERRIDE { 146 ash::test::AshTestBase::SetUp(); 147 148 widget_ = new views::Widget(); 149 views::Widget::InitParams params; 150 params.context = CurrentContext(); 151 widget_->Init(params); 152 widget_->Show(); 153 154 window()->SetProperty(aura::client::kShowStateKey, 155 ui::SHOW_STATE_FULLSCREEN); 156 157 gfx::Size window_size = widget_->GetWindowBoundsInScreen().size(); 158 content_view_ = new views::NativeViewHost(); 159 content_view_->SetBounds(0, 0, window_size.width(), window_size.height()); 160 widget_->GetContentsView()->AddChildView(content_view_); 161 162 top_container_ = new views::View(); 163 top_container_->SetBounds( 164 0, 0, window_size.width(), 100); 165 top_container_->SetFocusable(true); 166 widget_->GetContentsView()->AddChildView(top_container_); 167 168 delegate_.reset( 169 new MockImmersiveFullscreenControllerDelegate(top_container_)); 170 controller_.reset(new ImmersiveFullscreenController); 171 controller_->Init(delegate_.get(), widget_, top_container_); 172 controller_->SetupForTest(); 173 174 // The mouse is moved so that it is not over |top_container_| by 175 // AshTestBase. 176 } 177 178 // Enables / disables immersive fullscreen. 179 void SetEnabled(bool enabled) { 180 controller_->SetEnabled(ImmersiveFullscreenController::WINDOW_TYPE_OTHER, 181 enabled); 182 } 183 184 // Attempt to reveal the top-of-window views via |modality|. 185 // The top-of-window views can only be revealed via mouse hover or a gesture. 186 void AttemptReveal(Modality modality) { 187 ASSERT_NE(modality, MODALITY_GESTURE_TAP); 188 AttemptRevealStateChange(true, modality); 189 } 190 191 // Attempt to unreveal the top-of-window views via |modality|. The 192 // top-of-window views can be unrevealed via any modality. 193 void AttemptUnreveal(Modality modality) { 194 AttemptRevealStateChange(false, modality); 195 } 196 197 // Sets whether the mouse is hovered above |top_container_|. 198 // SetHovered(true) moves the mouse over the |top_container_| but does not 199 // move it to the top of the screen so will not initiate a reveal. 200 void SetHovered(bool is_mouse_hovered) { 201 MoveMouse(0, is_mouse_hovered ? 10 : top_container_->height() + 100); 202 } 203 204 // Move the mouse to the given coordinates. The coordinates should be in 205 // |top_container_| coordinates. 206 void MoveMouse(int x, int y) { 207 gfx::Point screen_position(x, y); 208 views::View::ConvertPointToScreen(top_container_, &screen_position); 209 GetEventGenerator().MoveMouseTo(screen_position.x(), screen_position.y()); 210 211 // If the top edge timer started running as a result of the mouse move, run 212 // the task which occurs after the timer delay. This reveals the 213 // top-of-window views synchronously if the mouse is hovered at the top of 214 // the screen. 215 if (controller()->top_edge_hover_timer_.IsRunning()) { 216 controller()->top_edge_hover_timer_.user_task().Run(); 217 controller()->top_edge_hover_timer_.Stop(); 218 } 219 } 220 221 private: 222 // Attempt to change the revealed state to |revealed| via |modality|. 223 void AttemptRevealStateChange(bool revealed, Modality modality) { 224 // Compute the event position in |top_container_| coordinates. 225 gfx::Point event_position(0, revealed ? 0 : top_container_->height() + 100); 226 switch (modality) { 227 case MODALITY_MOUSE: { 228 MoveMouse(event_position.x(), event_position.y()); 229 break; 230 } 231 case MODALITY_GESTURE_TAP: { 232 gfx::Point screen_position = event_position; 233 views::View::ConvertPointToScreen(top_container_, &screen_position); 234 ui::test::EventGenerator& event_generator(GetEventGenerator()); 235 event_generator.MoveTouch(event_position); 236 event_generator.PressTouch(); 237 event_generator.ReleaseTouch(); 238 break; 239 } 240 case MODALITY_GESTURE_SCROLL: { 241 gfx::Point start(0, revealed ? 0 : top_container_->height() - 2); 242 gfx::Vector2d scroll_delta(0, 40); 243 gfx::Point end = revealed ? start + scroll_delta : start - scroll_delta; 244 views::View::ConvertPointToScreen(top_container_, &start); 245 views::View::ConvertPointToScreen(top_container_, &end); 246 ui::test::EventGenerator& event_generator(GetEventGenerator()); 247 event_generator.GestureScrollSequence( 248 start, end, 249 base::TimeDelta::FromMilliseconds(30), 1); 250 break; 251 } 252 } 253 } 254 255 scoped_ptr<ImmersiveFullscreenController> controller_; 256 scoped_ptr<MockImmersiveFullscreenControllerDelegate> delegate_; 257 views::Widget* widget_; // Owned by the native widget. 258 views::View* top_container_; // Owned by |widget_|'s root-view. 259 views::NativeViewHost* content_view_; // Owned by |widget_|'s root-view. 260 261 DISALLOW_COPY_AND_ASSIGN(ImmersiveFullscreenControllerTest); 262 }; 263 264 // Test the initial state and that the delegate gets notified of the 265 // top-of-window views getting hidden and revealed. 266 TEST_F(ImmersiveFullscreenControllerTest, Delegate) { 267 // Initial state. 268 EXPECT_FALSE(controller()->IsEnabled()); 269 EXPECT_FALSE(controller()->IsRevealed()); 270 EXPECT_FALSE(delegate()->is_enabled()); 271 272 // Enabling initially hides the top views. 273 SetEnabled(true); 274 EXPECT_TRUE(controller()->IsEnabled()); 275 EXPECT_FALSE(controller()->IsRevealed()); 276 EXPECT_TRUE(delegate()->is_enabled()); 277 EXPECT_EQ(0, delegate()->visible_fraction()); 278 279 // Revealing shows the top views. 280 AttemptReveal(MODALITY_MOUSE); 281 EXPECT_TRUE(controller()->IsEnabled()); 282 EXPECT_TRUE(controller()->IsRevealed()); 283 EXPECT_TRUE(delegate()->is_enabled()); 284 EXPECT_EQ(1, delegate()->visible_fraction()); 285 286 // Disabling ends the immersive reveal. 287 SetEnabled(false); 288 EXPECT_FALSE(controller()->IsEnabled()); 289 EXPECT_FALSE(controller()->IsRevealed()); 290 EXPECT_FALSE(delegate()->is_enabled()); 291 } 292 293 // GetRevealedLock() specific tests. 294 TEST_F(ImmersiveFullscreenControllerTest, RevealedLock) { 295 scoped_ptr<ImmersiveRevealedLock> lock1; 296 scoped_ptr<ImmersiveRevealedLock> lock2; 297 298 // Immersive fullscreen is not on by default. 299 EXPECT_FALSE(controller()->IsEnabled()); 300 301 // 1) Test acquiring and releasing a revealed state lock while immersive 302 // fullscreen is disabled. Acquiring or releasing the lock should have no 303 // effect till immersive fullscreen is enabled. 304 lock1.reset(controller()->GetRevealedLock( 305 ImmersiveFullscreenController::ANIMATE_REVEAL_NO)); 306 EXPECT_FALSE(controller()->IsEnabled()); 307 EXPECT_FALSE(controller()->IsRevealed()); 308 309 // Immersive fullscreen should start in the revealed state due to the lock. 310 SetEnabled(true); 311 EXPECT_TRUE(controller()->IsEnabled()); 312 EXPECT_TRUE(controller()->IsRevealed()); 313 314 SetEnabled(false); 315 EXPECT_FALSE(controller()->IsEnabled()); 316 EXPECT_FALSE(controller()->IsRevealed()); 317 318 lock1.reset(); 319 EXPECT_FALSE(controller()->IsEnabled()); 320 EXPECT_FALSE(controller()->IsRevealed()); 321 322 // Immersive fullscreen should start in the closed state because the lock is 323 // no longer held. 324 SetEnabled(true); 325 EXPECT_TRUE(controller()->IsEnabled()); 326 EXPECT_FALSE(controller()->IsRevealed()); 327 328 // 2) Test that acquiring a lock reveals the top-of-window views if they are 329 // hidden. 330 lock1.reset(controller()->GetRevealedLock( 331 ImmersiveFullscreenController::ANIMATE_REVEAL_NO)); 332 EXPECT_TRUE(controller()->IsRevealed()); 333 334 // 3) Test that the top-of-window views are only hidden when all of the locks 335 // are released. 336 lock2.reset(controller()->GetRevealedLock( 337 ImmersiveFullscreenController::ANIMATE_REVEAL_NO)); 338 lock1.reset(); 339 EXPECT_TRUE(controller()->IsRevealed()); 340 341 lock2.reset(); 342 EXPECT_FALSE(controller()->IsRevealed()); 343 } 344 345 // Test mouse event processing for top-of-screen reveal triggering. 346 TEST_F(ImmersiveFullscreenControllerTest, OnMouseEvent) { 347 // Set up initial state. 348 SetEnabled(true); 349 ASSERT_TRUE(controller()->IsEnabled()); 350 ASSERT_FALSE(controller()->IsRevealed()); 351 352 ui::test::EventGenerator& event_generator(GetEventGenerator()); 353 354 gfx::Rect top_container_bounds_in_screen = 355 top_container()->GetBoundsInScreen(); 356 // A position along the top edge of TopContainerView in screen coordinates. 357 gfx::Point top_edge_pos(top_container_bounds_in_screen.x() + 100, 358 top_container_bounds_in_screen.y()); 359 360 // Mouse wheel event does nothing. 361 ui::MouseEvent wheel( 362 ui::ET_MOUSEWHEEL, top_edge_pos, top_edge_pos, ui::EF_NONE, ui::EF_NONE); 363 event_generator.Dispatch(&wheel); 364 EXPECT_FALSE(top_edge_hover_timer_running()); 365 366 // Move to top edge of screen starts hover timer running. We cannot use 367 // MoveMouse() because MoveMouse() stops the timer if it started running. 368 event_generator.MoveMouseTo(top_edge_pos); 369 EXPECT_TRUE(top_edge_hover_timer_running()); 370 EXPECT_EQ(top_edge_pos.x(), mouse_x_when_hit_top()); 371 372 // Moving |ImmersiveFullscreenControllerTest::kMouseRevealBoundsHeight| down 373 // from the top edge stops it. 374 event_generator.MoveMouseBy(0, 375 ImmersiveFullscreenController::kMouseRevealBoundsHeight); 376 EXPECT_FALSE(top_edge_hover_timer_running()); 377 378 // Moving back to the top starts the timer again. 379 event_generator.MoveMouseTo(top_edge_pos); 380 EXPECT_TRUE(top_edge_hover_timer_running()); 381 EXPECT_EQ(top_edge_pos.x(), mouse_x_when_hit_top()); 382 383 // Slight move to the right keeps the timer running for the same hit point. 384 event_generator.MoveMouseBy(1, 0); 385 EXPECT_TRUE(top_edge_hover_timer_running()); 386 EXPECT_EQ(top_edge_pos.x(), mouse_x_when_hit_top()); 387 388 // Moving back to the left also keeps the timer running. 389 event_generator.MoveMouseBy(-1, 0); 390 EXPECT_TRUE(top_edge_hover_timer_running()); 391 EXPECT_EQ(top_edge_pos.x(), mouse_x_when_hit_top()); 392 393 // Large move right restarts the timer (so it is still running) and considers 394 // this a new hit at the top. 395 event_generator.MoveMouseTo(top_edge_pos.x() + 100, top_edge_pos.y()); 396 EXPECT_TRUE(top_edge_hover_timer_running()); 397 EXPECT_EQ(top_edge_pos.x() + 100, mouse_x_when_hit_top()); 398 399 // Moving off the top edge horizontally stops the timer. 400 event_generator.MoveMouseTo(top_container_bounds_in_screen.right() + 1, 401 top_container_bounds_in_screen.y()); 402 EXPECT_FALSE(top_edge_hover_timer_running()); 403 404 // Once revealed, a move just a little below the top container doesn't end a 405 // reveal. 406 AttemptReveal(MODALITY_MOUSE); 407 event_generator.MoveMouseTo(top_container_bounds_in_screen.x(), 408 top_container_bounds_in_screen.bottom() + 1); 409 EXPECT_TRUE(controller()->IsRevealed()); 410 411 // Once revealed, clicking just below the top container ends the reveal. 412 event_generator.ClickLeftButton(); 413 EXPECT_FALSE(controller()->IsRevealed()); 414 415 // Moving a lot below the top container ends a reveal. 416 AttemptReveal(MODALITY_MOUSE); 417 EXPECT_TRUE(controller()->IsRevealed()); 418 event_generator.MoveMouseTo(top_container_bounds_in_screen.x(), 419 top_container_bounds_in_screen.bottom() + 50); 420 EXPECT_FALSE(controller()->IsRevealed()); 421 422 // The mouse position cannot cause a reveal when the top container's widget 423 // has capture. 424 views::Widget* widget = top_container()->GetWidget(); 425 widget->SetCapture(top_container()); 426 AttemptReveal(MODALITY_MOUSE); 427 EXPECT_FALSE(controller()->IsRevealed()); 428 widget->ReleaseCapture(); 429 430 // The mouse position cannot end the reveal while the top container's widget 431 // has capture. 432 AttemptReveal(MODALITY_MOUSE); 433 EXPECT_TRUE(controller()->IsRevealed()); 434 widget->SetCapture(top_container()); 435 event_generator.MoveMouseTo(top_container_bounds_in_screen.x(), 436 top_container_bounds_in_screen.bottom() + 51); 437 EXPECT_TRUE(controller()->IsRevealed()); 438 439 // Releasing capture should end the reveal. 440 widget->ReleaseCapture(); 441 EXPECT_FALSE(controller()->IsRevealed()); 442 } 443 444 // Test mouse event processing for top-of-screen reveal triggering when the 445 // top container's widget is inactive. 446 TEST_F(ImmersiveFullscreenControllerTest, Inactive) { 447 // Set up initial state. 448 views::Widget* popup_widget = views::Widget::CreateWindowWithContextAndBounds( 449 NULL, 450 CurrentContext(), 451 gfx::Rect(0, 0, 200, 200)); 452 popup_widget->Show(); 453 ASSERT_FALSE(top_container()->GetWidget()->IsActive()); 454 455 SetEnabled(true); 456 ASSERT_TRUE(controller()->IsEnabled()); 457 ASSERT_FALSE(controller()->IsRevealed()); 458 459 gfx::Rect top_container_bounds_in_screen = 460 top_container()->GetBoundsInScreen(); 461 gfx::Rect popup_bounds_in_screen = popup_widget->GetWindowBoundsInScreen(); 462 ASSERT_EQ(top_container_bounds_in_screen.origin().ToString(), 463 popup_bounds_in_screen.origin().ToString()); 464 ASSERT_GT(top_container_bounds_in_screen.right(), 465 popup_bounds_in_screen.right()); 466 467 // The top-of-window views should stay hidden if the cursor is at the top edge 468 // but above an obscured portion of the top-of-window views. 469 MoveMouse(popup_bounds_in_screen.x(), 470 top_container_bounds_in_screen.y()); 471 EXPECT_FALSE(controller()->IsRevealed()); 472 473 // The top-of-window views should reveal if the cursor is at the top edge and 474 // above an unobscured portion of the top-of-window views. 475 MoveMouse(top_container_bounds_in_screen.right() - 1, 476 top_container_bounds_in_screen.y()); 477 EXPECT_TRUE(controller()->IsRevealed()); 478 479 // The top-of-window views should stay revealed if the cursor is moved off 480 // of the top edge. 481 MoveMouse(top_container_bounds_in_screen.right() - 1, 482 top_container_bounds_in_screen.bottom() - 1); 483 EXPECT_TRUE(controller()->IsRevealed()); 484 485 // Moving way off of the top-of-window views should end the immersive reveal. 486 MoveMouse(top_container_bounds_in_screen.right() - 1, 487 top_container_bounds_in_screen.bottom() + 50); 488 EXPECT_FALSE(controller()->IsRevealed()); 489 490 // Moving way off of the top-of-window views in a region where the 491 // top-of-window views are obscured should also end the immersive reveal. 492 // Ideally, the immersive reveal would end immediately when the cursor moves 493 // to an obscured portion of the top-of-window views. 494 MoveMouse(top_container_bounds_in_screen.right() - 1, 495 top_container_bounds_in_screen.y()); 496 EXPECT_TRUE(controller()->IsRevealed()); 497 MoveMouse(top_container_bounds_in_screen.x(), 498 top_container_bounds_in_screen.bottom() + 50); 499 EXPECT_FALSE(controller()->IsRevealed()); 500 } 501 502 // Test mouse event processing for top-of-screen reveal triggering when the user 503 // has a vertical display layout (primary display above/below secondary display) 504 // and the immersive fullscreen window is on the bottom display. 505 TEST_F(ImmersiveFullscreenControllerTest, MouseEventsVerticalDisplayLayout) { 506 if (!SupportsMultipleDisplays()) 507 return; 508 509 // Set up initial state. 510 UpdateDisplay("800x600,800x600"); 511 ash::DisplayLayout display_layout(ash::DisplayLayout::TOP, 0); 512 ash::Shell::GetInstance()->display_manager()->SetLayoutForCurrentDisplays( 513 display_layout); 514 515 SetEnabled(true); 516 ASSERT_TRUE(controller()->IsEnabled()); 517 ASSERT_FALSE(controller()->IsRevealed()); 518 519 aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows(); 520 ASSERT_EQ(root_windows[0], 521 top_container()->GetWidget()->GetNativeWindow()->GetRootWindow()); 522 523 gfx::Rect primary_root_window_bounds_in_screen = 524 root_windows[0]->GetBoundsInScreen(); 525 // Do not set |x| to the root window's x position because the display's 526 // corners have special behavior. 527 int x = primary_root_window_bounds_in_screen.x() + 10; 528 // The y position of the top edge of the primary display. 529 int y_top_edge = primary_root_window_bounds_in_screen.y(); 530 531 ui::test::EventGenerator& event_generator(GetEventGenerator()); 532 533 // Moving right below the top edge starts the hover timer running. We 534 // cannot use MoveMouse() because MoveMouse() stops the timer if it started 535 // running. 536 event_generator.MoveMouseTo(x, y_top_edge + 1); 537 EXPECT_TRUE(top_edge_hover_timer_running()); 538 EXPECT_EQ(y_top_edge + 1, 539 aura::Env::GetInstance()->last_mouse_location().y()); 540 541 // The timer should continue running if the user moves the mouse to the top 542 // edge even though the mouse is warped to the secondary display. 543 event_generator.MoveMouseTo(x, y_top_edge); 544 EXPECT_TRUE(top_edge_hover_timer_running()); 545 546 // The timer should continue running if the user overshoots the top edge 547 // a bit. 548 event_generator.MoveMouseTo(x, y_top_edge - 2); 549 EXPECT_TRUE(top_edge_hover_timer_running()); 550 551 // The timer should stop running if the user overshoots the top edge by 552 // a lot. 553 event_generator.MoveMouseTo(x, y_top_edge - 20); 554 EXPECT_FALSE(top_edge_hover_timer_running()); 555 556 // The timer should not start if the user moves the mouse to the bottom of the 557 // secondary display without crossing the top edge first. 558 event_generator.MoveMouseTo(x, y_top_edge - 2); 559 560 // Reveal the top-of-window views by overshooting the top edge slightly. 561 event_generator.MoveMouseTo(x, y_top_edge + 1); 562 // MoveMouse() runs the timer task. 563 MoveMouse(x, y_top_edge - 2); 564 EXPECT_TRUE(controller()->IsRevealed()); 565 566 // The top-of-window views should stay revealed if the user moves the mouse 567 // around in the bottom region of the secondary display. 568 event_generator.MoveMouseTo(x + 10, y_top_edge - 3); 569 EXPECT_TRUE(controller()->IsRevealed()); 570 571 // The top-of-window views should hide if the user moves the mouse away from 572 // the bottom region of the secondary display. 573 event_generator.MoveMouseTo(x, y_top_edge - 20); 574 EXPECT_FALSE(controller()->IsRevealed()); 575 576 // Test that it is possible to reveal the top-of-window views by overshooting 577 // the top edge slightly when the top container's widget is not active. 578 views::Widget* popup_widget = views::Widget::CreateWindowWithContextAndBounds( 579 NULL, 580 CurrentContext(), 581 gfx::Rect(0, 200, 100, 100)); 582 popup_widget->Show(); 583 ASSERT_FALSE(top_container()->GetWidget()->IsActive()); 584 ASSERT_FALSE(top_container()->GetBoundsInScreen().Intersects( 585 popup_widget->GetWindowBoundsInScreen())); 586 event_generator.MoveMouseTo(x, y_top_edge + 1); 587 MoveMouse(x, y_top_edge - 2); 588 EXPECT_TRUE(controller()->IsRevealed()); 589 } 590 591 // Test behavior when the mouse becomes hovered without moving. 592 TEST_F(ImmersiveFullscreenControllerTest, MouseHoveredWithoutMoving) { 593 SetEnabled(true); 594 scoped_ptr<ImmersiveRevealedLock> lock; 595 596 // 1) Test that if the mouse becomes hovered without the mouse moving due to a 597 // lock causing the top-of-window views to be revealed (and the mouse 598 // happening to be near the top of the screen), the top-of-window views do not 599 // hide till the mouse moves off of the top-of-window views. 600 SetHovered(true); 601 EXPECT_FALSE(controller()->IsRevealed()); 602 lock.reset(controller()->GetRevealedLock( 603 ImmersiveFullscreenController::ANIMATE_REVEAL_NO)); 604 EXPECT_TRUE(controller()->IsRevealed()); 605 lock.reset(); 606 EXPECT_TRUE(controller()->IsRevealed()); 607 SetHovered(false); 608 EXPECT_FALSE(controller()->IsRevealed()); 609 610 // 2) Test that if the mouse becomes hovered without moving because of a 611 // reveal in ImmersiveFullscreenController::SetEnabled(true) and there are no 612 // locks keeping the top-of-window views revealed, that mouse hover does not 613 // prevent the top-of-window views from closing. 614 SetEnabled(false); 615 SetHovered(true); 616 EXPECT_FALSE(controller()->IsRevealed()); 617 SetEnabled(true); 618 EXPECT_FALSE(controller()->IsRevealed()); 619 620 // 3) Test that if the mouse becomes hovered without moving because of a 621 // reveal in ImmersiveFullscreenController::SetEnabled(true) and there is a 622 // lock keeping the top-of-window views revealed, that the top-of-window views 623 // do not hide till the mouse moves off of the top-of-window views. 624 SetEnabled(false); 625 SetHovered(true); 626 lock.reset(controller()->GetRevealedLock( 627 ImmersiveFullscreenController::ANIMATE_REVEAL_NO)); 628 EXPECT_FALSE(controller()->IsRevealed()); 629 SetEnabled(true); 630 EXPECT_TRUE(controller()->IsRevealed()); 631 lock.reset(); 632 EXPECT_TRUE(controller()->IsRevealed()); 633 SetHovered(false); 634 EXPECT_FALSE(controller()->IsRevealed()); 635 } 636 637 // Test revealing the top-of-window views using one modality and ending 638 // the reveal via another. For instance, initiating the reveal via a SWIPE_OPEN 639 // edge gesture, switching to using the mouse and ending the reveal by moving 640 // the mouse off of the top-of-window views. 641 TEST_F(ImmersiveFullscreenControllerTest, DifferentModalityEnterExit) { 642 SetEnabled(true); 643 EXPECT_TRUE(controller()->IsEnabled()); 644 EXPECT_FALSE(controller()->IsRevealed()); 645 646 // Initiate reveal via gesture, end reveal via mouse. 647 AttemptReveal(MODALITY_GESTURE_SCROLL); 648 EXPECT_TRUE(controller()->IsRevealed()); 649 MoveMouse(1, 1); 650 EXPECT_TRUE(controller()->IsRevealed()); 651 AttemptUnreveal(MODALITY_MOUSE); 652 EXPECT_FALSE(controller()->IsRevealed()); 653 654 // Initiate reveal via gesture, end reveal via touch. 655 AttemptReveal(MODALITY_GESTURE_SCROLL); 656 EXPECT_TRUE(controller()->IsRevealed()); 657 AttemptUnreveal(MODALITY_GESTURE_TAP); 658 EXPECT_FALSE(controller()->IsRevealed()); 659 660 // Initiate reveal via mouse, end reveal via gesture. 661 AttemptReveal(MODALITY_MOUSE); 662 EXPECT_TRUE(controller()->IsRevealed()); 663 AttemptUnreveal(MODALITY_GESTURE_SCROLL); 664 EXPECT_FALSE(controller()->IsRevealed()); 665 666 // Initiate reveal via mouse, end reveal via touch. 667 AttemptReveal(MODALITY_MOUSE); 668 EXPECT_TRUE(controller()->IsRevealed()); 669 AttemptUnreveal(MODALITY_GESTURE_TAP); 670 EXPECT_FALSE(controller()->IsRevealed()); 671 } 672 673 // Test when the SWIPE_CLOSE edge gesture closes the top-of-window views. 674 #if defined(OS_WIN) 675 // On Windows, touch events do not result in mouse events being disabled. As 676 // a result, the last part of this test which ends the reveal via a gesture will 677 // not work correctly. See crbug.com/332430, and the function 678 // ShouldHideCursorOnTouch() in compound_event_filter.cc. 679 #define MAYBE_EndRevealViaGesture DISABLED_EndRevealViaGesture 680 #else 681 #define MAYBE_EndRevealViaGesture EndRevealViaGesture 682 #endif 683 TEST_F(ImmersiveFullscreenControllerTest, MAYBE_EndRevealViaGesture) { 684 SetEnabled(true); 685 EXPECT_TRUE(controller()->IsEnabled()); 686 EXPECT_FALSE(controller()->IsRevealed()); 687 688 // A gesture should be able to close the top-of-window views when 689 // top-of-window views have focus. 690 AttemptReveal(MODALITY_MOUSE); 691 top_container()->RequestFocus(); 692 EXPECT_TRUE(controller()->IsRevealed()); 693 AttemptUnreveal(MODALITY_GESTURE_SCROLL); 694 EXPECT_FALSE(controller()->IsRevealed()); 695 696 // The top-of-window views should no longer have focus. Clearing focus is 697 // important because it closes focus-related popup windows like the touch 698 // selection handles. 699 EXPECT_FALSE(top_container()->HasFocus()); 700 701 // If some other code is holding onto a lock, a gesture should not be able to 702 // end the reveal. 703 AttemptReveal(MODALITY_MOUSE); 704 scoped_ptr<ImmersiveRevealedLock> lock(controller()->GetRevealedLock( 705 ImmersiveFullscreenController::ANIMATE_REVEAL_NO)); 706 EXPECT_TRUE(controller()->IsRevealed()); 707 AttemptUnreveal(MODALITY_GESTURE_SCROLL); 708 EXPECT_TRUE(controller()->IsRevealed()); 709 lock.reset(); 710 EXPECT_FALSE(controller()->IsRevealed()); 711 } 712 713 // Tests that touch-gesture can be used to reveal the top-of-window views when 714 // the child window consumes all events. 715 TEST_F(ImmersiveFullscreenControllerTest, RevealViaGestureChildConsumesEvents) { 716 // Enabling initially hides the top views. 717 SetEnabled(true); 718 EXPECT_TRUE(controller()->IsEnabled()); 719 EXPECT_FALSE(controller()->IsRevealed()); 720 721 aura::test::TestWindowDelegate child_delegate; 722 scoped_ptr<aura::Window> child( 723 CreateTestWindowInShellWithDelegateAndType(&child_delegate, 724 ui::wm::WINDOW_TYPE_CONTROL, 725 1234, 726 gfx::Rect())); 727 content_view()->Attach(child.get()); 728 child->Show(); 729 730 ConsumeEventHandler handler; 731 child->AddPreTargetHandler(&handler); 732 733 // Reveal the top views using a touch-scroll gesture. The child window should 734 // not receive the touch events. 735 AttemptReveal(MODALITY_GESTURE_SCROLL); 736 EXPECT_TRUE(controller()->IsRevealed()); 737 EXPECT_EQ(0, handler.num_touch_events()); 738 739 AttemptUnreveal(MODALITY_GESTURE_TAP); 740 EXPECT_FALSE(controller()->IsRevealed()); 741 EXPECT_GT(handler.num_touch_events(), 0); 742 child->RemovePreTargetHandler(&handler); 743 } 744 745 // Make sure touch events towards the top of the window do not leak through to 746 // windows underneath. 747 TEST_F(ImmersiveFullscreenControllerTest, EventsDoNotLeakToWindowUnderneath) { 748 gfx::Rect window_bounds = window()->GetBoundsInScreen(); 749 aura::test::TestWindowDelegate child_delegate; 750 scoped_ptr<aura::Window> behind(CreateTestWindowInShellWithDelegate( 751 &child_delegate, 1234, window_bounds)); 752 behind->Show(); 753 behind->SetBounds(window_bounds); 754 widget()->StackAbove(behind.get()); 755 756 // Make sure the windows are aligned on top. 757 EXPECT_EQ(behind->GetBoundsInScreen().y(), window()->GetBoundsInScreen().y()); 758 int top = behind->GetBoundsInScreen().y(); 759 760 ui::TouchEvent touch(ui::ET_TOUCH_MOVED, gfx::Point(10, top), 0, 761 ui::EventTimeForNow()); 762 ui::EventTarget* root = window()->GetRootWindow(); 763 ui::EventTargeter* targeter = root->GetEventTargeter(); 764 EXPECT_EQ(window(), targeter->FindTargetForEvent(root, &touch)); 765 766 SetEnabled(true); 767 EXPECT_FALSE(controller()->IsRevealed()); 768 // Make sure the windows are still aligned on top. 769 EXPECT_EQ(behind->GetBoundsInScreen().y(), window()->GetBoundsInScreen().y()); 770 top = behind->GetBoundsInScreen().y(); 771 ui::TouchEvent touch2(ui::ET_TOUCH_MOVED, gfx::Point(10, top), 0, 772 ui::EventTimeForNow()); 773 // The event should still be targeted to window(). 774 EXPECT_EQ(window(), targeter->FindTargetForEvent(root, &touch2)); 775 } 776 777 // Check that the window state gets properly marked for immersive fullscreen. 778 TEST_F(ImmersiveFullscreenControllerTest, WindowStateImmersiveFullscreen) { 779 ash::wm::WindowState* window_state = ash::wm::GetWindowState(window()); 780 781 EXPECT_FALSE(window_state->in_immersive_fullscreen()); 782 SetEnabled(true); 783 ASSERT_TRUE(controller()->IsEnabled()); 784 EXPECT_TRUE(window_state->in_immersive_fullscreen()); 785 786 SetEnabled(false); 787 ASSERT_FALSE(controller()->IsEnabled()); 788 EXPECT_FALSE(window_state->in_immersive_fullscreen()); 789 } 790 791 // Do not test under windows because focus testing is not reliable on 792 // Windows. (crbug.com/79493) 793 #if !defined(OS_WIN) 794 795 // Test how focus and activation affects whether the top-of-window views are 796 // revealed. 797 TEST_F(ImmersiveFullscreenControllerTest, Focus) { 798 // Add views to the view hierarchy which we will focus and unfocus during the 799 // test. 800 views::View* child_view = new views::View(); 801 child_view->SetBounds(0, 0, 10, 10); 802 child_view->SetFocusable(true); 803 top_container()->AddChildView(child_view); 804 views::View* unrelated_view = new views::View(); 805 unrelated_view->SetBounds(0, 100, 10, 10); 806 unrelated_view->SetFocusable(true); 807 top_container()->parent()->AddChildView(unrelated_view); 808 views::FocusManager* focus_manager = 809 top_container()->GetWidget()->GetFocusManager(); 810 811 SetEnabled(true); 812 813 // 1) Test that the top-of-window views stay revealed as long as either a 814 // |child_view| has focus or the mouse is hovered above the top-of-window 815 // views. 816 AttemptReveal(MODALITY_MOUSE); 817 child_view->RequestFocus(); 818 focus_manager->ClearFocus(); 819 EXPECT_TRUE(controller()->IsRevealed()); 820 child_view->RequestFocus(); 821 SetHovered(false); 822 EXPECT_TRUE(controller()->IsRevealed()); 823 focus_manager->ClearFocus(); 824 EXPECT_FALSE(controller()->IsRevealed()); 825 826 // 2) Test that focusing |unrelated_view| hides the top-of-window views. 827 // Note: In this test we can cheat and trigger a reveal via focus because 828 // the top container does not hide when the top-of-window views are not 829 // revealed. 830 child_view->RequestFocus(); 831 EXPECT_TRUE(controller()->IsRevealed()); 832 unrelated_view->RequestFocus(); 833 EXPECT_FALSE(controller()->IsRevealed()); 834 835 // 3) Test that a loss of focus of |child_view| to |unrelated_view| 836 // while immersive mode is disabled is properly registered. 837 child_view->RequestFocus(); 838 EXPECT_TRUE(controller()->IsRevealed()); 839 SetEnabled(false); 840 EXPECT_FALSE(controller()->IsRevealed()); 841 unrelated_view->RequestFocus(); 842 SetEnabled(true); 843 EXPECT_FALSE(controller()->IsRevealed()); 844 845 // Repeat test but with a revealed lock acquired when immersive mode is 846 // disabled because the code path is different. 847 child_view->RequestFocus(); 848 EXPECT_TRUE(controller()->IsRevealed()); 849 SetEnabled(false); 850 scoped_ptr<ImmersiveRevealedLock> lock(controller()->GetRevealedLock( 851 ImmersiveFullscreenController::ANIMATE_REVEAL_NO)); 852 EXPECT_FALSE(controller()->IsRevealed()); 853 unrelated_view->RequestFocus(); 854 SetEnabled(true); 855 EXPECT_TRUE(controller()->IsRevealed()); 856 lock.reset(); 857 EXPECT_FALSE(controller()->IsRevealed()); 858 } 859 860 // Test how transient windows affect whether the top-of-window views are 861 // revealed. 862 TEST_F(ImmersiveFullscreenControllerTest, Transient) { 863 views::Widget* top_container_widget = top_container()->GetWidget(); 864 865 SetEnabled(true); 866 ASSERT_FALSE(controller()->IsRevealed()); 867 868 // 1) Test that a transient window which is not a bubble does not trigger a 869 // reveal but does keep the top-of-window views revealed if they are already 870 // revealed. 871 views::Widget::InitParams transient_params; 872 transient_params.ownership = 873 views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 874 transient_params.parent = top_container_widget->GetNativeView(); 875 transient_params.bounds = gfx::Rect(0, 100, 100, 100); 876 scoped_ptr<views::Widget> transient_widget(new views::Widget()); 877 transient_widget->Init(transient_params); 878 879 EXPECT_FALSE(controller()->IsRevealed()); 880 AttemptReveal(MODALITY_MOUSE); 881 EXPECT_TRUE(controller()->IsRevealed()); 882 transient_widget->Show(); 883 SetHovered(false); 884 EXPECT_TRUE(controller()->IsRevealed()); 885 transient_widget.reset(); 886 EXPECT_FALSE(controller()->IsRevealed()); 887 888 // 2) Test that activating a non-transient window does not keep the 889 // top-of-window views revealed. 890 views::Widget::InitParams non_transient_params; 891 non_transient_params.ownership = 892 views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 893 non_transient_params.context = top_container_widget->GetNativeView(); 894 non_transient_params.bounds = gfx::Rect(0, 100, 100, 100); 895 scoped_ptr<views::Widget> non_transient_widget(new views::Widget()); 896 non_transient_widget->Init(non_transient_params); 897 898 EXPECT_FALSE(controller()->IsRevealed()); 899 AttemptReveal(MODALITY_MOUSE); 900 EXPECT_TRUE(controller()->IsRevealed()); 901 non_transient_widget->Show(); 902 SetHovered(false); 903 EXPECT_FALSE(controller()->IsRevealed()); 904 } 905 906 // Test how bubbles affect whether the top-of-window views are revealed. 907 TEST_F(ImmersiveFullscreenControllerTest, Bubbles) { 908 scoped_ptr<ImmersiveRevealedLock> revealed_lock; 909 views::Widget* top_container_widget = top_container()->GetWidget(); 910 911 // Add views to the view hierarchy to which we will anchor bubbles. 912 views::View* child_view = new views::View(); 913 child_view->SetBounds(0, 0, 10, 10); 914 top_container()->AddChildView(child_view); 915 views::View* unrelated_view = new views::View(); 916 unrelated_view->SetBounds(0, 100, 10, 10); 917 top_container()->parent()->AddChildView(unrelated_view); 918 919 SetEnabled(true); 920 ASSERT_FALSE(controller()->IsRevealed()); 921 922 // 1) Test that a bubble anchored to a child of the top container triggers 923 // a reveal and keeps the top-of-window views revealed for the duration of 924 // its visibility. 925 views::Widget* bubble_widget1(views::BubbleDelegateView::CreateBubble( 926 new views::BubbleDelegateView(child_view, views::BubbleBorder::NONE))); 927 bubble_widget1->Show(); 928 EXPECT_TRUE(controller()->IsRevealed()); 929 930 // Activating |top_container_widget| will close |bubble_widget1|. 931 top_container_widget->Activate(); 932 AttemptReveal(MODALITY_MOUSE); 933 revealed_lock.reset(controller()->GetRevealedLock( 934 ImmersiveFullscreenController::ANIMATE_REVEAL_NO)); 935 EXPECT_TRUE(controller()->IsRevealed()); 936 937 views::Widget* bubble_widget2 = views::BubbleDelegateView::CreateBubble( 938 new views::BubbleDelegateView(child_view, views::BubbleBorder::NONE)); 939 bubble_widget2->Show(); 940 EXPECT_TRUE(controller()->IsRevealed()); 941 revealed_lock.reset(); 942 SetHovered(false); 943 EXPECT_TRUE(controller()->IsRevealed()); 944 bubble_widget2->Close(); 945 EXPECT_FALSE(controller()->IsRevealed()); 946 947 // 2) Test that transitioning from keeping the top-of-window views revealed 948 // because of a bubble to keeping the top-of-window views revealed because of 949 // mouse hover by activating |top_container_widget| works. 950 views::Widget* bubble_widget3 = views::BubbleDelegateView::CreateBubble( 951 new views::BubbleDelegateView(child_view, views::BubbleBorder::NONE)); 952 bubble_widget3->Show(); 953 SetHovered(true); 954 EXPECT_TRUE(controller()->IsRevealed()); 955 top_container_widget->Activate(); 956 EXPECT_TRUE(controller()->IsRevealed()); 957 958 // 3) Test that the top-of-window views stay revealed as long as at least one 959 // bubble anchored to a child of the top container is visible. 960 SetHovered(false); 961 EXPECT_FALSE(controller()->IsRevealed()); 962 963 views::BubbleDelegateView* bubble_delegate4(new views::BubbleDelegateView( 964 child_view, views::BubbleBorder::NONE)); 965 bubble_delegate4->set_can_activate(false); 966 views::Widget* bubble_widget4(views::BubbleDelegateView::CreateBubble( 967 bubble_delegate4)); 968 bubble_widget4->Show(); 969 970 views::BubbleDelegateView* bubble_delegate5(new views::BubbleDelegateView( 971 child_view, views::BubbleBorder::NONE)); 972 bubble_delegate5->set_can_activate(false); 973 views::Widget* bubble_widget5(views::BubbleDelegateView::CreateBubble( 974 bubble_delegate5)); 975 bubble_widget5->Show(); 976 977 EXPECT_TRUE(controller()->IsRevealed()); 978 bubble_widget4->Hide(); 979 EXPECT_TRUE(controller()->IsRevealed()); 980 bubble_widget5->Hide(); 981 EXPECT_FALSE(controller()->IsRevealed()); 982 bubble_widget5->Show(); 983 EXPECT_TRUE(controller()->IsRevealed()); 984 985 // 4) Test that visibility changes which occur while immersive fullscreen is 986 // disabled are handled upon reenabling immersive fullscreen. 987 SetEnabled(false); 988 bubble_widget5->Hide(); 989 SetEnabled(true); 990 EXPECT_FALSE(controller()->IsRevealed()); 991 992 // We do not need |bubble_widget4| or |bubble_widget5| anymore, close them. 993 bubble_widget4->Close(); 994 bubble_widget5->Close(); 995 996 // 5) Test that a bubble added while immersive fullscreen is disabled is 997 // handled upon reenabling immersive fullscreen. 998 SetEnabled(false); 999 1000 views::Widget* bubble_widget6 = views::BubbleDelegateView::CreateBubble( 1001 new views::BubbleDelegateView(child_view, views::BubbleBorder::NONE)); 1002 bubble_widget6->Show(); 1003 1004 SetEnabled(true); 1005 EXPECT_TRUE(controller()->IsRevealed()); 1006 1007 bubble_widget6->Close(); 1008 1009 // 6) Test that a bubble which is not anchored to a child of the 1010 // TopContainerView does not trigger a reveal or keep the 1011 // top-of-window views revealed if they are already revealed. 1012 views::Widget* bubble_widget7 = views::BubbleDelegateView::CreateBubble( 1013 new views::BubbleDelegateView(unrelated_view, views::BubbleBorder::NONE)); 1014 bubble_widget7->Show(); 1015 EXPECT_FALSE(controller()->IsRevealed()); 1016 1017 // Activating |top_container_widget| will close |bubble_widget6|. 1018 top_container_widget->Activate(); 1019 AttemptReveal(MODALITY_MOUSE); 1020 EXPECT_TRUE(controller()->IsRevealed()); 1021 1022 views::Widget* bubble_widget8 = views::BubbleDelegateView::CreateBubble( 1023 new views::BubbleDelegateView(unrelated_view, views::BubbleBorder::NONE)); 1024 bubble_widget8->Show(); 1025 SetHovered(false); 1026 EXPECT_FALSE(controller()->IsRevealed()); 1027 bubble_widget8->Close(); 1028 } 1029 1030 #endif // defined(OS_WIN) 1031 1032 // Test that the shelf is set to auto hide as long as the window is in 1033 // immersive fullscreen and that the shelf's state before entering immersive 1034 // fullscreen is restored upon exiting immersive fullscreen. 1035 TEST_F(ImmersiveFullscreenControllerTest, Shelf) { 1036 ash::ShelfLayoutManager* shelf = 1037 ash::Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager(); 1038 1039 // Shelf is visible by default. 1040 window()->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); 1041 ASSERT_FALSE(controller()->IsEnabled()); 1042 ASSERT_EQ(ash::SHELF_VISIBLE, shelf->visibility_state()); 1043 1044 // Entering immersive fullscreen sets the shelf to auto hide. 1045 window()->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN); 1046 SetEnabled(true); 1047 EXPECT_EQ(ash::SHELF_AUTO_HIDE, shelf->visibility_state()); 1048 1049 // Disabling immersive fullscreen puts it back. 1050 SetEnabled(false); 1051 window()->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); 1052 ASSERT_FALSE(controller()->IsEnabled()); 1053 EXPECT_EQ(ash::SHELF_VISIBLE, shelf->visibility_state()); 1054 1055 // The user could toggle the shelf auto-hide behavior. 1056 shelf->SetAutoHideBehavior(ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS); 1057 EXPECT_EQ(ash::SHELF_AUTO_HIDE, shelf->visibility_state()); 1058 1059 // Entering immersive fullscreen keeps auto-hide. 1060 window()->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN); 1061 SetEnabled(true); 1062 EXPECT_EQ(ash::SHELF_AUTO_HIDE, shelf->visibility_state()); 1063 1064 // Disabling immersive fullscreen maintains the user's auto-hide selection. 1065 SetEnabled(false); 1066 window()->SetProperty(aura::client::kShowStateKey, 1067 ui::SHOW_STATE_NORMAL); 1068 EXPECT_EQ(ash::SHELF_AUTO_HIDE, shelf->visibility_state()); 1069 } 1070 1071 } // namespase ash 1072