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/events/test/event_generator.h"
24 #include "ui/views/controls/webview/webview.h"
28 // The path of the test application within the "platform_apps" directory.
29 const char kCustomLauncherPagePath
[] = "custom_launcher_page";
31 // The app ID of the test application.
32 const char kCustomLauncherPageID
[] = "lmadimbbgapmngbiclpjjngmdickadpl";
36 // Browser tests for custom launcher pages, platform apps that run as a page in
37 // the app launcher. Within this test class, LoadAndLaunchPlatformApp runs the
38 // app inside the launcher, not as a standalone background page.
40 class CustomLauncherPageBrowserTest
41 : public extensions::PlatformAppBrowserTest
{
43 CustomLauncherPageBrowserTest() {}
45 void SetUpCommandLine(base::CommandLine
* command_line
) override
{
46 PlatformAppBrowserTest::SetUpCommandLine(command_line
);
48 // Custom launcher pages only work in the experimental app list.
49 command_line
->AppendSwitch(app_list::switches::kEnableExperimentalAppList
);
51 // Ensure the app list does not close during the test.
52 command_line
->AppendSwitch(
53 app_list::switches::kDisableAppListDismissOnBlur
);
55 // The test app must be whitelisted to use launcher_page.
56 command_line
->AppendSwitchASCII(
57 extensions::switches::kWhitelistedExtensionID
, kCustomLauncherPageID
);
60 // Open the launcher. Ignores the Extension argument (this will simply
61 // activate any loaded launcher pages).
62 void LaunchPlatformApp(const extensions::Extension
* /*unused*/) override
{
63 AppListService
* service
=
64 AppListService::Get(chrome::HOST_DESKTOP_TYPE_NATIVE
);
66 service
->ShowForProfile(browser()->profile());
69 app_list::AppListView
* GetAppListView() {
70 app_list::AppListView
* app_list_view
= nullptr;
71 #if defined(OS_CHROMEOS)
72 ash::Shell
* shell
= ash::Shell::GetInstance();
73 app_list_view
= shell
->GetAppListView();
74 EXPECT_TRUE(shell
->GetAppListTargetVisibility());
76 AppListServiceViews
* service
= static_cast<AppListServiceViews
*>(
77 AppListService::Get(chrome::HOST_DESKTOP_TYPE_NATIVE
));
78 // The app list should have loaded instantly since the profile is already
80 EXPECT_TRUE(service
->IsAppListVisible());
81 app_list_view
= service
->shower().app_list();
86 // Set the active page on the app list, according to |state|. Does not wait
87 // for any animation or custom page to complete.
88 void SetActivePageAndVerify(app_list::AppListModel::State state
) {
89 app_list::ContentsView
* contents_view
=
90 GetAppListView()->app_list_main_view()->contents_view();
91 contents_view
->SetActivePage(contents_view
->GetPageIndexForState(state
));
92 EXPECT_TRUE(contents_view
->IsStateActive(state
));
95 void SetCustomLauncherPageEnabled(bool enabled
) {
96 const base::string16 kLauncherPageDisableScript
=
97 base::ASCIIToUTF16("disableCustomLauncherPage();");
98 const base::string16 kLauncherPageEnableScript
=
99 base::ASCIIToUTF16("enableCustomLauncherPage();");
101 app_list::ContentsView
* contents_view
=
102 GetAppListView()->app_list_main_view()->contents_view();
103 views::WebView
* custom_page_view
=
104 static_cast<views::WebView
*>(contents_view
->custom_page_view());
105 content::RenderFrameHost
* custom_page_frame
=
106 custom_page_view
->GetWebContents()->GetMainFrame();
108 const char* test_message
=
109 enabled
? "launcherPageEnabled" : "launcherPageDisabled";
111 ExtensionTestMessageListener
listener(test_message
, false);
112 custom_page_frame
->ExecuteJavaScript(enabled
? kLauncherPageEnableScript
113 : kLauncherPageDisableScript
);
114 listener
.WaitUntilSatisfied();
118 DISALLOW_COPY_AND_ASSIGN(CustomLauncherPageBrowserTest
);
121 IN_PROC_BROWSER_TEST_F(CustomLauncherPageBrowserTest
,
122 OpenLauncherAndSwitchToCustomPage
) {
123 LoadAndLaunchPlatformApp(kCustomLauncherPagePath
, "Launched");
124 app_list::AppListView
* app_list_view
= GetAppListView();
125 app_list::ContentsView
* contents_view
=
126 app_list_view
->app_list_main_view()->contents_view();
129 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
132 ExtensionTestMessageListener
listener("onPageProgressAt1", false);
133 contents_view
->SetActivePage(contents_view
->GetPageIndexForState(
134 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
136 listener
.WaitUntilSatisfied();
139 ExtensionTestMessageListener
listener("onPageProgressAt0", false);
140 contents_view
->SetActivePage(contents_view
->GetPageIndexForState(
141 app_list::AppListModel::STATE_START
));
143 listener
.WaitUntilSatisfied();
147 // Test that the app list will switch to the custom launcher page by sending a
148 // click inside the clickzone, or a mouse scroll event.
149 IN_PROC_BROWSER_TEST_F(CustomLauncherPageBrowserTest
,
150 EventsActivateSwitchToCustomPage
) {
151 LoadAndLaunchPlatformApp(kCustomLauncherPagePath
, "Launched");
152 // Use an event generator to ensure targeting is correct.
153 app_list::AppListView
* app_list_view
= GetAppListView();
154 app_list::ContentsView
* contents_view
=
155 app_list_view
->app_list_main_view()->contents_view();
156 gfx::NativeWindow window
= app_list_view
->GetWidget()->GetNativeWindow();
157 ui::test::EventGenerator
event_generator(window
->GetRootWindow(), window
);
159 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
161 // Find the clickzone.
162 gfx::Rect bounds
= contents_view
->GetCustomPageCollapsedBounds();
163 bounds
.Intersect(contents_view
->bounds());
164 gfx::Point point_in_clickzone
= bounds
.CenterPoint();
165 views::View::ConvertPointToWidget(contents_view
, &point_in_clickzone
);
167 // First try clicking 10px above the clickzone.
168 gfx::Point point_above_clickzone
= point_in_clickzone
;
169 point_above_clickzone
.set_y(bounds
.y() - 10);
170 views::View::ConvertPointToWidget(contents_view
, &point_above_clickzone
);
172 event_generator
.MoveMouseRelativeTo(window
, point_above_clickzone
);
173 event_generator
.ClickLeftButton();
175 // Should stay on the start page.
177 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
179 // Now click in the clickzone.
180 event_generator
.MoveMouseRelativeTo(window
, point_in_clickzone
);
181 // First, try disabling the custom page view. Click should do nothing.
182 SetCustomLauncherPageEnabled(false);
183 event_generator
.ClickLeftButton();
185 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
186 // Click again with it enabled. The active state should update immediately.
187 SetCustomLauncherPageEnabled(true);
188 event_generator
.ClickLeftButton();
189 EXPECT_TRUE(contents_view
->IsStateActive(
190 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
192 // Back to the start page. And send a mouse wheel event.
193 SetActivePageAndVerify(app_list::AppListModel::STATE_START
);
194 // Generate wheel events above the clickzone.
195 event_generator
.MoveMouseRelativeTo(window
, point_above_clickzone
);
196 // Scrolling left, right or up should do nothing.
197 event_generator
.MoveMouseWheel(-5, 0);
198 event_generator
.MoveMouseWheel(5, 0);
199 event_generator
.MoveMouseWheel(0, 5);
201 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
202 // Scroll down to open launcher page.
203 event_generator
.MoveMouseWheel(0, -5);
204 EXPECT_TRUE(contents_view
->IsStateActive(
205 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
207 // Constants for gesture/trackpad events.
208 const base::TimeDelta step_delay
= base::TimeDelta::FromMilliseconds(300);
209 const int num_steps
= 5;
210 const int num_fingers
= 2;
212 #if defined(OS_CHROMEOS)
213 // Gesture events need to be in host coordinates. On Desktop platforms, the
214 // Widget is the host, so nothing needs to be done. On ChromeOS, the points
215 // need to be put into screen coordinates. This works because the root window
216 // assumes it fills the screen.
217 point_in_clickzone
= bounds
.CenterPoint();
218 point_above_clickzone
.SetPoint(point_in_clickzone
.x(), bounds
.y() - 10);
219 views::View::ConvertPointToScreen(contents_view
, &point_above_clickzone
);
220 views::View::ConvertPointToScreen(contents_view
, &point_in_clickzone
);
223 // Back to the start page. And send a scroll gesture.
224 SetActivePageAndVerify(app_list::AppListModel::STATE_START
);
225 // Going down should do nothing.
226 event_generator
.GestureScrollSequence(
227 point_above_clickzone
, point_in_clickzone
, step_delay
, num_steps
);
229 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
230 // Now go up - should change state.
231 event_generator
.GestureScrollSequence(
232 point_in_clickzone
, point_above_clickzone
, step_delay
, num_steps
);
233 EXPECT_TRUE(contents_view
->IsStateActive(
234 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
236 // Back to the start page. And send a trackpad scroll event.
237 SetActivePageAndVerify(app_list::AppListModel::STATE_START
);
238 // Going down left, right or up should do nothing.
239 event_generator
.ScrollSequence(point_in_clickzone
, step_delay
, -5, 0,
240 num_steps
, num_fingers
);
241 event_generator
.ScrollSequence(point_in_clickzone
, step_delay
, 5, 0,
242 num_steps
, num_fingers
);
243 event_generator
.ScrollSequence(point_in_clickzone
, step_delay
, 0, 5,
244 num_steps
, num_fingers
);
246 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
247 // Scroll up to open launcher page.
248 event_generator
.ScrollSequence(point_in_clickzone
, step_delay
, 0, -5,
249 num_steps
, num_fingers
);
250 EXPECT_TRUE(contents_view
->IsStateActive(
251 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
253 // Back to the start page. And send a tap gesture.
254 SetActivePageAndVerify(app_list::AppListModel::STATE_START
);
255 // Tapping outside the clickzone should do nothing.
256 event_generator
.GestureTapAt(point_above_clickzone
);
258 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
259 // Now tap in the clickzone.
260 event_generator
.GestureTapAt(point_in_clickzone
);
261 EXPECT_TRUE(contents_view
->IsStateActive(
262 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
265 IN_PROC_BROWSER_TEST_F(CustomLauncherPageBrowserTest
, LauncherPageSubpages
) {
266 LoadAndLaunchPlatformApp(kCustomLauncherPagePath
, "Launched");
268 app_list::AppListView
* app_list_view
= GetAppListView();
269 app_list::AppListModel
* model
= app_list_view
->app_list_main_view()->model();
270 app_list::ContentsView
* contents_view
=
271 app_list_view
->app_list_main_view()->contents_view();
274 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
277 ExtensionTestMessageListener
listener("onPageProgressAt1", false);
278 contents_view
->SetActivePage(contents_view
->GetPageIndexForState(
279 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
280 listener
.WaitUntilSatisfied();
281 EXPECT_TRUE(contents_view
->IsStateActive(
282 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
283 // The app pushes 2 subpages when the launcher page is shown.
284 EXPECT_EQ(2, model
->custom_launcher_page_subpage_depth());
289 ExtensionTestMessageListener
listener("onPopSubpage", false);
290 EXPECT_TRUE(contents_view
->Back());
291 listener
.WaitUntilSatisfied();
292 EXPECT_TRUE(contents_view
->IsStateActive(
293 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
294 EXPECT_EQ(1, model
->custom_launcher_page_subpage_depth());
297 ExtensionTestMessageListener
listener("onPopSubpage", false);
298 EXPECT_TRUE(contents_view
->Back());
299 listener
.WaitUntilSatisfied();
300 EXPECT_TRUE(contents_view
->IsStateActive(
301 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
302 EXPECT_EQ(0, model
->custom_launcher_page_subpage_depth());
305 // Once all subpages are popped, the start page should show.
306 EXPECT_TRUE(contents_view
->Back());
308 // Immediately finish the animation.
309 contents_view
->Layout();
311 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
314 IN_PROC_BROWSER_TEST_F(CustomLauncherPageBrowserTest
,
316 const base::string16 kLauncherPageShowScript
=
317 base::ASCIIToUTF16("chrome.launcherPage.show();");
319 LoadAndLaunchPlatformApp(kCustomLauncherPagePath
, "Launched");
320 app_list::AppListView
* app_list_view
= GetAppListView();
321 app_list::ContentsView
* contents_view
=
322 app_list_view
->app_list_main_view()->contents_view();
324 views::WebView
* custom_page_view
=
325 static_cast<views::WebView
*>(contents_view
->custom_page_view());
327 content::RenderFrameHost
* custom_page_frame
=
328 custom_page_view
->GetWebContents()->GetMainFrame();
331 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
333 // Ensure launcherPage.show() will switch the page to the custom launcher page
334 // if the app launcher is already showing.
336 ExtensionTestMessageListener
listener("onPageProgressAt1", false);
337 custom_page_frame
->ExecuteJavaScript(kLauncherPageShowScript
);
339 listener
.WaitUntilSatisfied();
340 EXPECT_TRUE(contents_view
->IsStateActive(
341 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
344 // Ensure launcherPage.show() will show the app list if it's hidden.
346 // Close the app list immediately.
347 app_list_view
->Close();
348 app_list_view
->GetWidget()->Close();
350 ExtensionTestMessageListener
listener("onPageProgressAt1", false);
351 custom_page_frame
->ExecuteJavaScript(kLauncherPageShowScript
);
353 listener
.WaitUntilSatisfied();
355 // The app list view will have changed on ChromeOS.
356 app_list_view
= GetAppListView();
357 contents_view
= app_list_view
->app_list_main_view()->contents_view();
358 EXPECT_TRUE(contents_view
->IsStateActive(
359 app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
));
363 IN_PROC_BROWSER_TEST_F(CustomLauncherPageBrowserTest
, LauncherPageSetEnabled
) {
364 LoadAndLaunchPlatformApp(kCustomLauncherPagePath
, "Launched");
365 app_list::AppListView
* app_list_view
= GetAppListView();
366 app_list::AppListModel
* model
= app_list_view
->app_list_main_view()->model();
367 app_list::ContentsView
* contents_view
=
368 app_list_view
->app_list_main_view()->contents_view();
370 views::WebView
* custom_page_view
=
371 static_cast<views::WebView
*>(contents_view
->custom_page_view());
373 contents_view
->IsStateActive(app_list::AppListModel::STATE_START
));
375 EXPECT_TRUE(model
->custom_launcher_page_enabled());
376 EXPECT_TRUE(custom_page_view
->visible());
378 SetCustomLauncherPageEnabled(false);
379 EXPECT_FALSE(model
->custom_launcher_page_enabled());
380 EXPECT_FALSE(custom_page_view
->visible());
382 SetCustomLauncherPageEnabled(true);
383 EXPECT_TRUE(model
->custom_launcher_page_enabled());
384 EXPECT_TRUE(custom_page_view
->visible());