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