blob: 6ac62479689a7c559b65ac0010b11cbacd0ff5cf [file] [log] [blame]
// 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));
}