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 "ash/public/cpp/test/shell_test_api.h"
6 #include "base/macros.h"
7 #include "base/test/test_mock_time_task_runner.h"
8 #include "chrome/browser/profiles/profile_io_data.h"
9 #include "chrome/browser/ui/ash/tablet_mode_page_behavior.h"
10 #include "chrome/browser/ui/browser_commands.h"
11 #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
12 #include "chrome/browser/ui/exclusive_access/exclusive_access_test.h"
13 #include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
14 #include "chrome/browser/ui/views/frame/browser_non_client_frame_view.h"
15 #include "chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h"
16 #include "chrome/browser/ui/views/frame/browser_view.h"
17 #include "chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.h"
18 #include "chrome/browser/ui/views/frame/top_container_view.h"
19 #include "chrome/browser/ui/views/tabs/tab_strip.h"
20 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
21 #include "chrome/browser/ui/views/web_apps/web_app_frame_toolbar_view.h"
22 #include "chrome/browser/ui/views/web_apps/web_app_menu_button.h"
23 #include "chrome/browser/ui/web_applications/web_app_controller_browsertest.h"
24 #include "chrome/browser/web_applications/components/web_application_info.h"
25 #include "chrome/test/base/ui_test_utils.h"
26 #include "chrome/test/permissions/permission_request_manager_test_api.h"
27 #include "chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h"
28 #include "chromeos/ui/frame/immersive/immersive_fullscreen_controller_test_api.h"
29 #include "content/public/test/browser_test.h"
30 #include "content/public/test/content_mock_cert_verifier.h"
31 #include "net/cert/mock_cert_verifier.h"
32 #include "ui/aura/client/aura_constants.h"
33 #include "ui/views/animation/test/ink_drop_host_view_test_api.h"
34 #include "ui/views/window/frame_caption_button.h"
35 
36 class ImmersiveModeControllerChromeosWebAppBrowserTest
37     : public web_app::WebAppControllerBrowserTest {
38  public:
ImmersiveModeControllerChromeosWebAppBrowserTest()39   ImmersiveModeControllerChromeosWebAppBrowserTest()
40       : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
41 
42   ~ImmersiveModeControllerChromeosWebAppBrowserTest() override = default;
43 
44   // InProcessBrowserTest override:
SetUpOnMainThread()45   void SetUpOnMainThread() override {
46     cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
47     https_server_.AddDefaultHandlers(GetChromeTestDataDir());
48     ASSERT_TRUE(https_server_.Start());
49 
50     const GURL app_url = GetAppUrl();
51     auto web_app_info = std::make_unique<WebApplicationInfo>();
52     web_app_info->start_url = app_url;
53     web_app_info->scope = app_url.GetWithoutFilename();
54     web_app_info->theme_color = SK_ColorBLUE;
55 
56     app_id = InstallWebApp(std::move(web_app_info));
57   }
58 
GetAppUrl()59   GURL GetAppUrl() { return https_server_.GetURL("/simple.html"); }
60 
LaunchAppBrowser(bool wait=true)61   void LaunchAppBrowser(bool wait = true) {
62     ui_test_utils::UrlLoadObserver url_observer(
63         GetAppUrl(), content::NotificationService::AllSources());
64     browser_ = LaunchWebAppBrowser(app_id);
65 
66     if (wait) {
67       // Wait for the URL to load so that the location bar end-state stabilizes.
68       url_observer.Wait();
69     }
70     controller_ = browser_view()->immersive_mode_controller();
71 
72     // Disable animations in immersive fullscreen before we show the window,
73     // which triggers an animation.
74     chromeos::ImmersiveFullscreenControllerTestApi(
75         static_cast<ImmersiveModeControllerChromeos*>(controller_)
76             ->controller())
77         .SetupForTest();
78 
79     browser_->window()->Show();
80   }
81 
SetUpInProcessBrowserTestFixture()82   void SetUpInProcessBrowserTestFixture() override {
83     extensions::ExtensionBrowserTest::SetUpInProcessBrowserTestFixture();
84     cert_verifier_.SetUpInProcessBrowserTestFixture();
85   }
86 
TearDownInProcessBrowserTestFixture()87   void TearDownInProcessBrowserTestFixture() override {
88     cert_verifier_.TearDownInProcessBrowserTestFixture();
89     extensions::ExtensionBrowserTest::TearDownInProcessBrowserTestFixture();
90   }
91 
SetUpCommandLine(base::CommandLine * command_line)92   void SetUpCommandLine(base::CommandLine* command_line) override {
93     extensions::ExtensionBrowserTest::SetUpCommandLine(command_line);
94     cert_verifier_.SetUpCommandLine(command_line);
95   }
96 
97   // Returns the bounds of |view| in widget coordinates.
GetBoundsInWidget(views::View * view)98   gfx::Rect GetBoundsInWidget(views::View* view) {
99     return view->ConvertRectToWidget(view->GetLocalBounds());
100   }
101 
102   // Toggle the browser's fullscreen state.
ToggleFullscreen()103   void ToggleFullscreen() {
104     // The fullscreen change notification is sent asynchronously. The
105     // notification is used to trigger changes in whether the shelf is auto
106     // hidden.
107     FullscreenNotificationObserver waiter(browser());
108     chrome::ToggleFullscreenMode(browser());
109     waiter.Wait();
110   }
111 
112   // Attempt revealing the top-of-window views.
AttemptReveal()113   void AttemptReveal() {
114     if (!revealed_lock_.get()) {
115       revealed_lock_.reset(controller_->GetRevealedLock(
116           ImmersiveModeControllerChromeos::ANIMATE_REVEAL_NO));
117     }
118   }
119 
VerifyButtonsInImmersiveMode(BrowserNonClientFrameViewAsh * frame_view)120   void VerifyButtonsInImmersiveMode(BrowserNonClientFrameViewAsh* frame_view) {
121     WebAppFrameToolbarView* container =
122         frame_view->web_app_frame_toolbar_for_testing();
123     views::test::InkDropHostViewTestApi ink_drop_api(
124         container->GetAppMenuButton());
125     EXPECT_TRUE(container->GetContentSettingContainerForTesting()->layer());
126     EXPECT_EQ(views::InkDropHostView::InkDropMode::ON,
127               ink_drop_api.ink_drop_mode());
128   }
129 
browser()130   Browser* browser() { return browser_; }
browser_view()131   BrowserView* browser_view() {
132     return BrowserView::GetBrowserViewForBrowser(browser_);
133   }
controller()134   ImmersiveModeController* controller() { return controller_; }
titlebar_animation_delay()135   base::TimeDelta titlebar_animation_delay() {
136     return WebAppFrameToolbarView::kTitlebarAnimationDelay;
137   }
138 
139  private:
140   web_app::AppId app_id;
141   Browser* browser_ = nullptr;
142   ImmersiveModeController* controller_ = nullptr;
143 
144   std::unique_ptr<ImmersiveRevealedLock> revealed_lock_;
145 
146   net::EmbeddedTestServer https_server_;
147   // Similar to net::MockCertVerifier, but also updates the CertVerifier
148   // used by the NetworkService.
149   content::ContentMockCertVerifier cert_verifier_;
150 
151   DISALLOW_COPY_AND_ASSIGN(ImmersiveModeControllerChromeosWebAppBrowserTest);
152 };
153 
154 // Test the layout and visibility of the TopContainerView and web contents when
155 // a web app is put into immersive fullscreen.
IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerChromeosWebAppBrowserTest,Layout)156 IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerChromeosWebAppBrowserTest,
157                        Layout) {
158   LaunchAppBrowser();
159   TabStrip* tabstrip = browser_view()->tabstrip();
160   ToolbarView* toolbar = browser_view()->toolbar();
161   views::WebView* contents_web_view = browser_view()->contents_web_view();
162   views::View* top_container = browser_view()->top_container();
163 
164   // Immersive fullscreen starts out disabled.
165   ASSERT_FALSE(browser_view()->GetWidget()->IsFullscreen());
166   ASSERT_FALSE(controller()->IsEnabled());
167 
168   // The tabstrip is not visible for web apps.
169   EXPECT_FALSE(tabstrip->GetVisible());
170   EXPECT_TRUE(toolbar->GetVisible());
171 
172   // The window header should be above the web contents.
173   int header_height = GetBoundsInWidget(contents_web_view).y();
174 
175   ToggleFullscreen();
176   EXPECT_TRUE(browser_view()->GetWidget()->IsFullscreen());
177   EXPECT_TRUE(controller()->IsEnabled());
178   EXPECT_FALSE(controller()->IsRevealed());
179 
180   // Entering immersive fullscreen should make the web contents flush with the
181   // top of the widget. The popup browser type doesn't support tabstrip and
182   // toolbar feature, thus invisible.
183   EXPECT_FALSE(tabstrip->GetVisible());
184   EXPECT_FALSE(toolbar->GetVisible());
185   EXPECT_TRUE(top_container->GetVisibleBounds().IsEmpty());
186   EXPECT_EQ(0, GetBoundsInWidget(contents_web_view).y());
187 
188   // Reveal the window header.
189   AttemptReveal();
190 
191   // The tabstrip should still be hidden and the web contents should still be
192   // flush with the top of the screen.
193   EXPECT_FALSE(tabstrip->GetVisible());
194   EXPECT_TRUE(toolbar->GetVisible());
195   EXPECT_EQ(0, GetBoundsInWidget(contents_web_view).y());
196 
197   // During an immersive reveal, the window header should be painted to the
198   // TopContainerView. The TopContainerView should be flush with the top of the
199   // widget and have |header_height|.
200   gfx::Rect top_container_bounds_in_widget(GetBoundsInWidget(top_container));
201   EXPECT_EQ(0, top_container_bounds_in_widget.y());
202   EXPECT_EQ(header_height, top_container_bounds_in_widget.height());
203 
204   // Exit immersive fullscreen. The web contents should be back below the window
205   // header.
206   ToggleFullscreen();
207   EXPECT_FALSE(browser_view()->GetWidget()->IsFullscreen());
208   EXPECT_FALSE(controller()->IsEnabled());
209   EXPECT_FALSE(tabstrip->GetVisible());
210   EXPECT_TRUE(toolbar->GetVisible());
211   EXPECT_EQ(header_height, GetBoundsInWidget(contents_web_view).y());
212 }
213 
214 // Verify the immersive mode status is as expected in tablet mode (titlebars are
215 // autohidden in tablet mode).
216 
217 // Crashes on Linux Chromium OS ASan LSan Tests.  http://crbug.com/1091606
IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerChromeosWebAppBrowserTest,DISABLED_ImmersiveModeStatusTabletMode)218 IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerChromeosWebAppBrowserTest,
219                        DISABLED_ImmersiveModeStatusTabletMode) {
220   LaunchAppBrowser();
221   ASSERT_FALSE(controller()->IsEnabled());
222 
223   aura::Window* aura_window = browser_view()->frame()->GetNativeWindow();
224   // Verify that after entering tablet mode, immersive mode is enabled, and the
225   // the associated window's top inset is 0 (the top of the window is not
226   // visible).
227   ASSERT_NO_FATAL_FAILURE(
228       ash::ShellTestApi().SetTabletModeEnabledForTest(true));
229   EXPECT_TRUE(controller()->IsEnabled());
230   EXPECT_EQ(0, aura_window->GetProperty(aura::client::kTopViewInset));
231 
232   // Verify that after minimizing, immersive mode is disabled.
233   browser()->window()->Minimize();
234   EXPECT_TRUE(browser()->window()->IsMinimized());
235   EXPECT_FALSE(controller()->IsEnabled());
236 
237   // Verify that after showing the browser, immersive mode is reenabled.
238   browser()->window()->Show();
239   EXPECT_TRUE(controller()->IsEnabled());
240 
241   // Verify that immersive mode remains if fullscreen is toggled while in tablet
242   // mode.
243   ToggleFullscreen();
244   EXPECT_TRUE(controller()->IsEnabled());
245   ASSERT_NO_FATAL_FAILURE(
246       ash::ShellTestApi().SetTabletModeEnabledForTest(false));
247   EXPECT_TRUE(controller()->IsEnabled());
248 
249   // Verify that immersive mode remains if the browser was fullscreened when
250   // entering tablet mode.
251   ASSERT_NO_FATAL_FAILURE(
252       ash::ShellTestApi().SetTabletModeEnabledForTest(true));
253   EXPECT_TRUE(controller()->IsEnabled());
254 
255   // Verify that if the browser is not fullscreened, upon exiting tablet mode,
256   // immersive mode is not enabled, and the associated window's top inset is
257   // greater than 0 (the top of the window is visible).
258   ToggleFullscreen();
259   EXPECT_TRUE(controller()->IsEnabled());
260   ASSERT_NO_FATAL_FAILURE(
261       ash::ShellTestApi().SetTabletModeEnabledForTest(false));
262   EXPECT_FALSE(controller()->IsEnabled());
263 
264   EXPECT_GT(aura_window->GetProperty(aura::client::kTopViewInset), 0);
265 }
266 
267 // Verify that the frame layout is as expected when using immersive mode in
268 // tablet mode.
IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerChromeosWebAppBrowserTest,FrameLayoutToggleTabletMode)269 IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerChromeosWebAppBrowserTest,
270                        FrameLayoutToggleTabletMode) {
271   LaunchAppBrowser();
272   ASSERT_FALSE(controller()->IsEnabled());
273   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
274   BrowserNonClientFrameViewAsh* frame_view =
275       static_cast<BrowserNonClientFrameViewAsh*>(
276           browser_view->GetWidget()->non_client_view()->frame_view());
277   chromeos::FrameCaptionButtonContainerView* caption_button_container =
278       frame_view->caption_button_container_;
279   chromeos::FrameCaptionButtonContainerView::TestApi frame_test_api(
280       caption_button_container);
281 
282   EXPECT_TRUE(frame_test_api.size_button()->GetVisible());
283 
284   // Verify the size button is hidden in tablet mode.
285   ash::ShellTestApi().SetTabletModeEnabledForTest(true);
286   frame_test_api.EndAnimations();
287 
288   EXPECT_TRUE(frame_test_api.size_button()->GetVisible());
289 
290   VerifyButtonsInImmersiveMode(frame_view);
291 
292   // Verify the size button is visible in clamshell mode, and that it does not
293   // cover the other two buttons.
294   ash::ShellTestApi().SetTabletModeEnabledForTest(false);
295   frame_test_api.EndAnimations();
296 
297   EXPECT_TRUE(frame_test_api.size_button()->GetVisible());
298   EXPECT_FALSE(frame_test_api.size_button()->GetBoundsInScreen().Intersects(
299       frame_test_api.close_button()->GetBoundsInScreen()));
300   EXPECT_FALSE(frame_test_api.size_button()->GetBoundsInScreen().Intersects(
301       frame_test_api.minimize_button()->GetBoundsInScreen()));
302 
303   VerifyButtonsInImmersiveMode(frame_view);
304 }
305 
306 // Verify that the frame layout for new windows is as expected when using
307 // immersive mode in tablet mode.
IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerChromeosWebAppBrowserTest,FrameLayoutStartInTabletMode)308 IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerChromeosWebAppBrowserTest,
309                        FrameLayoutStartInTabletMode) {
310   // Start in tablet mode
311   ash::ShellTestApi().SetTabletModeEnabledForTest(true);
312 
313   BrowserNonClientFrameViewAsh* frame_view = nullptr;
314   {
315     auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
316     base::TestMockTimeTaskRunner::ScopedContext scoped_context(task_runner);
317 
318     // Launch app window while in tablet mode
319     LaunchAppBrowser(false);
320     BrowserView* browser_view =
321         BrowserView::GetBrowserViewForBrowser(browser());
322     frame_view = static_cast<BrowserNonClientFrameViewAsh*>(
323         browser_view->GetWidget()->non_client_view()->frame_view());
324 
325     task_runner->FastForwardBy(titlebar_animation_delay());
326 
327     VerifyButtonsInImmersiveMode(frame_view);
328   }
329 
330   // Verify the size button is visible in clamshell mode, and that it does not
331   // cover the other two buttons.
332   ash::ShellTestApi().SetTabletModeEnabledForTest(false);
333   VerifyButtonsInImmersiveMode(frame_view);
334 }
335 
336 // Tests that the permissions bubble dialog is anchored to the correct location.
337 // The dialog's anchor is normally the app menu button which is on the header.
338 // In immersive mode but not revealed, the app menu button is placed off screen
339 // but still drawn. In this case, we should have a null anchor view so that the
340 // bubble gets placed in the default top left corner. Regression test for
341 // https://crbug.com/1087143.
IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerChromeosWebAppBrowserTest,PermissionsBubbleAnchor)342 IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerChromeosWebAppBrowserTest,
343                        PermissionsBubbleAnchor) {
344   LaunchAppBrowser();
345   auto test_api =
346       std::make_unique<test::PermissionRequestManagerTestApi>(browser());
347   EXPECT_TRUE(test_api->manager());
348 
349   // Add a permission bubble using the test api.
350   test_api->AddSimpleRequest(
351       browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame(),
352       ContentSettingsType::GEOLOCATION);
353 
354   // The permission prompt is shown asynchronously. Without immersive mode
355   // enabled the anchor should exist.
356   base::RunLoop().RunUntilIdle();
357   views::Widget* prompt_window = test_api->GetPromptWindow();
358   views::BubbleDialogDelegate* bubble_dialog =
359       prompt_window->AsWidget()->widget_delegate()->AsBubbleDialogDelegate();
360   ASSERT_TRUE(bubble_dialog);
361   EXPECT_TRUE(bubble_dialog->GetAnchorView());
362 
363   // Turn on immersive, but do not reveal. The app menu button is hidden from
364   // sight so the anchor should be null. The bubble will get placed in the top
365   // left corner of the app.
366   auto* immersive_mode_controller =
367       BrowserView::GetBrowserViewForBrowser(browser())
368           ->immersive_mode_controller();
369   immersive_mode_controller->SetEnabled(true);
370   EXPECT_FALSE(immersive_mode_controller->IsRevealed());
371   EXPECT_FALSE(bubble_dialog->GetAnchorView());
372 
373   // Reveal the header. The anchor should exist since the app menu button is now
374   // visible.
375   {
376     std::unique_ptr<ImmersiveRevealedLock> focus_reveal_lock(
377         immersive_mode_controller->GetRevealedLock(
378             ImmersiveModeController::ANIMATE_REVEAL_YES));
379     EXPECT_TRUE(immersive_mode_controller->IsRevealed());
380     EXPECT_TRUE(bubble_dialog->GetAnchorView());
381   }
382 
383   EXPECT_FALSE(immersive_mode_controller->IsRevealed());
384   EXPECT_FALSE(bubble_dialog->GetAnchorView());
385 }
386