1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
8 #include "base/command_line.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/apps/app_browsertest_util.h"
11 #include "chrome/browser/ui/app_list/app_list_service.h"
12 #include "chrome/browser/ui/app_list/app_list_service_views.h"
13 #include "chrome/browser/ui/app_list/app_list_shower_views.h"
14 #include "content/public/browser/render_frame_host.h"
15 #include "content/public/browser/web_contents.h"
16 #include "extensions/common/extension.h"
17 #include "extensions/common/switches.h"
18 #include "extensions/test/extension_test_message_listener.h"
19 #include "ui/app_list/app_list_switches.h"
20 #include "ui/app_list/views/app_list_main_view.h"
21 #include "ui/app_list/views/app_list_view.h"
22 #include "ui/app_list/views/contents_view.h"
23 #include "ui/app_list/views/custom_launcher_page_view.h"
24 #include "ui/app_list/views/search_box_view.h"
25 #include "ui/events/test/event_generator.h"
26 #include "ui/views/controls/textfield/textfield.h"
27 #include "ui/views/controls/webview/webview.h"
28 #include "ui/views/focus/focus_manager.h"
32 // The path of the test application within the "platform_apps" directory.
33 const char kCustomLauncherPagePath
[] = "custom_launcher_page";
35 // The app ID of the test application.
36 const char kCustomLauncherPageID
[] = "lmadimbbgapmngbiclpjjngmdickadpl";
40 // Browser tests for custom launcher pages, platform apps that run as a page in
41 // the app launcher. Within this test class, LoadAndLaunchPlatformApp runs the
42 // app inside the launcher, not as a standalone background page.
44 class CustomLauncherPageBrowserTest
45 : public extensions::PlatformAppBrowserTest
{
47 CustomLauncherPageBrowserTest() {}
49 void SetUpCommandLine(base::CommandLine
* command_line
) override
{
50 PlatformAppBrowserTest::SetUpCommandLine(command_line
);
52 // Custom launcher pages only work in the experimental app list.
53 command_line
->AppendSwitch(app_list::switches::kEnableExperimentalAppList
);
55 // Ensure the app list does not close during the test.
56 command_line
->AppendSwitch(
57 app_list::switches::kDisableAppListDismissOnBlur
);
59 // The test app must be whitelisted to use launcher_page.
60 command_line
->AppendSwitchASCII(
61 extensions::switches::kWhitelistedExtensionID
, kCustomLauncherPageID
);
64 // Open the launcher. Ignores the Extension argument (this will simply
65 // activate any loaded launcher pages).
66 void LaunchPlatformApp(const extensions::Extension
* /*unused*/) override
{
67 AppListService
* service
=
68 AppListService::Get(chrome::HOST_DESKTOP_TYPE_NATIVE
);
70 service
->ShowForProfile(browser()->profile());
73 app_list::AppListView
* GetAppListView() {
74 app_list::AppListView
* app_list_view
= nullptr;
75 #if defined(OS_CHROMEOS)
76 ash::Shell
* shell
= ash::Shell::GetInstance();
77 app_list_view
= shell
->GetAppListView();
78 EXPECT_TRUE(shell
->GetAppListTargetVisibility());
80 AppListServiceViews
* service
= static_cast<AppListServiceViews
*>(
81 AppListService::Get(chrome::HOST_DESKTOP_TYPE_NATIVE
));
82 // The app list should have loaded instantly since the profile is already
84 EXPECT_TRUE(service
->IsAppListVisible());
85 app_list_view
= service
->shower().app_list();
90 // Set the active page on the app list, according to |state|. Does not wait
91 // for any animation or custom page to complete.
92 void SetActiveStateAndVerify(app_list::AppListModel::State state
) {
93 app_list::ContentsView
* contents_view
=
94 GetAppListView()->app_list_main_view()->contents_view();
95 contents_view
->SetActiveState(state
);
96 EXPECT_TRUE(contents_view
->IsStateActive(state
));
99 void SetCustomLauncherPageEnabled(bool enabled
) {
100 const base::string16 kLauncherPageDisableScript
=
101 base::ASCIIToUTF16("disableCustomLauncherPage();");
102 const base::string16 kLauncherPageEnableScript
=
103 base::ASCIIToUTF16("enableCustomLauncherPage();");
105 app_list::ContentsView
* contents_view
=
106 GetAppListView()->app_list_main_view()->contents_view();
107 views::WebView
* custom_page_view
= static_cast<views::WebView
*>(
108 contents_view
->custom_page_view()->custom_launcher_page_contents());
109 content::RenderFrameHost
* custom_page_frame
=
110 custom_page_view
->GetWebContents()->GetMainFrame();
112 const char* test_message
=
113 enabled
? "launcherPageEnabled" : "launcherPageDisabled";
115 ExtensionTestMessageListener
listener(test_message
, false);
116 custom_page_frame
->ExecuteJavaScriptForTests(
117 enabled
? kLauncherPageEnableScript
: kLauncherPageDisableScript
);
118 listener
.WaitUntilSatisfied();
122 DISALLOW_COPY_AND_ASSIGN(CustomLauncherPageBrowserTest
);
125 IN_PROC_BROWSER_TEST_F(CustomLauncherPageBrowserTest
,
126 OpenLauncherAndSwitchToCustomPage
) {
127 LoadAndLaunchPlatformApp(kCustomLauncherPagePath
, "Launched");
128 app_list::AppListView
* app_list_view
= GetAppListView();
129 app_list::ContentsView
* contents_view
=
130 app_list_view
->app_list_main_view()->contents_view();
133 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
136 ExtensionTestMessageListener
listener("onPageProgressAt1", false);
137 contents_view
->SetActiveState(
138 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
);
140 listener
.WaitUntilSatisfied();
143 ExtensionTestMessageListener
listener("onPageProgressAt0", false);
144 contents_view
->SetActiveState(app_list::AppListModel::STATE_START
);
146 listener
.WaitUntilSatisfied();
150 // Test that the app list will switch to the custom launcher page by sending a
151 // click inside the clickzone, or a mouse scroll event.
152 IN_PROC_BROWSER_TEST_F(CustomLauncherPageBrowserTest
,
153 EventsActivateSwitchToCustomPage
) {
154 LoadAndLaunchPlatformApp(kCustomLauncherPagePath
, "Launched");
155 // Use an event generator to ensure targeting is correct.
156 app_list::AppListView
* app_list_view
= GetAppListView();
158 // On ChromeOS, displaying the app list can be delayed while icons finish
159 // loading. Explicitly show it to ensure the event generator gets meaningful
160 // coordinates. See http://crbug.com/525128.
161 app_list_view
->GetWidget()->Show();
163 app_list::ContentsView
* contents_view
=
164 app_list_view
->app_list_main_view()->contents_view();
165 gfx::NativeWindow window
= app_list_view
->GetWidget()->GetNativeWindow();
166 ui::test::EventGenerator
event_generator(window
->GetRootWindow(), window
);
168 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
170 // Find the clickzone.
172 contents_view
->custom_page_view()->GetCollapsedLauncherPageBounds();
173 bounds
.Intersect(contents_view
->bounds());
174 gfx::Point point_in_clickzone
= bounds
.CenterPoint();
175 views::View::ConvertPointToWidget(contents_view
, &point_in_clickzone
);
177 // First try clicking 10px above the clickzone.
178 gfx::Point point_above_clickzone
= point_in_clickzone
;
179 point_above_clickzone
.set_y(bounds
.y() - 10);
180 views::View::ConvertPointToWidget(contents_view
, &point_above_clickzone
);
182 event_generator
.MoveMouseRelativeTo(window
, point_above_clickzone
);
183 event_generator
.ClickLeftButton();
185 // Should stay on the start page.
187 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
189 // Now click in the clickzone.
190 event_generator
.MoveMouseRelativeTo(window
, point_in_clickzone
);
191 // First, try disabling the custom page view. Click should do nothing.
192 SetCustomLauncherPageEnabled(false);
193 event_generator
.ClickLeftButton();
195 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
196 // Click again with it enabled. The active state should update immediately.
197 SetCustomLauncherPageEnabled(true);
198 event_generator
.ClickLeftButton();
199 EXPECT_TRUE(contents_view
->IsStateActive(
200 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
202 // Back to the start page. And send a mouse wheel event.
203 SetActiveStateAndVerify(app_list::AppListModel::STATE_START
);
204 // Generate wheel events above the clickzone.
205 event_generator
.MoveMouseRelativeTo(window
, point_above_clickzone
);
206 // Scrolling left, right or up should do nothing.
207 event_generator
.MoveMouseWheel(-5, 0);
208 event_generator
.MoveMouseWheel(5, 0);
209 event_generator
.MoveMouseWheel(0, 5);
211 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
212 // Scroll down to open launcher page.
213 event_generator
.MoveMouseWheel(0, -5);
214 EXPECT_TRUE(contents_view
->IsStateActive(
215 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
217 // Constants for gesture/trackpad events.
218 const base::TimeDelta step_delay
= base::TimeDelta::FromMilliseconds(300);
219 const int num_steps
= 5;
220 const int num_fingers
= 2;
222 #if defined(OS_CHROMEOS)
223 // Gesture events need to be in host coordinates. On Desktop platforms, the
224 // Widget is the host, so nothing needs to be done. On ChromeOS, the points
225 // need to be put into screen coordinates. This works because the root window
226 // assumes it fills the screen.
227 point_in_clickzone
= bounds
.CenterPoint();
228 point_above_clickzone
.SetPoint(point_in_clickzone
.x(), bounds
.y() - 10);
229 views::View::ConvertPointToScreen(contents_view
, &point_above_clickzone
);
230 views::View::ConvertPointToScreen(contents_view
, &point_in_clickzone
);
233 // Back to the start page. And send a scroll gesture.
234 SetActiveStateAndVerify(app_list::AppListModel::STATE_START
);
235 // Going down should do nothing.
236 event_generator
.GestureScrollSequence(
237 point_above_clickzone
, point_in_clickzone
, step_delay
, num_steps
);
239 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
240 // Now go up - should change state.
241 event_generator
.GestureScrollSequence(
242 point_in_clickzone
, point_above_clickzone
, step_delay
, num_steps
);
243 EXPECT_TRUE(contents_view
->IsStateActive(
244 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
246 // Back to the start page. And send a trackpad scroll event.
247 SetActiveStateAndVerify(app_list::AppListModel::STATE_START
);
248 // Going down left, right or up should do nothing.
249 event_generator
.ScrollSequence(point_in_clickzone
, step_delay
, -5, 0,
250 num_steps
, num_fingers
);
251 event_generator
.ScrollSequence(point_in_clickzone
, step_delay
, 5, 0,
252 num_steps
, num_fingers
);
253 event_generator
.ScrollSequence(point_in_clickzone
, step_delay
, 0, 5,
254 num_steps
, num_fingers
);
256 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
257 // Scroll up to open launcher page.
258 event_generator
.ScrollSequence(point_in_clickzone
, step_delay
, 0, -5,
259 num_steps
, num_fingers
);
260 EXPECT_TRUE(contents_view
->IsStateActive(
261 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
263 // Back to the start page. And send a tap gesture.
264 SetActiveStateAndVerify(app_list::AppListModel::STATE_START
);
265 // Tapping outside the clickzone should do nothing.
266 event_generator
.GestureTapAt(point_above_clickzone
);
268 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
269 // Now tap in the clickzone.
270 event_generator
.GestureTapAt(point_in_clickzone
);
271 EXPECT_TRUE(contents_view
->IsStateActive(
272 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
275 IN_PROC_BROWSER_TEST_F(CustomLauncherPageBrowserTest
, LauncherPageSubpages
) {
276 LoadAndLaunchPlatformApp(kCustomLauncherPagePath
, "Launched");
278 app_list::AppListView
* app_list_view
= GetAppListView();
279 app_list::AppListModel
* model
= app_list_view
->app_list_main_view()->model();
280 app_list::ContentsView
* contents_view
=
281 app_list_view
->app_list_main_view()->contents_view();
284 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
287 ExtensionTestMessageListener
listener("onPageProgressAt1", false);
288 contents_view
->SetActiveState(
289 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
);
290 listener
.WaitUntilSatisfied();
291 EXPECT_TRUE(contents_view
->IsStateActive(
292 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
293 // The app pushes 2 subpages when the launcher page is shown.
294 EXPECT_EQ(2, model
->custom_launcher_page_subpage_depth());
299 ExtensionTestMessageListener
listener("onPopSubpage", false);
300 EXPECT_TRUE(contents_view
->Back());
301 listener
.WaitUntilSatisfied();
302 EXPECT_TRUE(contents_view
->IsStateActive(
303 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
304 EXPECT_EQ(1, model
->custom_launcher_page_subpage_depth());
307 ExtensionTestMessageListener
listener("onPopSubpage", false);
308 EXPECT_TRUE(contents_view
->Back());
309 listener
.WaitUntilSatisfied();
310 EXPECT_TRUE(contents_view
->IsStateActive(
311 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
312 EXPECT_EQ(0, model
->custom_launcher_page_subpage_depth());
315 // Once all subpages are popped, the start page should show.
316 EXPECT_TRUE(contents_view
->Back());
318 // Immediately finish the animation.
319 contents_view
->Layout();
321 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
324 IN_PROC_BROWSER_TEST_F(CustomLauncherPageBrowserTest
, LauncherPageShowAndHide
) {
325 const base::string16 kLauncherPageShowScript
=
326 base::ASCIIToUTF16("chrome.launcherPage.show();");
327 const base::string16 kLauncherPageHideScript
=
328 base::ASCIIToUTF16("hideCustomLauncherPage()");
330 LoadAndLaunchPlatformApp(kCustomLauncherPagePath
, "Launched");
331 app_list::AppListView
* app_list_view
= GetAppListView();
332 app_list::ContentsView
* contents_view
=
333 app_list_view
->app_list_main_view()->contents_view();
335 views::WebView
* custom_page_view
= static_cast<views::WebView
*>(
336 contents_view
->custom_page_view()->custom_launcher_page_contents());
338 content::RenderFrameHost
* custom_page_frame
=
339 custom_page_view
->GetWebContents()->GetMainFrame();
342 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
344 // Ensure launcherPage.show() will switch the page to the custom launcher page
345 // if the app launcher is already showing.
347 ExtensionTestMessageListener
listener("onPageProgressAt1", false);
348 custom_page_frame
->ExecuteJavaScriptForTests(kLauncherPageShowScript
);
350 listener
.WaitUntilSatisfied();
351 EXPECT_TRUE(contents_view
->IsStateActive(
352 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
355 // Ensure launcherPage.show() will show the app list if it's hidden.
357 // Close the app list immediately.
358 app_list_view
->Close();
359 app_list_view
->GetWidget()->Close();
361 ExtensionTestMessageListener
listener("onPageProgressAt1", false);
362 custom_page_frame
->ExecuteJavaScriptForTests(kLauncherPageShowScript
);
364 listener
.WaitUntilSatisfied();
366 // The app list view will have changed on ChromeOS.
367 app_list_view
= GetAppListView();
368 contents_view
= app_list_view
->app_list_main_view()->contents_view();
369 EXPECT_TRUE(contents_view
->IsStateActive(
370 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
373 // Ensure launcherPage.hide() hides the launcher page when it's showing.
375 ExtensionTestMessageListener
listener("onPageProgressAt0", false);
376 custom_page_frame
->ExecuteJavaScriptForTests(kLauncherPageHideScript
);
378 listener
.WaitUntilSatisfied();
381 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
384 // Nothing should happen if hide() is called from the apps page.
386 contents_view
->SetActiveState(app_list::AppListModel::STATE_APPS
, false);
388 ExtensionTestMessageListener
listener("launcherPageHidden", false);
389 custom_page_frame
->ExecuteJavaScriptForTests(kLauncherPageHideScript
);
390 listener
.WaitUntilSatisfied();
393 contents_view
->IsStateActive(app_list::AppListModel::STATE_APPS
));
397 IN_PROC_BROWSER_TEST_F(CustomLauncherPageBrowserTest
, LauncherPageSetEnabled
) {
398 LoadAndLaunchPlatformApp(kCustomLauncherPagePath
, "Launched");
399 app_list::AppListView
* app_list_view
= GetAppListView();
400 app_list::AppListModel
* model
= app_list_view
->app_list_main_view()->model();
401 app_list::ContentsView
* contents_view
=
402 app_list_view
->app_list_main_view()->contents_view();
404 views::View
* custom_page_view
= contents_view
->custom_page_view();
406 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
408 EXPECT_TRUE(model
->custom_launcher_page_enabled());
409 EXPECT_TRUE(custom_page_view
->visible());
411 SetCustomLauncherPageEnabled(false);
412 EXPECT_FALSE(model
->custom_launcher_page_enabled());
413 EXPECT_FALSE(custom_page_view
->visible());
415 SetCustomLauncherPageEnabled(true);
416 EXPECT_TRUE(model
->custom_launcher_page_enabled());
417 EXPECT_TRUE(custom_page_view
->visible());
420 // Currently this is flaky.
421 // Disabled test http://crbug.com/463456
422 IN_PROC_BROWSER_TEST_F(CustomLauncherPageBrowserTest
,
423 LauncherPageFocusTraversal
) {
424 LoadAndLaunchPlatformApp(kCustomLauncherPagePath
, "Launched");
425 app_list::AppListView
* app_list_view
= GetAppListView();
426 app_list::ContentsView
* contents_view
=
427 app_list_view
->app_list_main_view()->contents_view();
428 app_list::SearchBoxView
* search_box_view
=
429 app_list_view
->app_list_main_view()->search_box_view();
432 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
435 ExtensionTestMessageListener
listener("onPageProgressAt1", false);
436 contents_view
->SetActiveState(
437 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
);
438 listener
.WaitUntilSatisfied();
441 // Expect that the search box and webview are the only two focusable views.
442 views::View
* search_box_textfield
= search_box_view
->search_box();
443 views::WebView
* custom_page_webview
= static_cast<views::WebView
*>(
444 contents_view
->custom_page_view()->custom_launcher_page_contents());
445 EXPECT_EQ(custom_page_webview
,
446 app_list_view
->GetFocusManager()->GetNextFocusableView(
447 search_box_textfield
, search_box_textfield
->GetWidget(), false,
450 search_box_textfield
,
451 app_list_view
->GetFocusManager()->GetNextFocusableView(
452 custom_page_webview
, custom_page_webview
->GetWidget(), false, false));
456 search_box_textfield
,
457 app_list_view
->GetFocusManager()->GetNextFocusableView(
458 custom_page_webview
, custom_page_webview
->GetWidget(), true, false));
459 EXPECT_EQ(custom_page_webview
,
460 app_list_view
->GetFocusManager()->GetNextFocusableView(
461 search_box_textfield
, search_box_textfield
->GetWidget(), true,