| // 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 <string> |
| |
| #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<AppListServiceViews*>( |
| 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<views::WebView*>( |
| 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, |
| LauncherPageShow) { |
| const base::string16 kLauncherPageShowScript = |
| base::ASCIIToUTF16("chrome.launcherPage.show();"); |
| |
| 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<views::WebView*>( |
| 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)); |
| } |
| } |
| |
| 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<views::WebView*>( |
| 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)); |
| } |