// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include "ash/shell.h" #include "base/command_line.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/apps/app_browsertest_util.h" #include "chrome/browser/ui/app_list/app_list_service.h" #include "chrome/browser/ui/app_list/app_list_service_views.h" #include "chrome/browser/ui/app_list/app_list_shower_views.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents.h" #include "extensions/common/extension.h" #include "extensions/common/switches.h" #include "extensions/test/extension_test_message_listener.h" #include "ui/app_list/app_list_switches.h" #include "ui/app_list/views/app_list_main_view.h" #include "ui/app_list/views/app_list_view.h" #include "ui/app_list/views/contents_view.h" #include "ui/app_list/views/custom_launcher_page_view.h" #include "ui/app_list/views/search_box_view.h" #include "ui/events/test/event_generator.h" #include "ui/views/controls/textfield/textfield.h" #include "ui/views/controls/webview/webview.h" #include "ui/views/focus/focus_manager.h" namespace { // The path of the test application within the "platform_apps" directory. const char kCustomLauncherPagePath[] = "custom_launcher_page"; // The app ID of the test application. const char kCustomLauncherPageID[] = "lmadimbbgapmngbiclpjjngmdickadpl"; } // namespace // Browser tests for custom launcher pages, platform apps that run as a page in // the app launcher. Within this test class, LoadAndLaunchPlatformApp runs the // app inside the launcher, not as a standalone background page. // the app launcher. class CustomLauncherPageBrowserTest : public extensions::PlatformAppBrowserTest { public: CustomLauncherPageBrowserTest() {} void SetUpCommandLine(base::CommandLine* command_line) override { PlatformAppBrowserTest::SetUpCommandLine(command_line); // Custom launcher pages only work in the experimental app list. command_line->AppendSwitch(app_list::switches::kEnableExperimentalAppList); // Ensure the app list does not close during the test. command_line->AppendSwitch( app_list::switches::kDisableAppListDismissOnBlur); // The test app must be whitelisted to use launcher_page. command_line->AppendSwitchASCII( extensions::switches::kWhitelistedExtensionID, kCustomLauncherPageID); } // Open the launcher. Ignores the Extension argument (this will simply // activate any loaded launcher pages). void LaunchPlatformApp(const extensions::Extension* /*unused*/) override { AppListService* service = AppListService::Get(chrome::HOST_DESKTOP_TYPE_NATIVE); DCHECK(service); service->ShowForProfile(browser()->profile()); } app_list::AppListView* GetAppListView() { app_list::AppListView* app_list_view = nullptr; #if defined(OS_CHROMEOS) ash::Shell* shell = ash::Shell::GetInstance(); app_list_view = shell->GetAppListView(); EXPECT_TRUE(shell->GetAppListTargetVisibility()); #else AppListServiceViews* service = static_cast( AppListService::Get(chrome::HOST_DESKTOP_TYPE_NATIVE)); // The app list should have loaded instantly since the profile is already // loaded. EXPECT_TRUE(service->IsAppListVisible()); app_list_view = service->shower().app_list(); #endif return app_list_view; } // Set the active page on the app list, according to |state|. Does not wait // for any animation or custom page to complete. void SetActiveStateAndVerify(app_list::AppListModel::State state) { app_list::ContentsView* contents_view = GetAppListView()->app_list_main_view()->contents_view(); contents_view->SetActiveState(state); EXPECT_TRUE(contents_view->IsStateActive(state)); } void SetCustomLauncherPageEnabled(bool enabled) { const base::string16 kLauncherPageDisableScript = base::ASCIIToUTF16("disableCustomLauncherPage();"); const base::string16 kLauncherPageEnableScript = base::ASCIIToUTF16("enableCustomLauncherPage();"); app_list::ContentsView* contents_view = GetAppListView()->app_list_main_view()->contents_view(); views::WebView* custom_page_view = static_cast( contents_view->custom_page_view()->custom_launcher_page_contents()); content::RenderFrameHost* custom_page_frame = custom_page_view->GetWebContents()->GetMainFrame(); const char* test_message = enabled ? "launcherPageEnabled" : "launcherPageDisabled"; ExtensionTestMessageListener listener(test_message, false); custom_page_frame->ExecuteJavaScript(enabled ? kLauncherPageEnableScript : kLauncherPageDisableScript); listener.WaitUntilSatisfied(); } private: DISALLOW_COPY_AND_ASSIGN(CustomLauncherPageBrowserTest); }; IN_PROC_BROWSER_TEST_F(CustomLauncherPageBrowserTest, OpenLauncherAndSwitchToCustomPage) { LoadAndLaunchPlatformApp(kCustomLauncherPagePath, "Launched"); app_list::AppListView* app_list_view = GetAppListView(); app_list::ContentsView* contents_view = app_list_view->app_list_main_view()->contents_view(); ASSERT_TRUE( contents_view->IsStateActive(app_list::AppListModel::STATE_START)); { ExtensionTestMessageListener listener("onPageProgressAt1", false); contents_view->SetActiveState( app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE); listener.WaitUntilSatisfied(); } { ExtensionTestMessageListener listener("onPageProgressAt0", false); contents_view->SetActiveState(app_list::AppListModel::STATE_START); listener.WaitUntilSatisfied(); } } // Test that the app list will switch to the custom launcher page by sending a // click inside the clickzone, or a mouse scroll event. IN_PROC_BROWSER_TEST_F(CustomLauncherPageBrowserTest, EventsActivateSwitchToCustomPage) { LoadAndLaunchPlatformApp(kCustomLauncherPagePath, "Launched"); // Use an event generator to ensure targeting is correct. app_list::AppListView* app_list_view = GetAppListView(); app_list::ContentsView* contents_view = app_list_view->app_list_main_view()->contents_view(); gfx::NativeWindow window = app_list_view->GetWidget()->GetNativeWindow(); ui::test::EventGenerator event_generator(window->GetRootWindow(), window); EXPECT_TRUE( contents_view->IsStateActive(app_list::AppListModel::STATE_START)); // Find the clickzone. gfx::Rect bounds = contents_view->custom_page_view()->GetCollapsedLauncherPageBounds(); bounds.Intersect(contents_view->bounds()); gfx::Point point_in_clickzone = bounds.CenterPoint(); views::View::ConvertPointToWidget(contents_view, &point_in_clickzone); // First try clicking 10px above the clickzone. gfx::Point point_above_clickzone = point_in_clickzone; point_above_clickzone.set_y(bounds.y() - 10); views::View::ConvertPointToWidget(contents_view, &point_above_clickzone); event_generator.MoveMouseRelativeTo(window, point_above_clickzone); event_generator.ClickLeftButton(); // Should stay on the start page. EXPECT_TRUE( contents_view->IsStateActive(app_list::AppListModel::STATE_START)); // Now click in the clickzone. event_generator.MoveMouseRelativeTo(window, point_in_clickzone); // First, try disabling the custom page view. Click should do nothing. SetCustomLauncherPageEnabled(false); event_generator.ClickLeftButton(); EXPECT_TRUE( contents_view->IsStateActive(app_list::AppListModel::STATE_START)); // Click again with it enabled. The active state should update immediately. SetCustomLauncherPageEnabled(true); event_generator.ClickLeftButton(); EXPECT_TRUE(contents_view->IsStateActive( app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE)); // Back to the start page. And send a mouse wheel event. SetActiveStateAndVerify(app_list::AppListModel::STATE_START); // Generate wheel events above the clickzone. event_generator.MoveMouseRelativeTo(window, point_above_clickzone); // Scrolling left, right or up should do nothing. event_generator.MoveMouseWheel(-5, 0); event_generator.MoveMouseWheel(5, 0); event_generator.MoveMouseWheel(0, 5); EXPECT_TRUE( contents_view->IsStateActive(app_list::AppListModel::STATE_START)); // Scroll down to open launcher page. event_generator.MoveMouseWheel(0, -5); EXPECT_TRUE(contents_view->IsStateActive( app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE)); // Constants for gesture/trackpad events. const base::TimeDelta step_delay = base::TimeDelta::FromMilliseconds(300); const int num_steps = 5; const int num_fingers = 2; #if defined(OS_CHROMEOS) // Gesture events need to be in host coordinates. On Desktop platforms, the // Widget is the host, so nothing needs to be done. On ChromeOS, the points // need to be put into screen coordinates. This works because the root window // assumes it fills the screen. point_in_clickzone = bounds.CenterPoint(); point_above_clickzone.SetPoint(point_in_clickzone.x(), bounds.y() - 10); views::View::ConvertPointToScreen(contents_view, &point_above_clickzone); views::View::ConvertPointToScreen(contents_view, &point_in_clickzone); #endif // Back to the start page. And send a scroll gesture. SetActiveStateAndVerify(app_list::AppListModel::STATE_START); // Going down should do nothing. event_generator.GestureScrollSequence( point_above_clickzone, point_in_clickzone, step_delay, num_steps); EXPECT_TRUE( contents_view->IsStateActive(app_list::AppListModel::STATE_START)); // Now go up - should change state. event_generator.GestureScrollSequence( point_in_clickzone, point_above_clickzone, step_delay, num_steps); EXPECT_TRUE(contents_view->IsStateActive( app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE)); // Back to the start page. And send a trackpad scroll event. SetActiveStateAndVerify(app_list::AppListModel::STATE_START); // Going down left, right or up should do nothing. event_generator.ScrollSequence(point_in_clickzone, step_delay, -5, 0, num_steps, num_fingers); event_generator.ScrollSequence(point_in_clickzone, step_delay, 5, 0, num_steps, num_fingers); event_generator.ScrollSequence(point_in_clickzone, step_delay, 0, 5, num_steps, num_fingers); EXPECT_TRUE( contents_view->IsStateActive(app_list::AppListModel::STATE_START)); // Scroll up to open launcher page. event_generator.ScrollSequence(point_in_clickzone, step_delay, 0, -5, num_steps, num_fingers); EXPECT_TRUE(contents_view->IsStateActive( app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE)); // Back to the start page. And send a tap gesture. SetActiveStateAndVerify(app_list::AppListModel::STATE_START); // Tapping outside the clickzone should do nothing. event_generator.GestureTapAt(point_above_clickzone); EXPECT_TRUE( contents_view->IsStateActive(app_list::AppListModel::STATE_START)); // Now tap in the clickzone. event_generator.GestureTapAt(point_in_clickzone); EXPECT_TRUE(contents_view->IsStateActive( app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE)); } IN_PROC_BROWSER_TEST_F(CustomLauncherPageBrowserTest, LauncherPageSubpages) { LoadAndLaunchPlatformApp(kCustomLauncherPagePath, "Launched"); app_list::AppListView* app_list_view = GetAppListView(); app_list::AppListModel* model = app_list_view->app_list_main_view()->model(); app_list::ContentsView* contents_view = app_list_view->app_list_main_view()->contents_view(); ASSERT_TRUE( contents_view->IsStateActive(app_list::AppListModel::STATE_START)); { ExtensionTestMessageListener listener("onPageProgressAt1", false); contents_view->SetActiveState( app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE); listener.WaitUntilSatisfied(); EXPECT_TRUE(contents_view->IsStateActive( app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE)); // The app pushes 2 subpages when the launcher page is shown. EXPECT_EQ(2, model->custom_launcher_page_subpage_depth()); } // Pop the subpages. { ExtensionTestMessageListener listener("onPopSubpage", false); EXPECT_TRUE(contents_view->Back()); listener.WaitUntilSatisfied(); EXPECT_TRUE(contents_view->IsStateActive( app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE)); EXPECT_EQ(1, model->custom_launcher_page_subpage_depth()); } { ExtensionTestMessageListener listener("onPopSubpage", false); EXPECT_TRUE(contents_view->Back()); listener.WaitUntilSatisfied(); EXPECT_TRUE(contents_view->IsStateActive( app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE)); EXPECT_EQ(0, model->custom_launcher_page_subpage_depth()); } // Once all subpages are popped, the start page should show. EXPECT_TRUE(contents_view->Back()); // Immediately finish the animation. contents_view->Layout(); EXPECT_TRUE( contents_view->IsStateActive(app_list::AppListModel::STATE_START)); } IN_PROC_BROWSER_TEST_F(CustomLauncherPageBrowserTest, LauncherPageShowAndHide) { const base::string16 kLauncherPageShowScript = base::ASCIIToUTF16("chrome.launcherPage.show();"); const base::string16 kLauncherPageHideScript = base::ASCIIToUTF16("hideCustomLauncherPage()"); LoadAndLaunchPlatformApp(kCustomLauncherPagePath, "Launched"); app_list::AppListView* app_list_view = GetAppListView(); app_list::ContentsView* contents_view = app_list_view->app_list_main_view()->contents_view(); views::WebView* custom_page_view = static_cast( contents_view->custom_page_view()->custom_launcher_page_contents()); content::RenderFrameHost* custom_page_frame = custom_page_view->GetWebContents()->GetMainFrame(); ASSERT_TRUE( contents_view->IsStateActive(app_list::AppListModel::STATE_START)); // Ensure launcherPage.show() will switch the page to the custom launcher page // if the app launcher is already showing. { ExtensionTestMessageListener listener("onPageProgressAt1", false); custom_page_frame->ExecuteJavaScript(kLauncherPageShowScript); listener.WaitUntilSatisfied(); EXPECT_TRUE(contents_view->IsStateActive( app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE)); } // Ensure launcherPage.show() will show the app list if it's hidden. { // Close the app list immediately. app_list_view->Close(); app_list_view->GetWidget()->Close(); ExtensionTestMessageListener listener("onPageProgressAt1", false); custom_page_frame->ExecuteJavaScript(kLauncherPageShowScript); listener.WaitUntilSatisfied(); // The app list view will have changed on ChromeOS. app_list_view = GetAppListView(); contents_view = app_list_view->app_list_main_view()->contents_view(); EXPECT_TRUE(contents_view->IsStateActive( app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE)); } // Ensure launcherPage.hide() hides the launcher page when it's showing. { ExtensionTestMessageListener listener("onPageProgressAt0", false); custom_page_frame->ExecuteJavaScript(kLauncherPageHideScript); listener.WaitUntilSatisfied(); EXPECT_TRUE( contents_view->IsStateActive(app_list::AppListModel::STATE_START)); } // Nothing should happen if hide() is called from the apps page. { contents_view->SetActiveState(app_list::AppListModel::STATE_APPS, false); ExtensionTestMessageListener listener("launcherPageHidden", false); custom_page_frame->ExecuteJavaScript(kLauncherPageHideScript); listener.WaitUntilSatisfied(); EXPECT_TRUE( contents_view->IsStateActive(app_list::AppListModel::STATE_APPS)); } } IN_PROC_BROWSER_TEST_F(CustomLauncherPageBrowserTest, LauncherPageSetEnabled) { LoadAndLaunchPlatformApp(kCustomLauncherPagePath, "Launched"); app_list::AppListView* app_list_view = GetAppListView(); app_list::AppListModel* model = app_list_view->app_list_main_view()->model(); app_list::ContentsView* contents_view = app_list_view->app_list_main_view()->contents_view(); views::View* custom_page_view = contents_view->custom_page_view(); ASSERT_TRUE( contents_view->IsStateActive(app_list::AppListModel::STATE_START)); EXPECT_TRUE(model->custom_launcher_page_enabled()); EXPECT_TRUE(custom_page_view->visible()); SetCustomLauncherPageEnabled(false); EXPECT_FALSE(model->custom_launcher_page_enabled()); EXPECT_FALSE(custom_page_view->visible()); SetCustomLauncherPageEnabled(true); EXPECT_TRUE(model->custom_launcher_page_enabled()); EXPECT_TRUE(custom_page_view->visible()); } // Currently this is flaky. // Disabled test http://crbug.com/463456 IN_PROC_BROWSER_TEST_F(CustomLauncherPageBrowserTest, LauncherPageFocusTraversal) { LoadAndLaunchPlatformApp(kCustomLauncherPagePath, "Launched"); app_list::AppListView* app_list_view = GetAppListView(); app_list::ContentsView* contents_view = app_list_view->app_list_main_view()->contents_view(); app_list::SearchBoxView* search_box_view = app_list_view->app_list_main_view()->search_box_view(); ASSERT_TRUE( contents_view->IsStateActive(app_list::AppListModel::STATE_START)); { ExtensionTestMessageListener listener("onPageProgressAt1", false); contents_view->SetActiveState( app_list::AppListModel::STATE_CUSTOM_LAUNCHER_PAGE); listener.WaitUntilSatisfied(); } // Expect that the search box and webview are the only two focusable views. views::View* search_box_textfield = search_box_view->search_box(); views::WebView* custom_page_webview = static_cast( contents_view->custom_page_view()->custom_launcher_page_contents()); EXPECT_EQ(custom_page_webview, app_list_view->GetFocusManager()->GetNextFocusableView( search_box_textfield, search_box_textfield->GetWidget(), false, false)); EXPECT_EQ( search_box_textfield, app_list_view->GetFocusManager()->GetNextFocusableView( custom_page_webview, custom_page_webview->GetWidget(), false, false)); // And in reverse. EXPECT_EQ( search_box_textfield, app_list_view->GetFocusManager()->GetNextFocusableView( custom_page_webview, custom_page_webview->GetWidget(), true, false)); EXPECT_EQ(custom_page_webview, app_list_view->GetFocusManager()->GetNextFocusableView( search_box_textfield, search_box_textfield->GetWidget(), true, false)); }