// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
#include <stddef.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/app_list/app_list_features.h"
#include "ash/public/cpp/app_menu_constants.h"
#include "ash/public/cpp/ash_view_ids.h"
#include "ash/public/cpp/shelf_item_delegate.h"
#include "ash/public/cpp/shelf_model.h"
#include "ash/public/cpp/shelf_test_api.h"
#include "ash/public/cpp/system_tray_test_api.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/root_window_controller.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_app_button.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shelf/shelf_menu_model_adapter.h"
#include "ash/shelf/shelf_view.h"
#include "ash/shelf/shelf_view_test_api.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/desks_controller.h"
#include "ash/wm/desks/desks_test_util.h"
#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/values.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/apps/app_service/app_launch_params.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/app_service_test.h"
#include "chrome/browser/apps/app_service/launch_utils.h"
#include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
#include "chrome/browser/ash/accessibility/accessibility_manager.h"
#include "chrome/browser/ash/accessibility/speech_monitor.h"
#include "chrome/browser/ash/app_list/app_list_controller_delegate.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/ash/file_manager/app_id.h"
#include "chrome/browser/ash/file_manager/file_manager_test_util.h"
#include "chrome/browser/ash/login/demo_mode/demo_mode_test_utils.h"
#include "chrome/browser/ash/login/demo_mode/demo_session.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/launch_util.h"
#include "chrome/browser/extensions/menu_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/shelf/app_shortcut_shelf_item_controller.h"
#include "chrome/browser/ui/ash/shelf/browser_shortcut_shelf_item_controller.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller_test_util.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_prefs.h"
#include "chrome/browser/ui/ash/shelf/shelf_context_menu.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/extensions/app_launch_params.h"
#include "chrome/browser/ui/settings_window_manager_chromeos.h"
#include "chrome/browser/ui/tabs/tab_enums.h"
#include "chrome/browser/ui/tabs/tab_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/test/test_app_window_icon_observer.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/browser/ui/web_applications/web_app_dialogs.h"
#include "chrome/browser/web_applications/external_install_options.h"
#include "chrome/browser/web_applications/externally_managed_app_manager.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
#include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
#include "chrome/browser/web_applications/policy/web_app_policy_constants.h"
#include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_test_observers.h"
#include "chrome/browser/web_applications/test/web_app_test_utils.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_id_constants.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chrome/browser/web_applications/web_app_install_manager.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chromeos/ash/components/standalone_browser/feature_refs.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/app_constants/constants.h"
#include "components/crx_file/id_util.h"
#include "components/prefs/pref_service.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/webapps/browser/test/service_worker_registration_waiter.h"
#include "components/webapps/common/web_app_id.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_mock_cert_verifier.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/browser/app_window/native_app_window.h"
#include "extensions/browser/browsertest_util.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry_factory.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/constants.h"
#include "extensions/common/switches.h"
#include "extensions/test/extension_test_message_listener.h"
#include "net/dns/mock_host_resolver.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/base/base_window.h"
#include "ui/base/window_open_disposition.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/display/display.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/screen.h"
#include "ui/display/test/display_manager_test_api.h"
#include "ui/display/types/display_constants.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/test/event_generator.h"
#include "ui/events/types/event_type.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/test/widget_animation_waiter.h"
#include "url/gurl.h"
namespace {
using ::ash::AccessibilityManager;
using ::content::WebContents;
using ::extensions::AppWindow;
using ::extensions::Extension;
using ::web_app::WebAppProvider;
ash::ShelfAction SelectItem(
const ash::ShelfID& id,
ui::EventType event_type = ui::EventType::kMousePressed,
int64_t display_id = display::kInvalidDisplayId,
ash::ShelfLaunchSource source = ash::LAUNCH_FROM_UNKNOWN) {
return SelectShelfItem(id, event_type, display_id, source);
}
// Find the browser that associated with |app_name|.
Browser* FindBrowserForApp(const std::string& app_name) {
for (Browser* browser : *BrowserList::GetInstance()) {
std::string browser_app_name =
web_app::GetAppIdFromApplicationName(browser->app_name());
if (browser_app_name == app_name)
return browser;
}
return nullptr;
}
// Close |app_browser| and wait until it's closed.
void CloseAppBrowserWindow(Browser* app_browser) {
app_browser->window()->Close();
ui_test_utils::WaitForBrowserToClose(app_browser);
}
// Close browsers from context menu
void CloseBrowserWindow(Browser* browser,
ShelfContextMenu* menu,
int close_command) {
// Note that event_flag is never used inside function ExecuteCommand.
menu->ExecuteCommand(close_command, ui::EF_NONE);
ui_test_utils::WaitForBrowserToClose(browser);
}
int64_t GetDisplayIdForBrowserWindow(BrowserWindow* window) {
return display::Screen::GetScreen()
->GetDisplayNearestWindow(window->GetNativeWindow())
.id();
}
void ExecuteScriptInChromeVox(Browser* browser, const std::string& script) {
std::string execute_script = R"JS((async function() {
)JS" + script + R"JS(
window.domAutomationController.send('done');
})())JS";
extensions::browsertest_util::ExecuteScriptInBackgroundPageDeprecated(
browser->profile(), extension_misc::kChromeVoxExtensionId,
execute_script);
}
void ExtendHotseat(Browser* browser) {
ash::RootWindowController* const controller =
ash::Shell::GetRootWindowControllerWithDisplayId(
display::Screen::GetScreen()->GetPrimaryDisplay().id());
EXPECT_EQ(ash::HotseatState::kHidden,
controller->shelf()->shelf_layout_manager()->hotseat_state());
BrowserView* const browser_view =
BrowserView::GetBrowserViewForBrowser(browser);
aura::Window* const browser_window =
browser_view->GetWidget()->GetNativeWindow();
const gfx::Rect display_bounds = display::Screen::GetScreen()
->GetDisplayNearestWindow(browser_window)
.bounds();
const gfx::Point start_point = gfx::Point(
display_bounds.width() / 4,
display_bounds.bottom() - ash::ShelfConfig::Get()->shelf_size() / 2);
// Swipe up for a small distance to bring up the hotseat.
gfx::Point end_point(start_point.x(), start_point.y() - 80);
ash::ShelfView* shelf_view = controller->shelf()->GetShelfViewForTesting();
// Observe hotseat animation before animation starts. Because
// views::WidgetAnimationWaiter only reacts to completion of the animation
// whose animation scheduling is recorded in views::WidgetAnimationWaiter.
views::WidgetAnimationWaiter waiter(shelf_view->GetWidget());
ui::test::EventGenerator event_generator(controller->GetRootWindow());
event_generator.GestureScrollSequence(start_point, end_point,
base::Milliseconds(500), 4);
// Wait until hotseat bounds animation completes.
waiter.WaitForAnimation();
EXPECT_EQ(ash::HotseatState::kExtended,
controller->shelf()->shelf_layout_manager()->hotseat_state());
}
ash::ShelfID CreateAppShortcutItem(const ash::ShelfID& shelf_id) {
auto* controller = ChromeShelfController::instance();
return controller->CreateAppItem(
std::make_unique<AppShortcutShelfItemController>(shelf_id),
ash::STATUS_CLOSED, /*pinned=*/true, /*title=*/std::u16string());
}
// A class that waits for the child removal to occur on a parent view.
class ChildRemovalWaiter : public views::ViewObserver {
public:
explicit ChildRemovalWaiter(views::View* parent_view)
: parent_view_(parent_view) {
parent_view_->AddObserver(this);
}
ChildRemovalWaiter(const ChildRemovalWaiter&) = delete;
ChildRemovalWaiter& operator=(const ChildRemovalWaiter&) = delete;
~ChildRemovalWaiter() override { parent_view_->RemoveObserver(this); }
void WaitForChildRemoval() { run_loop_.Run(); }
// views::ViewObserver:
void OnChildViewRemoved(views::View* observed_view,
views::View* child) override {
run_loop_.Quit();
}
private:
const raw_ptr<views::View> parent_view_;
base::RunLoop run_loop_;
};
} // namespace
class ShelfPlatformAppBrowserTest : public extensions::PlatformAppBrowserTest {
protected:
ShelfPlatformAppBrowserTest() = default;
ShelfPlatformAppBrowserTest(const ShelfPlatformAppBrowserTest&) = delete;
ShelfPlatformAppBrowserTest& operator=(const ShelfPlatformAppBrowserTest&) =
delete;
~ShelfPlatformAppBrowserTest() override = default;
void SetUpOnMainThread() override {
controller_ = ChromeShelfController::instance();
ASSERT_TRUE(controller_);
extensions::PlatformAppBrowserTest::SetUpOnMainThread();
app_service_test_.SetUp(browser()->profile());
}
ash::ShelfModel* shelf_model() { return controller_->shelf_model(); }
// Returns the last item in the shelf.
const ash::ShelfItem& GetLastShelfItem() {
return shelf_model()->items()[shelf_model()->item_count() - 1];
}
ash::ShelfItemDelegate* GetShelfItemDelegate(const ash::ShelfID& id) {
return shelf_model()->GetShelfItemDelegate(id);
}
apps::AppServiceTest& app_service_test() { return app_service_test_; }
raw_ptr<ChromeShelfController, DanglingUntriaged> controller_ = nullptr;
private:
apps::AppServiceTest app_service_test_;
};
class ShelfAppBrowserTest : public extensions::ExtensionBrowserTest {
protected:
ShelfAppBrowserTest() {
// TODO(crbug.com/40201067): Update expectations to support Lacros.
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{},
/*disabled_features=*/ash::standalone_browser::GetFeatureRefs());
}
ShelfAppBrowserTest(const ShelfAppBrowserTest&) = delete;
ShelfAppBrowserTest& operator=(const ShelfAppBrowserTest&) = delete;
~ShelfAppBrowserTest() override {}
ash::ShelfModel* shelf_model() { return controller_->shelf_model(); }
void SetUpOnMainThread() override {
controller_ = ChromeShelfController::instance();
ASSERT_TRUE(controller_);
extensions::ExtensionBrowserTest::SetUpOnMainThread();
}
size_t BrowserShortcutMenuItemCount(bool show_all_tabs) {
ash::ShelfItemDelegate* item_controller =
controller_->GetBrowserShortcutShelfItemControllerForTesting();
return item_controller
->GetAppMenuItems(show_all_tabs ? ui::EF_SHIFT_DOWN : 0,
base::NullCallback())
.size();
}
const Extension* LoadAndLaunchExtension(const char* name,
int32_t event_flags) {
EXPECT_TRUE(LoadExtension(test_data_dir_.AppendASCII(name)));
const Extension* extension =
extension_registry()->enabled_extensions().GetByID(
last_loaded_extension_id());
EXPECT_TRUE(extension);
auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile());
proxy->Launch(extension->id(), event_flags, apps::LaunchSource::kFromTest,
std::make_unique<apps::WindowInfo>(
display::Screen::GetScreen()->GetPrimaryDisplay().id()));
return extension;
}
ash::ShelfID CreateShortcut(const char* name) {
LoadExtension(test_data_dir_.AppendASCII(name));
// First get app_id.
const Extension* extension =
extension_registry()->enabled_extensions().GetByID(
last_loaded_extension_id());
const std::string app_id = extension->id();
// Then create a shortcut.
int item_count = shelf_model()->item_count();
ash::ShelfID shortcut_id = CreateAppShortcutItem(ash::ShelfID(app_id));
controller_->SyncPinPosition(shortcut_id);
EXPECT_EQ(++item_count, shelf_model()->item_count());
const ash::ShelfItem& item = *shelf_model()->ItemByID(shortcut_id);
EXPECT_EQ(ash::TYPE_PINNED_APP, item.type);
return item.id;
}
// Get the index of an item which has the given type.
int GetIndexOfShelfItemType(ash::ShelfItemType type) const {
return controller_->shelf_model()->GetItemIndexForType(type);
}
// Creates a context menu for the existing browser shortcut item.
std::unique_ptr<ShelfContextMenu> CreateBrowserItemContextMenu() {
int index = shelf_model()->GetItemIndexForType(ash::TYPE_BROWSER_SHORTCUT);
DCHECK_GE(index, 0);
ash::ShelfItem item = shelf_model()->items()[index];
int64_t display_id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
return ShelfContextMenu::Create(controller_, &item, display_id);
}
bool IsItemPresentInMenu(ShelfContextMenu* shelf_context_menu,
int command_id) {
base::RunLoop run_loop;
std::unique_ptr<ui::SimpleMenuModel> menu;
shelf_context_menu->GetMenuModel(base::BindLambdaForTesting(
[&](std::unique_ptr<ui::SimpleMenuModel> created_menu) {
menu = std::move(created_menu);
run_loop.Quit();
}));
run_loop.Run();
ui::MenuModel* menu_ptr = menu.get();
size_t index = 0;
return ui::MenuModel::GetModelAndIndexForCommandId(command_id, &menu_ptr,
&index);
}
// Launch the app.
void LaunchApp(const ash::ShelfID& id,
ash::ShelfLaunchSource source,
int event_flags,
int64_t display_id) {
controller_->LaunchApp(ash::ShelfID(last_loaded_extension_id()),
ash::LAUNCH_FROM_UNKNOWN, 0,
display::kInvalidDisplayId);
}
// Select the app.
void SelectApp(const std::string& app_id, ash::ShelfLaunchSource source) {
ash::ShelfID shelf_id(app_id);
ash::ShelfModel* model = controller_->shelf_model();
ash::ShelfItemDelegate* delegate = model->GetShelfItemDelegate(shelf_id);
ASSERT_TRUE(delegate);
delegate->ItemSelected(/*event=*/nullptr, display::kInvalidDisplayId,
ash::LAUNCH_FROM_UNKNOWN,
/*callback=*/base::DoNothing(),
/*filter_predicate=*/base::NullCallback());
}
raw_ptr<ChromeShelfController, DanglingUntriaged> controller_ = nullptr;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
class ShelfAppBrowserTestNoDefaultBrowser : public ShelfAppBrowserTest {
protected:
ShelfAppBrowserTestNoDefaultBrowser() {}
ShelfAppBrowserTestNoDefaultBrowser(
const ShelfAppBrowserTestNoDefaultBrowser&) = delete;
ShelfAppBrowserTestNoDefaultBrowser& operator=(
const ShelfAppBrowserTestNoDefaultBrowser&) = delete;
~ShelfAppBrowserTestNoDefaultBrowser() override {}
void SetUpCommandLine(base::CommandLine* command_line) override {
ShelfAppBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kNoStartupWindow);
}
};
class ShelfWebAppBrowserTest : public ShelfAppBrowserTest {
protected:
ShelfWebAppBrowserTest()
: https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
~ShelfWebAppBrowserTest() override = default;
net::EmbeddedTestServer* https_server() { return &https_server_; }
GURL GetSecureAppURL() {
return https_server()->GetURL("app.com", "/ssl/google.html");
}
webapps::AppId InstallWebApp(const GURL& start_url) {
auto web_app_info =
web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(start_url);
web_app_info->scope = start_url.GetWithoutFilename();
return web_app::test::InstallWebApp(browser()->profile(),
std::move(web_app_info));
}
// ShelfAppBrowserTest:
void SetUp() override {
https_server_.AddDefaultHandlers(GetChromeTestDataDir());
ShelfAppBrowserTest::SetUp();
}
void SetUpInProcessBrowserTestFixture() override {
ShelfAppBrowserTest::SetUpInProcessBrowserTestFixture();
cert_verifier_.SetUpInProcessBrowserTestFixture();
}
void TearDownInProcessBrowserTestFixture() override {
ShelfAppBrowserTest::TearDownInProcessBrowserTestFixture();
cert_verifier_.TearDownInProcessBrowserTestFixture();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
ShelfAppBrowserTest::SetUpCommandLine(command_line);
cert_verifier_.SetUpCommandLine(command_line);
}
void SetUpOnMainThread() override {
ShelfAppBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(https_server()->Start());
cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
web_app::test::WaitUntilReady(
web_app::WebAppProvider::GetForTest(browser()->profile()));
}
private:
net::EmbeddedTestServer https_server_;
content::ContentMockCertVerifier cert_verifier_;
web_app::OsIntegrationManager::ScopedSuppressForTesting os_hooks_suppress_;
};
// Test that we can launch a platform app and get a running item.
IN_PROC_BROWSER_TEST_F(ShelfPlatformAppBrowserTest, LaunchUnpinned) {
int item_count = shelf_model()->item_count();
const Extension* extension = LoadAndLaunchPlatformApp("launch", "Launched");
AppWindow* window = CreateAppWindow(browser()->profile(), extension);
++item_count;
ASSERT_EQ(item_count, shelf_model()->item_count());
const ash::ShelfItem& item = GetLastShelfItem();
EXPECT_EQ(ash::TYPE_APP, item.type);
EXPECT_EQ(ash::STATUS_RUNNING, item.status);
CloseAppWindow(window);
--item_count;
EXPECT_EQ(item_count, shelf_model()->item_count());
}
// Test that we can launch a platform app that already has a shortcut.
IN_PROC_BROWSER_TEST_F(ShelfPlatformAppBrowserTest, LaunchPinned) {
int item_count = shelf_model()->item_count();
// First get app_id.
const Extension* extension = LoadAndLaunchPlatformApp("launch", "Launched");
const std::string app_id = extension->id();
// Then create a shortcut.
ash::ShelfID shortcut_id = CreateAppShortcutItem(ash::ShelfID(app_id));
++item_count;
ASSERT_EQ(item_count, shelf_model()->item_count());
ash::ShelfItem item = *shelf_model()->ItemByID(shortcut_id);
EXPECT_EQ(ash::TYPE_PINNED_APP, item.type);
EXPECT_EQ(ash::STATUS_CLOSED, item.status);
// Open a window. Confirm the item is now running.
AppWindow* window = CreateAppWindow(browser()->profile(), extension);
window->GetBaseWindow()->Activate();
ASSERT_EQ(item_count, shelf_model()->item_count());
item = *shelf_model()->ItemByID(shortcut_id);
EXPECT_EQ(ash::TYPE_PINNED_APP, item.type);
EXPECT_EQ(ash::STATUS_RUNNING, item.status);
// Then close it, make sure there's still an item.
CloseAppWindow(window);
ASSERT_EQ(item_count, shelf_model()->item_count());
item = *shelf_model()->ItemByID(shortcut_id);
EXPECT_EQ(ash::TYPE_PINNED_APP, item.type);
EXPECT_EQ(ash::STATUS_CLOSED, item.status);
}
IN_PROC_BROWSER_TEST_F(ShelfPlatformAppBrowserTest, PinRunning) {
// Run.
int item_count = shelf_model()->item_count();
const Extension* extension = LoadAndLaunchPlatformApp("launch", "Launched");
AppWindow* window = CreateAppWindow(browser()->profile(), extension);
++item_count;
ASSERT_EQ(item_count, shelf_model()->item_count());
const ash::ShelfItem& item1 = GetLastShelfItem();
ash::ShelfID id = item1.id;
EXPECT_EQ(ash::TYPE_APP, item1.type);
EXPECT_EQ(ash::STATUS_RUNNING, item1.status);
// Create a shortcut. The app item should be after it.
ash::ShelfID foo_id =
CreateAppShortcutItem(ash::ShelfID(extension_misc::kYoutubeAppId));
++item_count;
ASSERT_EQ(item_count, shelf_model()->item_count());
EXPECT_LT(shelf_model()->ItemIndexByID(foo_id),
shelf_model()->ItemIndexByID(id));
// Pin the app. The item should remain.
controller_->shelf_model()->PinExistingItemWithID(extension->id());
ASSERT_EQ(item_count, shelf_model()->item_count());
const ash::ShelfItem& item2 = *shelf_model()->ItemByID(id);
EXPECT_EQ(ash::TYPE_PINNED_APP, item2.type);
EXPECT_EQ(ash::STATUS_RUNNING, item2.status);
// New shortcuts should come after the item.
ash::ShelfID bar_id =
CreateAppShortcutItem(ash::ShelfID(extension_misc::kGoogleDocsAppId));
++item_count;
ASSERT_EQ(item_count, shelf_model()->item_count());
EXPECT_LT(shelf_model()->ItemIndexByID(id),
shelf_model()->ItemIndexByID(bar_id));
// Then close it, make sure the item remains.
CloseAppWindow(window);
ASSERT_EQ(item_count, shelf_model()->item_count());
}
IN_PROC_BROWSER_TEST_F(ShelfPlatformAppBrowserTest, UnpinRunning) {
int item_count = shelf_model()->item_count();
// First get app_id.
const Extension* extension = LoadAndLaunchPlatformApp("launch", "Launched");
const std::string app_id = extension->id();
// Then create a shortcut.
ash::ShelfID shortcut_id = CreateAppShortcutItem(ash::ShelfID(app_id));
++item_count;
ASSERT_EQ(item_count, shelf_model()->item_count());
ash::ShelfItem item = *shelf_model()->ItemByID(shortcut_id);
EXPECT_EQ(ash::TYPE_PINNED_APP, item.type);
EXPECT_EQ(ash::STATUS_CLOSED, item.status);
// Create a second shortcut. This will be needed to force the first one to
// move once it gets unpinned.
ash::ShelfID foo_id =
CreateAppShortcutItem(ash::ShelfID(extension_misc::kYoutubeAppId));
++item_count;
ASSERT_EQ(item_count, shelf_model()->item_count());
EXPECT_LT(shelf_model()->ItemIndexByID(shortcut_id),
shelf_model()->ItemIndexByID(foo_id));
// Open a window. Confirm the item is now running.
AppWindow* window = CreateAppWindow(browser()->profile(), extension);
window->GetBaseWindow()->Activate();
ASSERT_EQ(item_count, shelf_model()->item_count());
item = *shelf_model()->ItemByID(shortcut_id);
EXPECT_EQ(ash::TYPE_PINNED_APP, item.type);
EXPECT_EQ(ash::STATUS_RUNNING, item.status);
// Unpin the app. The item should remain.
controller_->UnpinAppWithID(app_id);
ASSERT_EQ(item_count, shelf_model()->item_count());
item = *shelf_model()->ItemByID(shortcut_id);
EXPECT_EQ(ash::TYPE_APP, item.type);
EXPECT_EQ(ash::STATUS_RUNNING, item.status);
// The item should have moved after the other shortcuts.
EXPECT_GT(shelf_model()->ItemIndexByID(shortcut_id),
shelf_model()->ItemIndexByID(foo_id));
// Then close it, make sure the item's gone.
CloseAppWindow(window);
--item_count;
ASSERT_EQ(item_count, shelf_model()->item_count());
}
class UnpinnedBrowserShortcutTest : public extensions::ExtensionBrowserTest {
protected:
UnpinnedBrowserShortcutTest() {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/ash::standalone_browser::GetFeatureRefs(),
/*disabled_features=*/{});
scoped_command_line_.GetProcessCommandLine()->AppendSwitch(
ash::switches::kEnableLacrosForTesting);
}
UnpinnedBrowserShortcutTest(const UnpinnedBrowserShortcutTest&) = delete;
UnpinnedBrowserShortcutTest& operator=(const UnpinnedBrowserShortcutTest&) =
delete;
~UnpinnedBrowserShortcutTest() override = default;
ash::ShelfModel* shelf_model() { return controller_->shelf_model(); }
void SetUpCommandLine(base::CommandLine* command_line) override {
extensions::ExtensionBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kNoStartupWindow);
}
void SetUpOnMainThread() override {
controller_ = ChromeShelfController::instance();
ASSERT_TRUE(controller_);
extensions::ExtensionBrowserTest::SetUpOnMainThread();
}
raw_ptr<ChromeShelfController, DanglingUntriaged> controller_ = nullptr;
private:
base::test::ScopedFeatureList scoped_feature_list_;
base::test::ScopedCommandLine scoped_command_line_;
};
IN_PROC_BROWSER_TEST_F(UnpinnedBrowserShortcutTest, UnpinnedBrowserShortcut) {
DCHECK(web_app::IsWebAppsCrosapiEnabled());
EXPECT_EQ(-1, shelf_model()->GetItemIndexForType(ash::TYPE_BROWSER_SHORTCUT));
EXPECT_EQ(-1, shelf_model()->GetItemIndexForType(
ash::TYPE_UNPINNED_BROWSER_SHORTCUT));
EXPECT_EQ(-1, shelf_model()->GetItemIndexForType(ash::TYPE_APP));
CreateBrowser(profile());
EXPECT_EQ(-1, shelf_model()->GetItemIndexForType(ash::TYPE_BROWSER_SHORTCUT));
EXPECT_EQ(-1, shelf_model()->GetItemIndexForType(
ash::TYPE_UNPINNED_BROWSER_SHORTCUT));
EXPECT_NE(-1, shelf_model()->GetItemIndexForType(ash::TYPE_APP));
}
// Test that we can launch a platform app with more than one window.
IN_PROC_BROWSER_TEST_F(ShelfPlatformAppBrowserTest, MultipleWindows) {
int item_count = shelf_model()->item_count();
// Run the application; a shelf item should be added with one app menu item.
const Extension* extension = LoadAndLaunchPlatformApp("launch", "Launched");
AppWindow* window1 = CreateAppWindow(browser()->profile(), extension);
ASSERT_EQ(item_count + 1, shelf_model()->item_count());
const ash::ShelfItem& item1 = GetLastShelfItem();
ash::ShelfID item_id = item1.id;
EXPECT_EQ(ash::TYPE_APP, item1.type);
EXPECT_EQ(ash::STATUS_RUNNING, item1.status);
EXPECT_EQ(1u, controller_->GetAppMenuItemsForTesting(item1).size());
// Add a second window; confirm the shelf item stays; check the app menu.
AppWindow* window2 = CreateAppWindow(browser()->profile(), extension);
ASSERT_EQ(item_count + 1, shelf_model()->item_count());
const ash::ShelfItem& item2 = *shelf_model()->ItemByID(item_id);
EXPECT_EQ(ash::STATUS_RUNNING, item2.status);
EXPECT_EQ(2u, controller_->GetAppMenuItemsForTesting(item2).size());
// Close the second window; confirm the shelf item stays; check the app menu.
CloseAppWindow(window2);
ASSERT_EQ(item_count + 1, shelf_model()->item_count());
const ash::ShelfItem& item3 = *shelf_model()->ItemByID(item_id);
EXPECT_EQ(ash::STATUS_RUNNING, item3.status);
EXPECT_EQ(1u, controller_->GetAppMenuItemsForTesting(item3).size());
// Close the first window; the shelf item should be removed.
CloseAppWindow(window1);
ASSERT_EQ(item_count, shelf_model()->item_count());
}
IN_PROC_BROWSER_TEST_F(ShelfPlatformAppBrowserTest, MultipleApps) {
int item_count = shelf_model()->item_count();
// First run app.
const Extension* extension1 = LoadAndLaunchPlatformApp("launch", "Launched");
AppWindow* window1 = CreateAppWindow(browser()->profile(), extension1);
++item_count;
ASSERT_EQ(item_count, shelf_model()->item_count());
const ash::ShelfItem& item1 = GetLastShelfItem();
ash::ShelfID item_id1 = item1.id;
EXPECT_EQ(ash::TYPE_APP, item1.type);
EXPECT_EQ(ash::STATUS_RUNNING, item1.status);
// Then run second app.
const Extension* extension2 =
LoadAndLaunchPlatformApp("launch_2", "Launched");
AppWindow* window2 = CreateAppWindow(browser()->profile(), extension2);
++item_count;
ASSERT_EQ(item_count, shelf_model()->item_count());
const ash::ShelfItem& item2 = GetLastShelfItem();
ash::ShelfID item_id2 = item2.id;
EXPECT_EQ(ash::TYPE_APP, item2.type);
EXPECT_EQ(ash::STATUS_RUNNING, item2.status);
EXPECT_NE(item_id1, item_id2);
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->ItemByID(item_id1)->status);
// Close second app.
CloseAppWindow(window2);
--item_count;
ASSERT_EQ(item_count, shelf_model()->item_count());
// First app should still be running.
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->ItemByID(item_id1)->status);
// Close first app.
CloseAppWindow(window1);
--item_count;
ASSERT_EQ(item_count, shelf_model()->item_count());
}
// Confirm that app windows can be reactivated by clicking their icons and that
// the correct activation order is maintained.
// TODO(crbug.com/331536126): This test is flaky.
IN_PROC_BROWSER_TEST_F(ShelfPlatformAppBrowserTest, DISABLED_WindowActivation) {
int item_count = shelf_model()->item_count();
// First run app.
const Extension* extension1 = LoadAndLaunchPlatformApp("launch", "Launched");
AppWindow* window1 = CreateAppWindow(browser()->profile(), extension1);
++item_count;
ASSERT_EQ(item_count, shelf_model()->item_count());
const ash::ShelfItem& item1 = GetLastShelfItem();
ash::ShelfID item_id1 = item1.id;
EXPECT_EQ(ash::TYPE_APP, item1.type);
EXPECT_EQ(ash::STATUS_RUNNING, item1.status);
// Then run second app.
const Extension* extension2 =
LoadAndLaunchPlatformApp("launch_2", "Launched");
AppWindow* window2 = CreateAppWindow(browser()->profile(), extension2);
++item_count;
ASSERT_EQ(item_count, shelf_model()->item_count());
const ash::ShelfItem& item2 = GetLastShelfItem();
ash::ShelfID item_id2 = item2.id;
EXPECT_EQ(ash::TYPE_APP, item2.type);
EXPECT_EQ(ash::STATUS_RUNNING, item2.status);
EXPECT_NE(item_id1, item_id2);
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->ItemByID(item_id1)->status);
// Activate first one.
SelectItem(item_id1);
EXPECT_TRUE(window1->GetBaseWindow()->IsActive());
EXPECT_FALSE(window2->GetBaseWindow()->IsActive());
// Activate second one.
SelectItem(item_id2);
EXPECT_FALSE(window1->GetBaseWindow()->IsActive());
EXPECT_TRUE(window2->GetBaseWindow()->IsActive());
// Add window for app1. This will activate it.
AppWindow* window1b = CreateAppWindow(browser()->profile(), extension1);
window1b->GetBaseWindow()->Activate();
EXPECT_FALSE(window1->GetBaseWindow()->IsActive());
EXPECT_FALSE(window2->GetBaseWindow()->IsActive());
EXPECT_TRUE(window1b->GetBaseWindow()->IsActive());
// Key events selecting app1's shelf item will cycle through its windows.
SelectItem(item_id1, ui::EventType::kKeyReleased);
EXPECT_TRUE(window1->GetBaseWindow()->IsActive());
EXPECT_FALSE(window1b->GetBaseWindow()->IsActive());
SelectItem(item_id1, ui::EventType::kKeyReleased);
EXPECT_FALSE(window1->GetBaseWindow()->IsActive());
EXPECT_TRUE(window1b->GetBaseWindow()->IsActive());
// Activate the second app again
SelectItem(item_id2);
EXPECT_FALSE(window1->GetBaseWindow()->IsActive());
EXPECT_TRUE(window2->GetBaseWindow()->IsActive());
EXPECT_FALSE(window1b->GetBaseWindow()->IsActive());
// Activate the first app again
SelectItem(item_id1);
EXPECT_TRUE(window1b->GetBaseWindow()->IsActive());
EXPECT_FALSE(window2->GetBaseWindow()->IsActive());
EXPECT_FALSE(window1->GetBaseWindow()->IsActive());
// Close second app.
CloseAppWindow(window2);
--item_count;
EXPECT_EQ(item_count, shelf_model()->item_count());
// First app is still running.
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->ItemByID(item_id1)->status);
// Close first app.
CloseAppWindow(window1b);
CloseAppWindow(window1);
--item_count;
EXPECT_EQ(item_count, shelf_model()->item_count());
}
IN_PROC_BROWSER_TEST_F(ShelfPlatformAppBrowserTest, MultipleBrowsers) {
EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
Browser* const browser1 = chrome::FindLastActive();
ASSERT_TRUE(browser1);
Browser* const browser2 = CreateBrowser(profile());
ASSERT_TRUE(browser2);
EXPECT_EQ(2u, chrome::GetTotalBrowserCount());
EXPECT_NE(browser1->window(), browser2->window());
EXPECT_TRUE(browser2->window()->IsActive());
const Extension* app = LoadAndLaunchPlatformApp("launch", "Launched");
ui::BaseWindow* const app_window =
CreateAppWindow(browser()->profile(), app)->GetBaseWindow();
const ash::ShelfItem item = GetLastShelfItem();
EXPECT_EQ(app->id(), item.id.app_id);
EXPECT_EQ(ash::TYPE_APP, item.type);
EXPECT_EQ(ash::STATUS_RUNNING, item.status);
EXPECT_TRUE(app_window->IsActive());
EXPECT_FALSE(browser2->window()->IsActive());
SelectItem(ash::ShelfID(app_constants::kChromeAppId));
EXPECT_FALSE(app_window->IsActive());
EXPECT_TRUE(browser2->window()->IsActive());
}
// Confirm the minimizing click behavior for apps.
IN_PROC_BROWSER_TEST_F(ShelfPlatformAppBrowserTest,
PackagedAppClickBehaviorInMinimizeMode) {
// Launch one platform app and create a window for it.
const Extension* extension1 = LoadAndLaunchPlatformApp("launch", "Launched");
AppWindow* window1 = CreateAppWindow(browser()->profile(), extension1);
EXPECT_TRUE(window1->GetNativeWindow()->IsVisible());
EXPECT_TRUE(window1->GetBaseWindow()->IsActive());
// Confirm that a controller item was created and is the correct state.
const ash::ShelfItem& item = GetLastShelfItem();
EXPECT_EQ(ash::TYPE_APP, item.type);
EXPECT_EQ(ash::STATUS_RUNNING, item.status);
// Since it is already active, clicking it should minimize.
SelectItem(item.id);
EXPECT_FALSE(window1->GetNativeWindow()->IsVisible());
EXPECT_FALSE(window1->GetBaseWindow()->IsActive());
EXPECT_TRUE(window1->GetBaseWindow()->IsMinimized());
// Clicking the item again should activate the window again.
SelectItem(item.id);
EXPECT_TRUE(window1->GetNativeWindow()->IsVisible());
EXPECT_TRUE(window1->GetBaseWindow()->IsActive());
// Maximizing a window should preserve state after minimize + click.
window1->GetBaseWindow()->Maximize();
window1->GetBaseWindow()->Minimize();
SelectItem(item.id);
EXPECT_TRUE(window1->GetNativeWindow()->IsVisible());
EXPECT_TRUE(window1->GetBaseWindow()->IsActive());
EXPECT_TRUE(window1->GetBaseWindow()->IsMaximized());
window1->GetBaseWindow()->Restore();
EXPECT_TRUE(window1->GetNativeWindow()->IsVisible());
EXPECT_TRUE(window1->GetBaseWindow()->IsActive());
EXPECT_FALSE(window1->GetBaseWindow()->IsMaximized());
// Creating a second window of the same type should change the behavior so
// that a click on the shelf item does not change the activation state.
AppWindow* window1a = CreateAppWindow(browser()->profile(), extension1);
EXPECT_TRUE(window1->GetNativeWindow()->IsVisible());
EXPECT_TRUE(window1a->GetNativeWindow()->IsVisible());
EXPECT_FALSE(window1->GetBaseWindow()->IsActive());
EXPECT_TRUE(window1a->GetBaseWindow()->IsActive());
// Ensure the same shelf item and delegate are used for |window1a|.
EXPECT_EQ(item.id, GetLastShelfItem().id);
EXPECT_EQ(GetShelfItemDelegate(item.id),
GetShelfItemDelegate(GetLastShelfItem().id));
// The first click does nothing.
SelectItem(item.id);
EXPECT_TRUE(window1->GetNativeWindow()->IsVisible());
EXPECT_TRUE(window1a->GetNativeWindow()->IsVisible());
EXPECT_FALSE(window1->GetBaseWindow()->IsActive());
EXPECT_TRUE(window1a->GetBaseWindow()->IsActive());
// The second neither.
SelectItem(item.id);
EXPECT_TRUE(window1->GetNativeWindow()->IsVisible());
EXPECT_TRUE(window1a->GetNativeWindow()->IsVisible());
EXPECT_FALSE(window1->GetBaseWindow()->IsActive());
EXPECT_TRUE(window1a->GetBaseWindow()->IsActive());
}
IN_PROC_BROWSER_TEST_F(ShelfPlatformAppBrowserTest, BrowserActivation) {
int item_count = shelf_model()->item_count();
// First run app.
const Extension* extension1 = LoadAndLaunchPlatformApp("launch", "Launched");
CreateAppWindow(browser()->profile(), extension1);
++item_count;
ASSERT_EQ(item_count, shelf_model()->item_count());
const ash::ShelfItem& item = GetLastShelfItem();
ash::ShelfID item_id1 = item.id;
EXPECT_EQ(ash::TYPE_APP, item.type);
EXPECT_EQ(ash::STATUS_RUNNING, item.status);
browser()->window()->Activate();
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->ItemByID(item_id1)->status);
}
// Test that opening an app sets the correct icon
IN_PROC_BROWSER_TEST_F(ShelfPlatformAppBrowserTest, SetIcon) {
TestAppWindowIconObserver test_observer(browser()->profile());
int base_shelf_item_count = shelf_model()->item_count();
ExtensionTestMessageListener ready_listener("ready",
ReplyBehavior::kWillReply);
const Extension* extension = LoadAndLaunchPlatformApp("app_icon", "Launched");
ASSERT_TRUE(extension);
gfx::ImageSkia image_skia;
int32_t size_hint_in_dip = 48;
image_skia =
app_service_test().LoadAppIconBlocking(extension->id(), size_hint_in_dip);
// Create non-shelf window.
EXPECT_TRUE(ready_listener.WaitUntilSatisfied());
ready_listener.Reply("createNonShelfWindow");
ready_listener.Reset();
// Default app icon + extension icon updates + AppServiceProxy load icon
// updates.
test_observer.WaitForIconUpdates(3);
EXPECT_TRUE(app_service_test().AreIconImageEqual(
image_skia, test_observer.last_app_icon()));
// Create shelf window.
EXPECT_TRUE(ready_listener.WaitUntilSatisfied());
ready_listener.Reply("createShelfWindow");
ready_listener.Reset();
// Default app icon + extension icon updates + AppServiceProxy load icon
// updates.
test_observer.WaitForIconUpdates(3);
EXPECT_TRUE(app_service_test().AreIconImageEqual(
image_skia, test_observer.last_app_icon()));
// Set shelf window icon.
EXPECT_TRUE(ready_listener.WaitUntilSatisfied());
ready_listener.Reply("setShelfWindowIcon");
ready_listener.Reset();
// Custom icon update.
test_observer.WaitForIconUpdate();
EXPECT_FALSE(app_service_test().AreIconImageEqual(
image_skia, test_observer.last_app_icon()));
gfx::ImageSkia custome_icon = test_observer.last_app_icon();
// Create shelf window with custom icon on init.
EXPECT_TRUE(ready_listener.WaitUntilSatisfied());
ready_listener.Reply("createShelfWindowWithCustomIcon");
ready_listener.Reset();
int update_number;
// Default app icon + extension icon + AppServiceProxy load icon + custom
// icon updates. Ensure the custom icon is set as the window's icon.
test_observer.WaitForIconUpdates(custome_icon);
EXPECT_TRUE(app_service_test().AreIconImageEqual(
custome_icon, test_observer.last_app_icon()));
update_number = test_observer.icon_updates();
const gfx::ImageSkia app_item_custom_image = test_observer.last_app_icon();
const int shelf_item_count = shelf_model()->item_count();
ASSERT_EQ(base_shelf_item_count + 3, shelf_item_count);
const ash::ShelfItem& app_item =
shelf_model()->items()[base_shelf_item_count];
const ash::ShelfItem& app_custom_icon_item =
shelf_model()->items()[base_shelf_item_count + 1];
// Icons for Apps are set by the AppWindowShelfController, so
// image_set_by_controller() should be set.
const ash::ShelfItemDelegate* app_item_delegate =
GetShelfItemDelegate(app_item.id);
ASSERT_TRUE(app_item_delegate);
EXPECT_TRUE(app_item_delegate->image_set_by_controller());
const ash::ShelfItemDelegate* app_custom_icon_item_delegate =
GetShelfItemDelegate(app_custom_icon_item.id);
ASSERT_TRUE(app_custom_icon_item_delegate);
EXPECT_TRUE(app_custom_icon_item_delegate->image_set_by_controller());
// Ensure icon height is correct (see test.js in app_icon/ test directory)
// Note, images are no longer available in ChromeShelfController. They are
// are passed directly to the ShelfController.
EXPECT_EQ(extension_misc::EXTENSION_ICON_LARGE,
app_item_custom_image.height());
// No more icon updates.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(app_service_test().AreIconImageEqual(
custome_icon, test_observer.last_app_icon()));
EXPECT_EQ(update_number, test_observer.icon_updates());
// Exit.
EXPECT_TRUE(ready_listener.WaitUntilSatisfied());
ready_listener.Reply("exit");
ready_listener.Reset();
}
// Test that app window has shelf ID and app ID properties set.
IN_PROC_BROWSER_TEST_F(ShelfPlatformAppBrowserTest, AppIDWindowProperties) {
const Extension* extension = LoadAndLaunchPlatformApp("launch", "Launched");
AppWindow* window = CreateAppWindow(browser()->profile(), extension);
ASSERT_TRUE(window);
const gfx::NativeWindow native_window = window->GetNativeWindow();
ash::ShelfID shelf_id =
ash::ShelfID::Deserialize(native_window->GetProperty(ash::kShelfIDKey));
EXPECT_EQ(extension->id(), shelf_id.app_id);
std::string* app_id = native_window->GetProperty(ash::kAppIDKey);
ASSERT_TRUE(app_id);
EXPECT_EQ(shelf_id.app_id, *app_id);
CloseAppWindow(window);
}
// Test that we can launch an app with a shortcut.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, LaunchPinned) {
TabStripModel* tab_strip = browser()->tab_strip_model();
int tab_count = tab_strip->count();
ash::ShelfID shortcut_id = CreateShortcut("app1");
EXPECT_EQ(ash::STATUS_CLOSED, shelf_model()->ItemByID(shortcut_id)->status);
SelectItem(shortcut_id);
EXPECT_EQ(++tab_count, tab_strip->count());
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->ItemByID(shortcut_id)->status);
WebContents* tab = tab_strip->GetActiveWebContents();
content::WebContentsDestroyedWatcher destroyed_watcher(tab);
browser()->tab_strip_model()->CloseSelectedTabs();
destroyed_watcher.Wait();
EXPECT_EQ(--tab_count, tab_strip->count());
EXPECT_EQ(ash::STATUS_CLOSED, shelf_model()->ItemByID(shortcut_id)->status);
}
// Tests behavior of launching app from shelf in the first display while the
// second display has the focus. Initially, Browsers exists in the first
// display.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, LaunchAppFromDisplayWithoutFocus0) {
// Updates the display configuration to add a secondary display.
display::DisplayManager* display_manager =
ash::Shell::Get()->display_manager();
display::test::DisplayManagerTestApi(display_manager)
.UpdateDisplay("0+0-800x700,801+0-800x700");
display::Displays displays = display_manager->active_display_list();
aura::Window::Windows roots = ash::Shell::GetAllRootWindows();
EXPECT_EQ(displays.size(), 2U);
EXPECT_EQ(roots.size(), 2U);
EXPECT_EQ(
displays[0].id(),
display::Screen::GetScreen()->GetDisplayNearestWindow(roots[0]).id());
EXPECT_EQ(
displays[1].id(),
display::Screen::GetScreen()->GetDisplayNearestWindow(roots[1]).id());
// Ensures that display 0 has one browser with focus and display 1 has two
// browsers. Each browser only has one tab.
BrowserList* browser_list = BrowserList::GetInstance();
Browser* browser0 = browser();
Browser* browser1 = CreateBrowser(browser()->profile());
Browser* browser2 = CreateBrowser(browser()->profile());
browser0->window()->SetBounds(displays[0].work_area());
browser1->window()->SetBounds(displays[1].work_area());
browser2->window()->SetBounds(displays[1].work_area());
// Ensures browser 2 is above browser 1 in display 1.
browser_list->SetLastActive(browser2);
browser_list->SetLastActive(browser0);
EXPECT_EQ(browser_list->size(), 3U);
EXPECT_EQ(displays[0].id(), GetDisplayIdForBrowserWindow(browser0->window()));
EXPECT_EQ(displays[1].id(), GetDisplayIdForBrowserWindow(browser1->window()));
EXPECT_EQ(displays[1].id(), GetDisplayIdForBrowserWindow(browser2->window()));
EXPECT_EQ(browser0->tab_strip_model()->count(), 1);
EXPECT_EQ(browser1->tab_strip_model()->count(), 1);
EXPECT_EQ(browser2->tab_strip_model()->count(), 1);
// Launches an app from the shelf of display 0 and expects a new tab is opened
// in the uppermost browser in display 0.
ash::ShelfID shortcut_id = CreateShortcut("app1");
SelectItem(shortcut_id, ui::EventType::kMousePressed, displays[1].id());
EXPECT_EQ(browser0->tab_strip_model()->count(), 1);
EXPECT_EQ(browser1->tab_strip_model()->count(), 1);
EXPECT_EQ(browser2->tab_strip_model()->count(), 2);
}
// Tests behavior of launching app from shelf in the first display while the
// second display has the focus. Initially, No browser exists in the first
// display.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, LaunchAppFromDisplayWithoutFocus1) {
// Updates the display configuration to add a secondary display.
display::DisplayManager* display_manager =
ash::Shell::Get()->display_manager();
display::test::DisplayManagerTestApi(display_manager)
.UpdateDisplay("800x700,801+0-800x700");
display::Displays displays = display_manager->active_display_list();
aura::Window::Windows roots = ash::Shell::GetAllRootWindows();
EXPECT_EQ(displays.size(), 2U);
EXPECT_EQ(roots.size(), 2U);
EXPECT_EQ(
displays[0].id(),
display::Screen::GetScreen()->GetDisplayNearestWindow(roots[0]).id());
EXPECT_EQ(
displays[1].id(),
display::Screen::GetScreen()->GetDisplayNearestWindow(roots[1]).id());
// Ensures that display 0 has one browser with focus and display 1 has no
// browser. The browser only has one tab.
BrowserList* browser_list = BrowserList::GetInstance();
Browser* browser0 = browser();
browser0->window()->SetBounds(displays[0].work_area());
EXPECT_EQ(browser_list->size(), 1U);
EXPECT_EQ(displays[0].id(), GetDisplayIdForBrowserWindow(browser0->window()));
EXPECT_EQ(browser0->tab_strip_model()->count(), 1);
// Launches an app from the shelf of display 0 and expects a new browser with
// one tab is opened in display 0.
ash::ShelfID shortcut_id = CreateShortcut("app1");
SelectItem(shortcut_id, ui::EventType::kMousePressed, displays[1].id());
Browser* browser1 = browser_list->GetLastActive();
EXPECT_EQ(browser_list->size(), 2U);
EXPECT_NE(browser1, browser0);
EXPECT_EQ(browser0->tab_strip_model()->count(), 1);
EXPECT_EQ(browser1->tab_strip_model()->count(), 1);
}
// Launch the app first and then create the shortcut.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, LaunchUnpinned) {
TabStripModel* tab_strip = browser()->tab_strip_model();
int tab_count = tab_strip->count();
LoadAndLaunchExtension(
"app1", apps::GetEventFlags(WindowOpenDisposition::NEW_FOREGROUND_TAB,
true /* prefer_containner */));
EXPECT_EQ(++tab_count, tab_strip->count());
ash::ShelfID shortcut_id = CreateShortcut("app1");
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->ItemByID(shortcut_id)->status);
WebContents* tab = tab_strip->GetActiveWebContents();
content::WebContentsDestroyedWatcher destroyed_watcher(tab);
browser()->tab_strip_model()->CloseSelectedTabs();
destroyed_watcher.Wait();
EXPECT_EQ(--tab_count, tab_strip->count());
EXPECT_EQ(ash::STATUS_CLOSED, shelf_model()->ItemByID(shortcut_id)->status);
}
// Verifies that native browser window properties are properly set when showing
// an unpinned hosted app web contents.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, AppIDForUnpinnedHostedApp) {
const extensions::Extension* extension = LoadAndLaunchExtension(
"app1", apps::GetEventFlags(WindowOpenDisposition::NEW_FOREGROUND_TAB,
true /* prefer_containner */));
int browser_index = GetIndexOfShelfItemType(ash::TYPE_BROWSER_SHORTCUT);
ash::ShelfID browser_id = shelf_model()->items()[browser_index].id;
// If the app is not pinned, and thus does not have an associated shelf item,
// the shelf ID should be set to the browser ID,
const gfx::NativeWindow native_window =
browser()->window()->GetNativeWindow();
ash::ShelfID shelf_id =
ash::ShelfID::Deserialize(native_window->GetProperty(ash::kShelfIDKey));
EXPECT_EQ(browser_id, shelf_id);
// The app ID should have the actual extension ID.
std::string* app_id = native_window->GetProperty(ash::kAppIDKey);
ASSERT_TRUE(app_id);
EXPECT_EQ(extension->id(), *app_id);
}
// Verifies that native browser window properties are properly set when showing
// a pinned hosted app web contents.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, AppIDForPinnedHostedApp) {
// Load and pin a hosted app.
const Extension* extension =
LoadExtension(test_data_dir_.AppendASCII("app1/"));
ASSERT_TRUE(extension);
PinAppWithIDToShelf(extension->id());
// Navigate to the app's launch URL.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), extensions::AppLaunchInfo::GetLaunchWebURL(extension)));
// When an app shportcut exists, the window shelf ID should point to the app
// shortcut.
const gfx::NativeWindow native_window =
browser()->window()->GetNativeWindow();
ash::ShelfID shelf_id =
ash::ShelfID::Deserialize(native_window->GetProperty(ash::kShelfIDKey));
EXPECT_EQ(extension->id(), shelf_id.app_id);
std::string* app_id = native_window->GetProperty(ash::kAppIDKey);
ASSERT_TRUE(app_id);
EXPECT_EQ(extension->id(), *app_id);
}
// Verifies that native browser window properties are properly set when showing
// an unpinned web app.
IN_PROC_BROWSER_TEST_F(ShelfWebAppBrowserTest, AppIDForUnpinnedWebApp) {
// Load and navigate to a web app.
const GURL app_url = GetSecureAppURL();
const webapps::AppId web_app_id = InstallWebApp(app_url);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), app_url));
int browser_index = GetIndexOfShelfItemType(ash::TYPE_BROWSER_SHORTCUT);
ash::ShelfID browser_id = shelf_model()->items()[browser_index].id;
// If the app is not pinned, and thus does not have an associated shelf item,
// the shelf ID should be set to the browser ID,
const gfx::NativeWindow native_window =
browser()->window()->GetNativeWindow();
ash::ShelfID shelf_id =
ash::ShelfID::Deserialize(native_window->GetProperty(ash::kShelfIDKey));
EXPECT_EQ(browser_id, shelf_id);
// The app ID should have the actual web app ID.
std::string* app_id = native_window->GetProperty(ash::kAppIDKey);
ASSERT_TRUE(app_id);
EXPECT_EQ(web_app_id, *app_id);
}
// Verifies that native browser window properties are properly set when showing
// a pinned web app.
IN_PROC_BROWSER_TEST_F(ShelfWebAppBrowserTest, AppIDForPinnedWebApp) {
// Load and navigate to a web app.
const GURL app_url = GetSecureAppURL();
const webapps::AppId web_app_id = InstallWebApp(app_url);
PinAppWithIDToShelf(web_app_id);
// Navigate to the app's launch URL.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), app_url));
// When an app shportcut exists, the window shelf ID should point to the app
// shortcut.
const gfx::NativeWindow native_window =
browser()->window()->GetNativeWindow();
ash::ShelfID shelf_id =
ash::ShelfID::Deserialize(native_window->GetProperty(ash::kShelfIDKey));
EXPECT_EQ(web_app_id, shelf_id.app_id);
std::string* app_id = native_window->GetProperty(ash::kAppIDKey);
ASSERT_TRUE(app_id);
EXPECT_EQ(web_app_id, *app_id);
}
// Verifies that native browser window properties are properly set when showing
// a PWA tab.
IN_PROC_BROWSER_TEST_F(ShelfWebAppBrowserTest, AppIDForPWA) {
// Start server and open test page.
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("/banners/manifest_test_page.html"));
web_app::ServiceWorkerRegistrationWaiter registration_waiter(profile(), url);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
registration_waiter.AwaitRegistration();
// Install PWA.
web_app::SetAutoAcceptPWAInstallConfirmationForTesting(true);
web_app::WebAppTestInstallWithOsHooksObserver install_observer(profile());
install_observer.BeginListening();
chrome::ExecuteCommand(browser(), IDC_INSTALL_PWA);
const webapps::AppId app_id = install_observer.Wait();
web_app::SetAutoAcceptPWAInstallConfirmationForTesting(false);
// Find the native window for the app.
gfx::NativeWindow native_window = gfx::NativeWindow();
for (Browser* browser : *BrowserList::GetInstance()) {
if (browser->app_controller() &&
browser->app_controller()->app_id() == app_id) {
native_window = browser->window()->GetNativeWindow();
break;
}
}
ASSERT_TRUE(native_window);
// The native window shelf ID and app ID should match the web app ID.
ash::ShelfID shelf_id =
ash::ShelfID::Deserialize(native_window->GetProperty(ash::kShelfIDKey));
EXPECT_EQ(app_id, shelf_id.app_id);
std::string* window_app_id = native_window->GetProperty(ash::kAppIDKey);
ASSERT_TRUE(window_app_id);
EXPECT_EQ(app_id, *window_app_id);
}
// Launches an app in the background and then tries to open it. This is test for
// a crash we had.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, LaunchInBackground) {
TabStripModel* tab_strip = browser()->tab_strip_model();
int tab_count = tab_strip->count();
LoadAndLaunchExtension(
"app1", apps::GetEventFlags(WindowOpenDisposition::NEW_BACKGROUND_TAB,
true /* prefer_containner */));
EXPECT_EQ(++tab_count, tab_strip->count());
controller_->LaunchApp(ash::ShelfID(last_loaded_extension_id()),
ash::LAUNCH_FROM_UNKNOWN, 0,
display::kInvalidDisplayId);
}
// Confirm that clicking a icon for an app running in one of 2 maximized windows
// activates the right window.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, LaunchMaximized) {
browser()->window()->Maximize();
// Load about:blank in a new window.
Browser* browser2 = CreateBrowser(browser()->profile());
EXPECT_NE(browser(), browser2);
TabStripModel* tab_strip = browser2->tab_strip_model();
int tab_count = tab_strip->count();
browser2->window()->Maximize();
ash::ShelfID shortcut_id = CreateShortcut("app1");
SelectItem(shortcut_id);
EXPECT_EQ(++tab_count, tab_strip->count());
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->ItemByID(shortcut_id)->status);
// Activate the first browser window.
browser()->window()->Activate();
EXPECT_FALSE(browser2->window()->IsActive());
// Selecting the shortcut activates the second window.
SelectItem(shortcut_id);
EXPECT_TRUE(browser2->window()->IsActive());
}
// Launching the same app multiple times should launch a copy for each call.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, LaunchApp) {
TabStripModel* tab_strip = browser()->tab_strip_model();
int tab_count = tab_strip->count();
ash::ShelfID id(LoadExtension(test_data_dir_.AppendASCII("app1"))->id());
LaunchApp(id, ash::LAUNCH_FROM_UNKNOWN, 0, display::kInvalidDisplayId);
EXPECT_EQ(++tab_count, tab_strip->count());
LaunchApp(id, ash::LAUNCH_FROM_UNKNOWN, 0, display::kInvalidDisplayId);
EXPECT_EQ(++tab_count, tab_strip->count());
}
// The Browsertest verifying FilesManager's features.
class FilesManagerExtensionTest : public ShelfPlatformAppBrowserTest {
public:
void SetUp() override { ShelfPlatformAppBrowserTest::SetUp(); }
void SetUpOnMainThread() override {
ShelfPlatformAppBrowserTest::SetUpOnMainThread();
CHECK(profile());
file_manager::test::AddDefaultComponentExtensionsOnMainThread(profile());
ash::SystemWebAppManager::GetForTest(profile())
->InstallSystemAppsForTesting();
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Verifies that FilesManager's first shelf context menu item is "New window"
// (see https://crbug.com/1102781).
IN_PROC_BROWSER_TEST_F(FilesManagerExtensionTest, VerifyFirstItem) {
const std::string top_level_item_label("New window");
auto shelf_id =
CreateAppShortcutItem(ash::ShelfID(file_manager::kFileManagerSwaAppId));
const ash::ShelfItem* item = shelf_model()->ItemByID(shelf_id);
int64_t display_id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
auto menu = ShelfContextMenu::Create(controller_, item, display_id);
// Fetch |extension|'s shelf context menu model and verify that the top level
// menu item should be the first one.
base::RunLoop run_loop;
menu->GetMenuModel(base::BindLambdaForTesting(
[&](std::unique_ptr<ui::SimpleMenuModel> menu_model) {
EXPECT_EQ(base::ASCIIToUTF16(top_level_item_label),
menu_model->GetLabelAt(0));
run_loop.Quit();
}));
run_loop.Run();
}
// Launching an app from the shelf when not in Demo Mode should not record app
// launch stat.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, NoDemoModeAppLaunchSourceReported) {
EXPECT_FALSE(ash::DemoSession::IsDeviceInDemoMode());
base::HistogramTester histogram_tester;
// Should see 0 apps launched from the Shelf in the histogram at first.
histogram_tester.ExpectTotalCount("DemoMode.AppLaunchSource", 0);
ash::ShelfID id(LoadExtension(test_data_dir_.AppendASCII("app1"))->id());
controller_->LaunchApp(id, ash::LAUNCH_FROM_SHELF, 0,
display::kInvalidDisplayId);
// Should still see 0 apps launched from the Shelf in the histogram.
histogram_tester.ExpectTotalCount("DemoMode.AppLaunchSource", 0);
}
// Launching an app from the shelf in Demo Mode should record app
// launch stat.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, DemoModeAppLaunchSourceReported) {
// Set Demo mode
ash::test::LockDemoDeviceInstallAttributes();
EXPECT_TRUE(ash::DemoSession::IsDeviceInDemoMode());
base::HistogramTester histogram_tester;
// Should see 0 apps launched from the Shelf in the histogram at first.
histogram_tester.ExpectTotalCount("DemoMode.AppLaunchSource", 0);
ash::ShelfID id(LoadExtension(test_data_dir_.AppendASCII("app1"))->id());
controller_->LaunchApp(id, ash::LAUNCH_FROM_SHELF, 0,
display::kInvalidDisplayId);
// Should see 1 app launched from the shelf in the histogram.
histogram_tester.ExpectUniqueSample(
"DemoMode.AppLaunchSource", ash::DemoSession::AppLaunchSource::kShelf, 1);
}
// Confirm that a page can be navigated from and to while maintaining the
// correct running state.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, Navigation) {
ash::ShelfID shortcut_id = CreateShortcut("app1");
EXPECT_EQ(ash::STATUS_CLOSED, shelf_model()->ItemByID(shortcut_id)->status);
SelectItem(shortcut_id);
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->ItemByID(shortcut_id)->status);
// Navigate away.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), GURL("http://www.example.com/path0/bar.html")));
EXPECT_EQ(ash::STATUS_CLOSED, shelf_model()->ItemByID(shortcut_id)->status);
// Navigate back.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), GURL("http://www.example.com/path1/foo.html")));
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->ItemByID(shortcut_id)->status);
}
// Confirm that a tab can be moved between browsers while maintaining the
// correct running state.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, TabDragAndDrop) {
EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
TabStripModel* tab_strip_model1 = browser()->tab_strip_model();
EXPECT_EQ(1, tab_strip_model1->count());
const int browser_index = GetIndexOfShelfItemType(ash::TYPE_BROWSER_SHORTCUT);
EXPECT_GE(browser_index, 0);
// Create a shortcut for app1.
ash::ShelfID shortcut_id = CreateShortcut("app1");
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->items()[browser_index].status);
EXPECT_EQ(ash::STATUS_CLOSED, shelf_model()->ItemByID(shortcut_id)->status);
// Activate app1 and check its item status.
SelectItem(shortcut_id);
EXPECT_EQ(2, tab_strip_model1->count());
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->items()[browser_index].status);
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->ItemByID(shortcut_id)->status);
// Create a new browser with blank tab.
Browser* browser2 = CreateBrowser(profile());
EXPECT_EQ(2u, chrome::GetTotalBrowserCount());
TabStripModel* tab_strip_model2 = browser2->tab_strip_model();
EXPECT_EQ(1, tab_strip_model2->count());
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->items()[browser_index].status);
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->ItemByID(shortcut_id)->status);
// Detach a tab at index 1 (app1) from |tab_strip_model1| and insert it as an
// active tab at index 1 to |tab_strip_model2|.
std::unique_ptr<tabs::TabModel> detached_tab =
tab_strip_model1->DetachTabAtForInsertion(1);
tab_strip_model2->InsertDetachedTabAt(1, std::move(detached_tab),
AddTabTypes::ADD_ACTIVE);
EXPECT_EQ(1, tab_strip_model1->count());
EXPECT_EQ(2, tab_strip_model2->count());
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->items()[browser_index].status);
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->ItemByID(shortcut_id)->status);
tab_strip_model1->CloseAllTabs();
tab_strip_model2->CloseAllTabs();
}
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, RefocusFilterLaunch) {
TabStripModel* tab_strip = browser()->tab_strip_model();
int tab_count = tab_strip->count();
ash::ShelfID shortcut_id = CreateShortcut("app1");
SetRefocusURL(shortcut_id, GURL("http://www.example.com/path1/*"));
// Create new tab.
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL("http://www.example2.com/path2/bar.html"),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
EXPECT_EQ(++tab_count, tab_strip->count());
WebContents* first_tab = tab_strip->GetActiveWebContents();
// Confirm app is not active.
EXPECT_EQ(ash::STATUS_CLOSED, shelf_model()->ItemByID(shortcut_id)->status);
// Activating app should launch new tab, because second tab isn't
// in its refocus url path.
SelectItem(shortcut_id);
EXPECT_EQ(++tab_count, tab_strip->count());
WebContents* second_tab = tab_strip->GetActiveWebContents();
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->ItemByID(shortcut_id)->status);
EXPECT_NE(first_tab, second_tab);
EXPECT_EQ(tab_strip->GetActiveWebContents(), second_tab);
}
// Check that the shelf item activation state for a V1 application stays closed
// even after an asynchronous browser event comes in after the tab got
// destroyed.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, AsyncActivationStateCheck) {
TabStripModel* tab_strip = browser()->tab_strip_model();
ash::ShelfID shortcut_id = CreateShortcut("app1");
SetRefocusURL(shortcut_id, GURL("http://www.example.com/path1/*"));
EXPECT_EQ(ash::STATUS_CLOSED, shelf_model()->ItemByID(shortcut_id)->status);
// Create new tab which would be the running app.
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL("http://www.example.com/path1/bar.html"),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->ItemByID(shortcut_id)->status);
// To address the issue of crbug.com/174050, the tab we are about to close
// has to be active.
tab_strip->ActivateTabAt(1);
EXPECT_EQ(1, tab_strip->active_index());
// Close the web contents.
tab_strip->CloseWebContentsAt(1, TabCloseTypes::CLOSE_NONE);
// The status should now be set to closed.
EXPECT_EQ(ash::STATUS_CLOSED, shelf_model()->ItemByID(shortcut_id)->status);
}
// Test that the App window could restore to its previous window state from
// before it was closed.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, AppWindowRestoreBehaviorTest) {
// Open an App, maximized its window, and close it.
const Extension* extension = LoadAndLaunchExtension(
"app1", apps::GetEventFlags(WindowOpenDisposition::NEW_WINDOW,
false /* prefer_containner */));
Browser* app_browser = FindBrowserForApp(extension->id());
ASSERT_TRUE(app_browser);
BrowserWindow* window = app_browser->window();
EXPECT_FALSE(window->IsMaximized());
window->Maximize();
EXPECT_TRUE(window->IsMaximized());
CloseAppBrowserWindow(app_browser);
// Reopen the App. It should start maximized. Un-maximize it and close it.
extension = LoadAndLaunchExtension(
"app1", apps::GetEventFlags(WindowOpenDisposition::NEW_WINDOW,
false /* prefer_containner */));
app_browser = FindBrowserForApp(extension->id());
ASSERT_TRUE(app_browser);
window = app_browser->window();
EXPECT_TRUE(window->IsMaximized());
window->Restore();
EXPECT_FALSE(window->IsMaximized());
app_browser->window()->Close();
CloseAppBrowserWindow(app_browser);
// Reopen the App. It should start un-maximized.
extension = LoadAndLaunchExtension(
"app1", apps::GetEventFlags(WindowOpenDisposition::NEW_WINDOW,
false /* prefer_containner */));
app_browser = FindBrowserForApp(extension->id());
ASSERT_TRUE(app_browser);
window = app_browser->window();
EXPECT_FALSE(window->IsMaximized());
}
// Checks that a windowed application does not add an item to the browser list.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTestNoDefaultBrowser,
WindowedAppDoesNotAddToBrowser) {
// Get the number of items in the browser menu.
size_t items = BrowserShortcutMenuItemCount(false);
size_t running_browser = chrome::GetTotalBrowserCount();
EXPECT_EQ(0u, items);
EXPECT_EQ(0u, running_browser);
const Extension* extension = LoadAndLaunchExtension(
"app1", apps::GetEventFlags(WindowOpenDisposition::NEW_WINDOW,
false /* prefer_containner */));
ASSERT_TRUE(extension);
// No new browser should get detected, even though one more is running.
EXPECT_EQ(0u, BrowserShortcutMenuItemCount(false));
EXPECT_EQ(++running_browser, chrome::GetTotalBrowserCount());
auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile());
proxy->Launch(extension->id(),
apps::GetEventFlags(WindowOpenDisposition::NEW_FOREGROUND_TAB,
true /* prefer_containner */),
apps::LaunchSource::kFromTest,
std::make_unique<apps::WindowInfo>(
display::Screen::GetScreen()->GetPrimaryDisplay().id()));
// A new browser should get detected and one more should be running.
EXPECT_EQ(BrowserShortcutMenuItemCount(false), 1u);
EXPECT_EQ(++running_browser, chrome::GetTotalBrowserCount());
}
// Checks the functionality to enumerate all browsers vs. all tabs.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTestNoDefaultBrowser,
EnumerateAllBrowsersAndTabs) {
// Create at least one browser.
LoadAndLaunchExtension(
"app1", apps::GetEventFlags(WindowOpenDisposition::NEW_FOREGROUND_TAB,
true /* prefer_containner */));
size_t browsers = BrowserShortcutMenuItemCount(false);
size_t tabs = BrowserShortcutMenuItemCount(true);
// Create a second browser.
const Extension* extension =
extension_registry()->enabled_extensions().GetByID(
last_loaded_extension_id());
EXPECT_TRUE(extension);
apps::AppServiceProxyFactory::GetForProfile(profile())->LaunchAppWithParams(
apps::AppLaunchParams(
extension->id(), apps::LaunchContainer::kLaunchContainerTab,
WindowOpenDisposition::NEW_WINDOW, apps::LaunchSource::kFromTest));
EXPECT_EQ(++browsers, BrowserShortcutMenuItemCount(false));
EXPECT_EQ(++tabs, BrowserShortcutMenuItemCount(true));
// Create only a tab.
LoadAndLaunchExtension(
"app1", apps::GetEventFlags(WindowOpenDisposition::NEW_FOREGROUND_TAB,
true /* prefer_containner */));
EXPECT_EQ(browsers, BrowserShortcutMenuItemCount(false));
EXPECT_EQ(++tabs, BrowserShortcutMenuItemCount(true));
}
// Check that the keyboard activation of a shelf item tabs properly through
// the items at hand.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, AltNumberTabsTabbing) {
TabStripModel* tab_strip = browser()->tab_strip_model();
ash::ShelfID shortcut_id = CreateShortcut("app");
SetRefocusURL(shortcut_id, GURL("http://www.example.com/path/*"));
std::string url = "http://www.example.com/path/bla";
// Create an application handled browser tab.
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(url), WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
content::WebContents* content1 = tab_strip->GetActiveWebContents();
// Create some other browser tab.
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL("http://www.test.com"),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
content::WebContents* content1a = tab_strip->GetActiveWebContents();
// Make sure that the active tab is now our handled tab.
EXPECT_NE(content1a, content1);
// The active tab should still be the unnamed tab. Then we switch and reach
// the first app and stay there.
EXPECT_EQ(content1a, tab_strip->GetActiveWebContents());
SelectItem(shortcut_id, ui::EventType::kKeyReleased);
EXPECT_EQ(content1, tab_strip->GetActiveWebContents());
SelectItem(shortcut_id, ui::EventType::kKeyReleased);
EXPECT_EQ(content1, tab_strip->GetActiveWebContents());
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(url), WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
content::WebContents* content2 = tab_strip->GetActiveWebContents();
EXPECT_EQ(content2, browser()->tab_strip_model()->GetActiveWebContents());
SelectItem(shortcut_id, ui::EventType::kKeyReleased);
EXPECT_EQ(content1, browser()->tab_strip_model()->GetActiveWebContents());
SelectItem(shortcut_id, ui::EventType::kKeyReleased);
EXPECT_EQ(content2, browser()->tab_strip_model()->GetActiveWebContents());
}
// Check that the keyboard activation of a shelf item tabs properly through
// the items at hand.
// TODO(crbug.com/331536126): This test is flaky.
IN_PROC_BROWSER_TEST_F(ShelfPlatformAppBrowserTest,
DISABLED_AltNumberAppsTabbing) {
// First run app.
const Extension* extension1 = LoadAndLaunchPlatformApp("launch", "Launched");
ui::BaseWindow* window1 =
CreateAppWindow(browser()->profile(), extension1)->GetBaseWindow();
const ash::ShelfItem item = GetLastShelfItem();
EXPECT_EQ(ash::TYPE_APP, item.type);
EXPECT_EQ(ash::STATUS_RUNNING, item.status);
const Extension* extension2 =
LoadAndLaunchPlatformApp("launch_2", "Launched");
ui::BaseWindow* window2 =
CreateAppWindow(browser()->profile(), extension2)->GetBaseWindow();
// By now the browser should be active. Issue Alt keystrokes several times to
// see that we stay on that application.
EXPECT_TRUE(window2->IsActive());
SelectItem(item.id, ui::EventType::kKeyReleased);
EXPECT_TRUE(window1->IsActive());
SelectItem(item.id, ui::EventType::kKeyReleased);
EXPECT_TRUE(window1->IsActive());
ui::BaseWindow* window1a =
CreateAppWindow(browser()->profile(), extension1)->GetBaseWindow();
EXPECT_TRUE(window1a->IsActive());
EXPECT_FALSE(window1->IsActive());
SelectItem(item.id, ui::EventType::kKeyReleased);
EXPECT_TRUE(window1->IsActive());
SelectItem(item.id, ui::EventType::kKeyReleased);
EXPECT_TRUE(window1a->IsActive());
}
// Check that the keyboard activation of a shelf item tabs even if the app is
// not currently activated.
IN_PROC_BROWSER_TEST_F(ShelfPlatformAppBrowserTest,
AltNumberAppsTabbingFromOtherApp) {
// Create one app with two windows.
const Extension* app1_extension1 =
LoadAndLaunchPlatformApp("launch", "Launched");
ui::BaseWindow* app1_window1 =
CreateAppWindow(browser()->profile(), app1_extension1)->GetBaseWindow();
ui::BaseWindow* app1_window2 =
CreateAppWindow(browser()->profile(), app1_extension1)->GetBaseWindow();
const ash::ShelfItem item1 = GetLastShelfItem();
EXPECT_EQ(ash::TYPE_APP, item1.type);
EXPECT_EQ(ash::STATUS_RUNNING, item1.status);
// Create another app with two windows.
const Extension* app2_extension1 =
LoadAndLaunchPlatformApp("launch_2", "Launched");
ui::BaseWindow* app2_window1 =
CreateAppWindow(browser()->profile(), app2_extension1)->GetBaseWindow();
ui::BaseWindow* app2_window2 =
CreateAppWindow(browser()->profile(), app2_extension1)->GetBaseWindow();
const ash::ShelfItem item2 = GetLastShelfItem();
EXPECT_EQ(ash::TYPE_APP, item2.type);
EXPECT_EQ(ash::STATUS_RUNNING, item2.status);
// Last created window should be active. Hitting the app shortcut should go to
// the first window of the app.
ASSERT_TRUE(app2_window2->IsActive());
SelectItem(item2.id, ui::EventType::kKeyReleased);
EXPECT_TRUE(app2_window1->IsActive());
// Hitting the other app's shortcut should jump and focus the other app's
// windows.
SelectItem(item1.id, ui::EventType::kKeyReleased);
EXPECT_TRUE(app1_window2->IsActive());
SelectItem(item1.id, ui::EventType::kKeyReleased);
EXPECT_TRUE(app1_window1->IsActive());
}
// Test that we get correct shelf presence with hidden app windows.
IN_PROC_BROWSER_TEST_F(ShelfPlatformAppBrowserTest, HiddenAppWindows) {
int item_count = shelf_model()->item_count();
const Extension* extension = LoadAndLaunchPlatformApp("launch", "Launched");
AppWindow::CreateParams params;
// Create a hidden window.
params.hidden = true;
AppWindow* window_1 =
CreateAppWindowFromParams(browser()->profile(), extension, params);
EXPECT_EQ(item_count, shelf_model()->item_count());
// Create a visible window.
params.hidden = false;
AppWindow* window_2 =
CreateAppWindowFromParams(browser()->profile(), extension, params);
++item_count;
EXPECT_EQ(item_count, shelf_model()->item_count());
// Minimize the visible window.
window_2->Minimize();
EXPECT_EQ(item_count, shelf_model()->item_count());
// Hide the visible window.
window_2->Hide();
--item_count;
EXPECT_EQ(item_count, shelf_model()->item_count());
// Show the originally hidden window.
window_1->Show(AppWindow::SHOW_ACTIVE);
++item_count;
EXPECT_EQ(item_count, shelf_model()->item_count());
// Close the originally hidden window.
CloseAppWindow(window_1);
--item_count;
EXPECT_EQ(item_count, shelf_model()->item_count());
}
// Test attention states of windows.
IN_PROC_BROWSER_TEST_F(ShelfPlatformAppBrowserTest, WindowAttentionStatus) {
const Extension* extension = LoadAndLaunchPlatformApp("launch", "Launched");
AppWindow::CreateParams params;
params.focused = false;
AppWindow* window =
CreateAppWindowFromParams(browser()->profile(), extension, params);
EXPECT_TRUE(window->GetNativeWindow()->IsVisible());
// The window should not be active by default.
EXPECT_FALSE(window->GetBaseWindow()->IsActive());
// Confirm that a shelf item was created and is the correct state.
const ash::ShelfItem& item = GetLastShelfItem();
EXPECT_TRUE(GetShelfItemDelegate(item.id));
EXPECT_EQ(ash::TYPE_APP, item.type);
EXPECT_EQ(ash::STATUS_RUNNING, item.status);
// App windows should go to attention state.
window->GetNativeWindow()->SetProperty(aura::client::kDrawAttentionKey, true);
EXPECT_EQ(ash::STATUS_ATTENTION, item.status);
// Click the item and confirm that the window is activated.
EXPECT_EQ(ash::SHELF_ACTION_WINDOW_ACTIVATED, SelectItem(item.id));
EXPECT_TRUE(window->GetBaseWindow()->IsActive());
// Active windows don't show attention.
window->GetNativeWindow()->SetProperty(aura::client::kDrawAttentionKey, true);
EXPECT_EQ(ash::STATUS_RUNNING, item.status);
}
IN_PROC_BROWSER_TEST_F(ShelfPlatformAppBrowserTest,
ShowInShelfWindowsWithWindowKeySet) {
// Add a window with shelf True, close it
int item_count = shelf_model()->item_count();
const Extension* extension = LoadAndLaunchPlatformApp("launch", "Launched");
AppWindow::CreateParams params;
params.show_in_shelf = true;
params.window_key = "window1";
AppWindow* window1 =
CreateAppWindowFromParams(browser()->profile(), extension, params);
// There should be only 1 item added to the shelf.
EXPECT_EQ(item_count + 1, shelf_model()->item_count());
CloseAppWindow(window1);
EXPECT_EQ(item_count, shelf_model()->item_count());
// Add a window with false, following one with true
item_count = shelf_model()->item_count();
extension = LoadAndLaunchPlatformApp("launch", "Launched");
params.show_in_shelf = false;
params.window_key = "window1";
window1 = CreateAppWindowFromParams(browser()->profile(), extension, params);
EXPECT_EQ(item_count + 1, shelf_model()->item_count());
params.show_in_shelf = true;
params.window_key = "window2";
AppWindow* window2 =
CreateAppWindowFromParams(browser()->profile(), extension, params);
// There should be 2 items added to the shelf: although window1 has
// show_in_shelf set to false, it's the first window created so its icon must
// show up in shelf.
EXPECT_EQ(item_count + 2, shelf_model()->item_count());
CloseAppWindow(window1);
EXPECT_EQ(item_count + 1, shelf_model()->item_count());
CloseAppWindow(window2);
EXPECT_EQ(item_count, shelf_model()->item_count());
// Open just one window with false
item_count = shelf_model()->item_count();
extension = LoadAndLaunchPlatformApp("launch", "Launched");
params.show_in_shelf = false;
params.window_key = "window1";
window1 = CreateAppWindowFromParams(browser()->profile(), extension, params);
// There should be 1 item added to the shelf: although show_in_shelf is false,
// this is the first window created.
EXPECT_EQ(item_count + 1, shelf_model()->item_count());
CloseAppWindow(window1);
EXPECT_EQ(item_count, shelf_model()->item_count());
// Add a window with true, following one with false
item_count = shelf_model()->item_count();
extension = LoadAndLaunchPlatformApp("launch", "Launched");
params.show_in_shelf = true;
params.window_key = "window1";
window1 = CreateAppWindowFromParams(browser()->profile(), extension, params);
EXPECT_EQ(item_count + 1, shelf_model()->item_count()); // main window
params.show_in_shelf = false;
params.window_key = "window2";
window2 = CreateAppWindowFromParams(browser()->profile(), extension, params);
EXPECT_EQ(item_count + 2, shelf_model()->item_count());
CloseAppWindow(window1);
// There should be 1 item added to the shelf as the second window
// is set to show_in_shelf false
EXPECT_EQ(item_count + 1, shelf_model()->item_count());
CloseAppWindow(window2);
EXPECT_EQ(item_count, shelf_model()->item_count());
// Test closing windows in different order
item_count = shelf_model()->item_count();
extension = LoadAndLaunchPlatformApp("launch", "Launched");
params.show_in_shelf = false;
params.window_key = "window1";
window1 = CreateAppWindowFromParams(browser()->profile(), extension, params);
EXPECT_EQ(item_count + 1, shelf_model()->item_count());
params.show_in_shelf = false;
params.window_key = "window2";
window2 = CreateAppWindowFromParams(browser()->profile(), extension, params);
EXPECT_EQ(item_count + 1, shelf_model()->item_count());
params.show_in_shelf = true;
params.window_key = "window3";
AppWindow* window3 =
CreateAppWindowFromParams(browser()->profile(), extension, params);
EXPECT_EQ(item_count + 2, shelf_model()->item_count());
params.show_in_shelf = true;
params.window_key = "window4";
AppWindow* window4 =
CreateAppWindowFromParams(browser()->profile(), extension, params);
// There should be 3 items added to the shelf.
EXPECT_EQ(item_count + 3, shelf_model()->item_count());
// Any window close order should be valid
CloseAppWindow(window4);
// Closed window4 that was shown in shelf. item_count would decrease
EXPECT_EQ(item_count + 2, shelf_model()->item_count());
CloseAppWindow(window1);
// Closed window1 which was grouped together with window2 so item_count
// would not decrease
EXPECT_EQ(item_count + 2, shelf_model()->item_count());
CloseAppWindow(window3);
// Closed window3 that was shown in shelf. item_count would decrease
EXPECT_EQ(item_count + 1, shelf_model()->item_count());
CloseAppWindow(window2);
// Closed window2 - there is no other window in that group and item_count
// would decrease
EXPECT_EQ(item_count, shelf_model()->item_count());
}
// Checks that the browser Alt "tabbing" is properly done.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTestNoDefaultBrowser,
AltNumberBrowserTabbing) {
// Get the number of items in the browser menu.
EXPECT_EQ(0u, chrome::GetTotalBrowserCount());
// The first activation should create a browser at index 2 (App List @ 0 and
// back button @ 1).
const ash::ShelfID browser_id = shelf_model()->items()[0].id;
SelectItem(browser_id, ui::EventType::kKeyReleased);
EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
// A second activation should not create a new instance.
SelectItem(browser_id, ui::EventType::kKeyReleased);
Browser* browser1 = chrome::FindLastActive();
EXPECT_TRUE(browser1);
Browser* browser2 = CreateBrowser(profile());
EXPECT_EQ(2u, chrome::GetTotalBrowserCount());
EXPECT_NE(browser1->window(), browser2->window());
EXPECT_TRUE(browser2->window()->IsActive());
// Activate multiple times the switcher to see that the windows get activated.
SelectItem(browser_id, ui::EventType::kKeyReleased);
EXPECT_TRUE(browser1->window()->IsActive());
SelectItem(browser_id, ui::EventType::kKeyReleased);
EXPECT_TRUE(browser2->window()->IsActive());
// Create a third browser - make sure that we do not toggle simply between
// two windows.
Browser* browser3 = CreateBrowser(profile());
EXPECT_EQ(3u, chrome::GetTotalBrowserCount());
EXPECT_NE(browser1->window(), browser3->window());
EXPECT_NE(browser2->window(), browser3->window());
EXPECT_TRUE(browser3->window()->IsActive());
SelectItem(browser_id, ui::EventType::kKeyReleased);
EXPECT_TRUE(browser1->window()->IsActive());
SelectItem(browser_id, ui::EventType::kKeyReleased);
EXPECT_TRUE(browser2->window()->IsActive());
SelectItem(browser_id, ui::EventType::kKeyReleased);
EXPECT_TRUE(browser3->window()->IsActive());
SelectItem(browser_id, ui::EventType::kKeyReleased);
EXPECT_TRUE(browser1->window()->IsActive());
// Create another app and make sure that none of our browsers is active.
LoadAndLaunchExtension("app1",
apps::GetEventFlags(WindowOpenDisposition::NEW_WINDOW,
false /* prefer_containner */));
EXPECT_FALSE(browser1->window()->IsActive());
EXPECT_FALSE(browser2->window()->IsActive());
// After activation our browser should be active again.
SelectItem(browser_id, ui::EventType::kKeyReleased);
EXPECT_TRUE(browser1->window()->IsActive());
}
// Checks that after a session restore, we do not start applications on an
// activation.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, ActivateAfterSessionRestore) {
EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
// Create a known application.
ash::ShelfID shortcut_id = CreateShortcut("app1");
// Create a new browser - without activating it - and load an "app" into it.
Browser::CreateParams params = Browser::CreateParams(profile(), true);
params.initial_show_state = ui::SHOW_STATE_INACTIVE;
Browser* browser2 = Browser::Create(params);
SetRefocusURL(shortcut_id, GURL("http://www.example.com/path/*"));
std::string url = "http://www.example.com/path/bla";
ui_test_utils::NavigateToURLWithDisposition(
browser2, GURL(url), WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
// Remember the number of tabs for each browser.
TabStripModel* tab_strip = browser()->tab_strip_model();
int tab_count1 = tab_strip->count();
TabStripModel* tab_strip2 = browser2->tab_strip_model();
int tab_count2 = tab_strip2->count();
// Check that we have two browsers and the inactive browser remained inactive.
EXPECT_EQ(2u, chrome::GetTotalBrowserCount());
EXPECT_EQ(chrome::FindLastActive(), browser());
EXPECT_TRUE(browser()->window()->IsActive());
// Check that the MRU browser list contains both the original browser and
// |browser2|.
BrowserList* browser_list = BrowserList::GetInstance();
BrowserList::const_reverse_iterator it =
browser_list->begin_browsers_ordered_by_activation();
EXPECT_EQ(*it, browser());
++it;
EXPECT_EQ(*it, browser2);
// Now request to either activate an existing app or create a new one.
SelectItem(shortcut_id);
// Check that we have set focus on the existing application and nothing new
// was created.
EXPECT_EQ(2u, chrome::GetTotalBrowserCount());
EXPECT_EQ(tab_count1, tab_strip->count());
EXPECT_EQ(tab_count2, tab_strip2->count());
EXPECT_EQ(chrome::FindLastActive(), browser2);
EXPECT_TRUE(browser2->window()->IsActive());
}
// TODO(crbug.com/759779, crbug.com/819386): add back |DISABLED_DragAndDrop|.
// TODO(crbug.com/759779, crbug.com/819386): add back
// |MultiDisplayBasicDragAndDrop|.
// TODO(crbug.com/759779, crbug.com/819386): add back |ClickItem|.
// Check browser shortcut item functionality.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTestNoDefaultBrowser,
BrowserShortcutShelfItemController) {
ash::ShelfItemDelegate* item_controller =
controller_->GetBrowserShortcutShelfItemControllerForTesting();
ASSERT_TRUE(item_controller);
const ash::ShelfID browser_id = item_controller->shelf_id();
EXPECT_EQ(app_constants::kChromeAppId, browser_id.app_id);
extensions::ExtensionPrefs* prefs =
extensions::ExtensionPrefs::Get(profile());
// Get the number of browsers.
size_t running_browser = chrome::GetTotalBrowserCount();
EXPECT_EQ(0u, running_browser);
EXPECT_FALSE(controller_->IsOpen(browser_id));
// No launch time recorded for Chrome yet.
EXPECT_EQ(base::Time(),
prefs->GetLastLaunchTime(app_constants::kChromeAppId));
// Activate. This creates new browser
base::Time time_before_launch = base::Time::Now();
SelectItem(browser_id, ui::EventType::kUnknown);
base::Time time_after_launch = base::Time::Now();
// New Window is created.
running_browser = chrome::GetTotalBrowserCount();
EXPECT_EQ(1u, running_browser);
EXPECT_TRUE(controller_->IsOpen(browser_id));
// Valid launch time should be recorded for Chrome.
const base::Time time_launch =
prefs->GetLastLaunchTime(app_constants::kChromeAppId);
EXPECT_LE(time_before_launch, time_launch);
EXPECT_GE(time_after_launch, time_launch);
// Minimize Window.
Browser* browser = chrome::FindLastActive();
ASSERT_TRUE(browser);
browser->window()->Minimize();
EXPECT_TRUE(browser->window()->IsMinimized());
// Activate again. This doesn't create new browser, it activates the window.
SelectItem(browser_id, ui::EventType::kUnknown);
running_browser = chrome::GetTotalBrowserCount();
EXPECT_EQ(1u, running_browser);
EXPECT_TRUE(controller_->IsOpen(browser_id));
EXPECT_FALSE(browser->window()->IsMinimized());
// Re-activation should not upate the recorded launch time.
EXPECT_GE(time_launch, prefs->GetLastLaunchTime(app_constants::kChromeAppId));
}
// Check that browser launch time is recorded when the browser is started
// by means other than BrowserShortcutShelfItemController.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTestNoDefaultBrowser,
BrowserLaunchTimeRecorded) {
extensions::ExtensionPrefs* prefs =
extensions::ExtensionPrefs::Get(profile());
EXPECT_EQ(0u, chrome::GetTotalBrowserCount());
EXPECT_EQ(base::Time(),
prefs->GetLastLaunchTime(app_constants::kChromeAppId));
base::Time time_before_launch = base::Time::Now();
// Load about:blank in a new window.
CreateBrowser(profile());
base::Time time_after_launch = base::Time::Now();
const base::Time time_launch =
prefs->GetLastLaunchTime(app_constants::kChromeAppId);
EXPECT_LE(time_before_launch, time_launch);
EXPECT_GE(time_after_launch, time_launch);
}
// Verifies that closing a launched (unpinned) system app through the shelf app
// icon's context menu works as expected.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, CloseSystemAppByShelfContextMenu) {
// Prepare for launching the setting app.
ash::SystemWebAppManager::GetForTest(browser()->profile())
->InstallSystemAppsForTesting();
// Record the default shelf item count.
const int default_item_count = shelf_model()->item_count();
// Verify that the setting app does not exist in the shelf model by default.
ash::RootWindowController* controller = ash::RootWindowController::ForWindow(
browser()->window()->GetNativeWindow());
ash::ShelfView* shelf_view = controller->shelf()->GetShelfViewForTesting();
ash::ShelfModel* model = shelf_view->model();
EXPECT_EQ(-1, model->ItemIndexByAppID(web_app::kOsSettingsAppId));
// Open the system tray then click at the quick setting button.
std::unique_ptr<ash::SystemTrayTestApi> tray_test_api =
ash::SystemTrayTestApi::Create();
tray_test_api->ShowBubble();
ui_test_utils::BrowserChangeObserver browser_opened(
nullptr, ui_test_utils::BrowserChangeObserver::ChangeType::kAdded);
tray_test_api->ClickBubbleView(ash::VIEW_ID_QS_SETTINGS_BUTTON);
browser_opened.Wait();
Browser* app_browser = BrowserList::GetInstance()->GetLastActive();
EXPECT_EQ(web_app::kOsSettingsAppId,
ash::ShelfID::Deserialize(
app_browser->window()->GetNativeWindow()->GetProperty(
ash::kShelfIDKey))
.app_id);
// Wait until the web contents finish loading.
EXPECT_TRUE(
WaitForLoadStop(app_browser->tab_strip_model()->GetActiveWebContents()));
// Wait until the shelf app icon addition animation finishes.
ash::ShelfViewTestAPI shelf_test_api(shelf_view);
shelf_test_api.RunMessageLoopUntilAnimationsDone();
// Verify that the shelf item count increases by one.
EXPECT_EQ(default_item_count + 1, model->item_count());
// Verify that the shelf item of the setting app is placed at the end of the
// shelf model.
const int setting_app_item_index =
model->ItemIndexByAppID(web_app::kOsSettingsAppId);
EXPECT_EQ(model->item_count() - 1, setting_app_item_index);
// Get the setting app's shelf app button.
const ash::ShelfID setting_app_id = model->items()[setting_app_item_index].id;
views::View* setting_app_icon = shelf_view->GetShelfAppButton(setting_app_id);
ash::ShelfViewTestAPI test_api(shelf_view);
base::RunLoop run_loop;
test_api.SetShelfContextMenuCallback(run_loop.QuitClosure());
// Right mouse click at `setting_app_icon`.
ui::test::EventGenerator event_generator(controller->GetRootWindow());
event_generator.MoveMouseTo(
setting_app_icon->GetBoundsInScreen().CenterPoint());
event_generator.PressRightButton();
// Wait until the context menu shows.
run_loop.Run();
ash::ShelfMenuModelAdapter* shelf_menu_model_adapter =
shelf_view->shelf_menu_model_adapter_for_testing();
ASSERT_TRUE(shelf_menu_model_adapter->IsShowingMenu());
// Click at the menu item whose command is ash::MENU_CLOSE.
event_generator.MoveMouseTo(shelf_menu_model_adapter->root_for_testing()
->GetMenuItemByID(ash::MENU_CLOSE)
->GetBoundsInScreen()
.CenterPoint());
event_generator.ClickLeftButton();
ChildRemovalWaiter removal_waiter(shelf_view);
removal_waiter.WaitForChildRemoval();
shelf_test_api.RunMessageLoopUntilAnimationsDone();
// Verify that the shelf item count decreases.
EXPECT_EQ(default_item_count, model->item_count());
}
// Check that the window's ShelfID property matches that of the active tab.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, MatchingShelfIDAndActiveTab) {
EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
EXPECT_EQ(1, browser()->tab_strip_model()->count());
EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
EXPECT_EQ(1, shelf_model()->item_count());
aura::Window* window = browser()->window()->GetNativeWindow();
int browser_index = GetIndexOfShelfItemType(ash::TYPE_BROWSER_SHORTCUT);
ash::ShelfID browser_id = shelf_model()->items()[browser_index].id;
ash::ShelfID id =
ash::ShelfID::Deserialize(window->GetProperty(ash::kShelfIDKey));
EXPECT_EQ(browser_id, id);
std::string* window_app_id = window->GetProperty(ash::kAppIDKey);
ASSERT_TRUE(window_app_id);
EXPECT_EQ(browser_id.app_id, *window_app_id);
ash::ShelfID app_id = CreateShortcut("app1");
EXPECT_EQ(2, shelf_model()->item_count());
// Create and activate a new tab for "app1" and expect an application ShelfID.
SelectItem(app_id);
EXPECT_EQ(2, browser()->tab_strip_model()->count());
EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
id = ash::ShelfID::Deserialize(window->GetProperty(ash::kShelfIDKey));
EXPECT_EQ(app_id, id);
window_app_id = window->GetProperty(ash::kAppIDKey);
ASSERT_TRUE(window_app_id);
EXPECT_EQ(app_id.app_id, *window_app_id);
// Activate the tab at index 0 (NTP) and expect a browser ShelfID.
browser()->tab_strip_model()->ActivateTabAt(0);
EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
id = ash::ShelfID::Deserialize(window->GetProperty(ash::kShelfIDKey));
EXPECT_EQ(browser_id, id);
window_app_id = window->GetProperty(ash::kAppIDKey);
ASSERT_TRUE(window_app_id);
EXPECT_EQ(browser_id.app_id, *window_app_id);
}
// Check that a windowed V1 application can navigate away from its domain, but
// still gets detected properly.
// Disabled due to https://crbug.com/838743.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, DISABLED_V1AppNavigation) {
// We assume that the web store is always there (which it apparently is).
PinAppWithIDToShelf(extensions::kWebStoreAppId);
const ash::ShelfID id(extensions::kWebStoreAppId);
EXPECT_EQ(ash::STATUS_CLOSED, shelf_model()->ItemByID(id)->status);
// Create a windowed application.
apps::AppServiceProxyFactory::GetForProfile(profile())->Launch(
extensions::kWebStoreAppId,
apps::GetEventFlags(WindowOpenDisposition::NEW_FOREGROUND_TAB,
true /* prefer_containner */),
apps::LaunchSource::kFromTest,
std::make_unique<apps::WindowInfo>(
display::Screen::GetScreen()->GetPrimaryDisplay().id()));
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->ItemByID(id)->status);
// Find the browser which holds our app.
Browser* app_browser = nullptr;
for (Browser* browser : BrowserList::GetInstance()->OrderedByActivation()) {
if (browser->is_type_app()) {
app_browser = browser;
break;
}
}
ASSERT_TRUE(app_browser);
// After navigating away in the app, we should still be active.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
app_browser, GURL("http://www.foo.com/bar.html")));
// Make sure the navigation was entirely performed.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->ItemByID(id)->status);
app_browser->tab_strip_model()->CloseWebContentsAt(0,
TabCloseTypes::CLOSE_NONE);
// Make sure that the app is really gone.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(ash::STATUS_CLOSED, shelf_model()->ItemByID(id)->status);
}
// Ensure opening settings and task manager windows create new shelf items.
IN_PROC_BROWSER_TEST_F(ShelfWebAppBrowserTest, SettingsAndTaskManagerWindows) {
// Install the Settings App.
ash::SystemWebAppManager::GetForTest(browser()->profile())
->InstallSystemAppsForTesting();
chrome::SettingsWindowManager* settings_manager =
chrome::SettingsWindowManager::GetInstance();
// Get the number of items in the shelf and browser menu.
int item_count = shelf_model()->item_count();
ASSERT_GE(item_count, 0);
size_t browser_count = BrowserShortcutMenuItemCount(false);
base::RunLoop run_loop;
// Open a settings window. Number of browser items should remain unchanged,
// number of shelf items should increase.
settings_manager->ShowChromePageForProfile(
browser()->profile(), chrome::GetOSSettingsUrl(std::string()),
display::kInvalidDisplayId,
base::BindOnce([](apps::LaunchResult&& result) {
EXPECT_EQ(apps::State::kSuccess, result.state);
}).Then(run_loop.QuitClosure()));
// Spin a run loop to sync Ash's ShelfModel change for the settings window.
run_loop.Run();
Browser* settings_browser =
settings_manager->FindBrowserForProfile(browser()->profile());
ASSERT_TRUE(settings_browser);
EXPECT_EQ(browser_count, BrowserShortcutMenuItemCount(false));
EXPECT_EQ(item_count + 1, shelf_model()->item_count());
aura::Window* settings_window = settings_browser->window()->GetNativeWindow();
ASSERT_TRUE(settings_window->GetProperty(ash::kAppIDKey));
EXPECT_TRUE(crx_file::id_util::IdIsValid(
*settings_window->GetProperty(ash::kAppIDKey)));
chrome::ShowTaskManager(browser());
// Spin a run loop to sync Ash's ShelfModel change for the task manager.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(item_count + 2, shelf_model()->item_count());
// Validates that all items have valid app id.
for (const auto& item : shelf_model()->items())
EXPECT_TRUE(crx_file::id_util::IdIsValid(item.id.app_id));
// TODO(stevenjb): Test multiprofile on Chrome OS when test support is addded.
// crbug.com/230464.
}
// Check that tabbed hosted and web apps have correct shelf presence.
IN_PROC_BROWSER_TEST_F(ShelfWebAppBrowserTest, TabbedHostedAndWebApps) {
// Load and pin a hosted app.
const Extension* hosted_app =
LoadExtension(test_data_dir_.AppendASCII("app1/"));
ASSERT_TRUE(hosted_app);
PinAppWithIDToShelf(hosted_app->id());
const ash::ShelfID hosted_app_shelf_id(hosted_app->id());
// Load and pin a web app.
const GURL web_app_url = GetSecureAppURL();
const webapps::AppId web_app_id = InstallWebApp(web_app_url);
PinAppWithIDToShelf(web_app_id);
const ash::ShelfID web_app_shelf_id(web_app_id);
// The apps should be closed.
EXPECT_EQ(ash::STATUS_CLOSED,
shelf_model()->ItemByID(hosted_app_shelf_id)->status);
EXPECT_EQ(ash::STATUS_CLOSED,
shelf_model()->ItemByID(web_app_shelf_id)->status);
// Navigate to the app's launch URLs in two tabs.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), extensions::AppLaunchInfo::GetLaunchWebURL(hosted_app)));
ui_test_utils::NavigateToURLWithDisposition(
browser(), web_app_url, WindowOpenDisposition::NEW_FOREGROUND_TAB, 0);
// The apps should now be running.
EXPECT_EQ(ash::STATUS_RUNNING,
shelf_model()->ItemByID(hosted_app_shelf_id)->status);
EXPECT_EQ(ash::STATUS_RUNNING,
shelf_model()->ItemByID(web_app_shelf_id)->status);
// Now use the shelf controller to activate the apps.
SelectApp(hosted_app->id(), ash::LAUNCH_FROM_APP_LIST);
SelectApp(web_app_id, ash::LAUNCH_FROM_APP_LIST);
// There should be no new browsers or tabs as both apps were already open.
EXPECT_EQ(1u, chrome::GetBrowserCount(browser()->profile()));
EXPECT_EQ(2, browser()->tab_strip_model()->count());
}
// Check that windowed hosted and web apps have correct shelf presence.
IN_PROC_BROWSER_TEST_F(ShelfWebAppBrowserTest, WindowedHostedAndWebApps) {
// Load and pin a hosted app.
const Extension* hosted_app =
LoadExtension(test_data_dir_.AppendASCII("app1/"));
ASSERT_TRUE(hosted_app);
PinAppWithIDToShelf(hosted_app->id());
const ash::ShelfID hosted_app_shelf_id(hosted_app->id());
// Load and pin a web app.
const GURL web_app_url = GetSecureAppURL();
const webapps::AppId web_app_id = InstallWebApp(web_app_url);
PinAppWithIDToShelf(web_app_id);
const ash::ShelfID web_app_shelf_id(web_app_id);
// Set both apps to open in windows.
extensions::SetLaunchType(browser()->profile(), hosted_app->id(),
extensions::LAUNCH_TYPE_WINDOW);
WebAppProvider* provider = WebAppProvider::GetForTest(browser()->profile());
DCHECK(provider);
provider->sync_bridge_unsafe().SetAppUserDisplayModeForTesting(
web_app_id, web_app::mojom::UserDisplayMode::kStandalone);
// The apps should be closed.
EXPECT_EQ(ash::STATUS_CLOSED,
shelf_model()->ItemByID(hosted_app_shelf_id)->status);
EXPECT_EQ(ash::STATUS_CLOSED,
shelf_model()->ItemByID(web_app_shelf_id)->status);
// Navigate to the app's launch URLs in two tabs.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), extensions::AppLaunchInfo::GetLaunchWebURL(hosted_app)));
ui_test_utils::NavigateToURLWithDisposition(
browser(), web_app_url, WindowOpenDisposition::NEW_FOREGROUND_TAB, 0);
// The apps should still be closed.
EXPECT_EQ(ash::STATUS_CLOSED,
shelf_model()->ItemByID(hosted_app_shelf_id)->status);
EXPECT_EQ(ash::STATUS_CLOSED,
shelf_model()->ItemByID(web_app_shelf_id)->status);
// Now use the shelf controller to activate the apps.
ui_test_utils::BrowserChangeObserver browser_opened1(
nullptr, ui_test_utils::BrowserChangeObserver::ChangeType::kAdded);
SelectApp(hosted_app->id(), ash::LAUNCH_FROM_APP_LIST);
browser_opened1.Wait();
ui_test_utils::BrowserChangeObserver browser_opened2(
nullptr, ui_test_utils::BrowserChangeObserver::ChangeType::kAdded);
SelectApp(web_app_id, ash::LAUNCH_FROM_APP_LIST);
browser_opened2.Wait();
// There should be two new browsers.
EXPECT_EQ(3u, chrome::GetBrowserCount(browser()->profile()));
// The apps should now be running.
EXPECT_EQ(ash::STATUS_RUNNING,
shelf_model()->ItemByID(hosted_app_shelf_id)->status);
EXPECT_EQ(ash::STATUS_RUNNING,
shelf_model()->ItemByID(web_app_shelf_id)->status);
}
// Windowed progressive web apps should have shelf activity indicator showing
// after install.
IN_PROC_BROWSER_TEST_F(ShelfWebAppBrowserTest,
WindowedPwasHaveActivityIndicatorSet) {
// Start server and open test page.
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("/banners/manifest_test_page.html"));
web_app::ServiceWorkerRegistrationWaiter registration_waiter(profile(), url);
ASSERT_TRUE(AddTabAtIndex(1, url, ui::PAGE_TRANSITION_LINK));
registration_waiter.AwaitRegistration();
// Install PWA.
web_app::SetAutoAcceptPWAInstallConfirmationForTesting(true);
web_app::WebAppTestInstallWithOsHooksObserver install_observer(profile());
install_observer.BeginListening();
chrome::ExecuteCommand(browser(), IDC_INSTALL_PWA);
const webapps::AppId app_id = install_observer.Wait();
web_app::SetAutoAcceptPWAInstallConfirmationForTesting(false);
ash::ShelfID shelf_id(app_id);
EXPECT_FALSE(ChromeShelfController::instance()->IsPinned(shelf_id));
EXPECT_EQ(
shelf_id,
ChromeShelfController::instance()->shelf_model()->active_shelf_id());
}
// Windowed shortcut apps should have shelf activity indicator showing after
// install.
IN_PROC_BROWSER_TEST_F(ShelfWebAppBrowserTest,
WindowedShortcutAppsHaveActivityIndicatorSet) {
// Start server and open test page.
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(AddTabAtIndex(
1,
GURL(embedded_test_server()->GetURL("/banners/manifest_test_page.html")),
ui::PAGE_TRANSITION_LINK));
// Install shortcut app.
web_app::SetAutoAcceptWebAppDialogForTesting(true, true);
web_app::WebAppTestInstallWithOsHooksObserver install_observer(profile());
install_observer.BeginListening();
chrome::ExecuteCommand(browser(), IDC_CREATE_SHORTCUT);
const webapps::AppId app_id = install_observer.Wait();
web_app::SetAutoAcceptWebAppDialogForTesting(false, false);
ash::ShelfID shelf_id(app_id);
EXPECT_FALSE(ChromeShelfController::instance()->IsPinned(shelf_id));
EXPECT_EQ(
shelf_id,
ChromeShelfController::instance()->shelf_model()->active_shelf_id());
}
IN_PROC_BROWSER_TEST_F(ShelfWebAppBrowserTest, WebAppPolicy) {
// Install web app from policy.
GURL app_url = https_server()->GetURL("/web_apps/basic.html");
web_app::ExternalInstallOptions options =
web_app::CreateInstallOptions(app_url);
options.install_source = web_app::ExternalInstallSource::kExternalPolicy;
web_app::ExternallyManagedAppManager::InstallResult result =
web_app::ExternallyManagedAppManagerInstall(browser()->profile(),
options);
// Set policy to pin the web app.
base::Value::Dict entry;
entry.Set(ChromeShelfPrefs::kPinnedAppsPrefAppIDKey, app_url.spec());
base::Value::List policy_value;
policy_value.Append(std::move(entry));
profile()->GetPrefs()->SetList(prefs::kPolicyPinnedLauncherApps,
std::move(policy_value));
// Check web app is pinned and fixed.
ASSERT_EQ(shelf_model()->item_count(), 2);
EXPECT_EQ(shelf_model()->items()[0].type, ash::TYPE_BROWSER_SHORTCUT);
EXPECT_EQ(shelf_model()->items()[1].type, ash::TYPE_PINNED_APP);
ASSERT_TRUE(result.app_id.has_value());
EXPECT_EQ(shelf_model()->items()[1].id.app_id, result.app_id.value());
EXPECT_EQ(shelf_model()->items()[1].title, u"Basic web app");
EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
GetPinnableForAppID(result.app_id.value(), profile()));
}
IN_PROC_BROWSER_TEST_F(ShelfWebAppBrowserTest, WebAppPolicyUpdate) {
// Install web app.
GURL app_url = GURL("https://example.org/");
auto web_app_info =
web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(app_url);
web_app_info->scope = app_url;
web_app_info->title = u"Example";
webapps::AppId app_id =
web_app::test::InstallWebApp(profile(), std::move(web_app_info));
// Set policy to pin the web app.
base::Value::Dict entry;
entry.Set(ChromeShelfPrefs::kPinnedAppsPrefAppIDKey, app_url.spec());
base::Value::List policy_value;
policy_value.Append(std::move(entry));
profile()->GetPrefs()->SetList(prefs::kPolicyPinnedLauncherApps,
std::move(policy_value));
// Check web app is not pinned.
EXPECT_EQ(shelf_model()->item_count(), 1);
EXPECT_EQ(shelf_model()->items()[0].type, ash::TYPE_BROWSER_SHORTCUT);
EXPECT_EQ(AppListControllerDelegate::PIN_EDITABLE,
GetPinnableForAppID(app_id, profile()));
web_app::WebAppProvider* provider =
web_app::WebAppProvider::GetForTest(profile());
web_app::test::AddInstallUrlData(
profile()->GetPrefs(), &provider->sync_bridge_unsafe(), app_id, app_url,
web_app::ExternalInstallSource::kExternalPolicy);
provider->install_manager().NotifyWebAppInstalledWithOsHooks(app_id);
// Check web app is pinned and fixed.
EXPECT_EQ(shelf_model()->item_count(), 2);
EXPECT_EQ(shelf_model()->items()[0].type, ash::TYPE_BROWSER_SHORTCUT);
EXPECT_EQ(shelf_model()->items()[1].type, ash::TYPE_PINNED_APP);
EXPECT_EQ(shelf_model()->items()[1].id.app_id, app_id);
EXPECT_EQ(shelf_model()->items()[1].title, u"Example");
EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
GetPinnableForAppID(app_id, profile()));
}
IN_PROC_BROWSER_TEST_F(ShelfWebAppBrowserTest, WebAppPolicyNonExistentApp) {
// Don't install the web app.
GURL app_url = GURL("https://example.org/");
webapps::AppId app_id =
web_app::GenerateAppId(/*manifest_id=*/std::nullopt, app_url);
// Set policy to pin the non existent web app.
base::Value::Dict entry;
entry.Set(ChromeShelfPrefs::kPinnedAppsPrefAppIDKey, app_url.spec());
base::Value::List policy_value;
policy_value.Append(std::move(entry));
profile()->GetPrefs()->SetList(prefs::kPolicyPinnedLauncherApps,
std::move(policy_value));
// Check web app policy is ignored.
EXPECT_EQ(shelf_model()->item_count(), 1);
EXPECT_EQ(shelf_model()->items()[0].type, ash::TYPE_BROWSER_SHORTCUT);
EXPECT_EQ(AppListControllerDelegate::PIN_EDITABLE,
GetPinnableForAppID(app_id, profile()));
}
IN_PROC_BROWSER_TEST_F(ShelfWebAppBrowserTest, WebAppInstallForceList) {
constexpr char kAppUrl[] = "https://example.site";
base::RunLoop run_loop;
web_app::WebAppProvider::GetForTest(browser()->profile())
->policy_manager()
.SetOnAppsSynchronizedCompletedCallbackForTesting(run_loop.QuitClosure());
web_app::WebAppTestInstallWithOsHooksObserver install_observer(profile());
install_observer.BeginListening();
{
base::Value::Dict entry;
entry.Set(ChromeShelfPrefs::kPinnedAppsPrefAppIDKey, kAppUrl);
base::Value::List policy_value;
policy_value.Append(std::move(entry));
profile()->GetPrefs()->SetList(prefs::kPolicyPinnedLauncherApps,
std::move(policy_value));
}
{
base::Value::Dict item;
item.Set(web_app::kUrlKey, kAppUrl);
base::Value::List list;
list.Append(std::move(item));
profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
std::move(list));
}
const webapps::AppId app_id = install_observer.Wait();
run_loop.Run();
// Check web app is pinned and fixed.
ASSERT_EQ(shelf_model()->item_count(), 2);
EXPECT_EQ(shelf_model()->items()[0].type, ash::TYPE_BROWSER_SHORTCUT);
EXPECT_EQ(shelf_model()->items()[1].type, ash::TYPE_PINNED_APP);
EXPECT_EQ(shelf_model()->items()[1].id.app_id, app_id);
EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
GetPinnableForAppID(app_id, profile()));
}
// Test that "Close" is shown in the context menu when there are opened browsers
// windows.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest,
ShelfContextMenuVerifyCloseItemAppearance) {
// Open a context menu for the existing browser window.
std::unique_ptr<ShelfContextMenu> menu1 = CreateBrowserItemContextMenu();
// Check if "Close" is added to in the context menu.
ASSERT_TRUE(IsItemPresentInMenu(menu1.get(), ash::MENU_CLOSE));
// Close all windows via the menu item.
CloseBrowserWindow(browser(), menu1.get(), ash::MENU_CLOSE);
EXPECT_EQ(0u, BrowserList::GetInstance()->size());
// Check if "Close" is removed from the context menu.
std::unique_ptr<ShelfContextMenu> menu2 = CreateBrowserItemContextMenu();
ASSERT_FALSE(IsItemPresentInMenu(menu2.get(), ash::MENU_CLOSE));
}
// Chrome's ShelfModel should have the browser item and delegate.
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest, ShelfModelInitialization) {
EXPECT_EQ(1, shelf_model()->item_count());
EXPECT_EQ(app_constants::kChromeAppId, shelf_model()->items()[0].id.app_id);
EXPECT_TRUE(
shelf_model()->GetShelfItemDelegate(shelf_model()->items()[0].id));
}
class HotseatShelfAppBrowserTest : public ShelfAppBrowserTest {
public:
HotseatShelfAppBrowserTest() = default;
HotseatShelfAppBrowserTest(const HotseatShelfAppBrowserTest&) = delete;
HotseatShelfAppBrowserTest& operator=(const HotseatShelfAppBrowserTest&) =
delete;
~HotseatShelfAppBrowserTest() override = default;
// ShelfAppBrowserTest:
void SetUp() override {
// Disable contextual nudges to prevent in-app to home nudge from being
// announced in the ChromeVox test.
scoped_feature_list_.InitAndDisableFeature(
ash::features::kHideShelfControlsInTabletMode);
ShelfAppBrowserTest::SetUp();
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Verifies that hotseat should be hidden after launching the browser from
// a context menu (https://crbug.com/1072043).
IN_PROC_BROWSER_TEST_F(HotseatShelfAppBrowserTest, LaunchAppFromContextMenu) {
ash::TabletModeControllerTestApi().EnterTabletMode();
ash::RootWindowController* controller =
ash::Shell::GetRootWindowControllerWithDisplayId(
display::Screen::GetScreen()->GetPrimaryDisplay().id());
ash::ShelfView* shelf_view = controller->shelf()->GetShelfViewForTesting();
ash::ShelfModel* model = shelf_view->model();
EXPECT_EQ(1, model->item_count());
ExtendHotseat(browser());
const ash::ShelfID browser_icon_id = model->items()[0].id;
views::View* browser_icon = shelf_view->GetShelfAppButton(browser_icon_id);
ash::ShelfViewTestAPI test_api(shelf_view);
base::RunLoop run_loop;
test_api.SetShelfContextMenuCallback(run_loop.QuitClosure());
ui::test::EventGenerator event_generator(controller->GetRootWindow());
event_generator.MoveMouseTo(browser_icon->GetBoundsInScreen().CenterPoint());
event_generator.PressRightButton();
// Wait until the context menu shows.
run_loop.Run();
ash::ShelfMenuModelAdapter* shelf_menu_model_adapter =
shelf_view->shelf_menu_model_adapter_for_testing();
ASSERT_TRUE(shelf_menu_model_adapter->IsShowingMenu());
// Click at the menu item whose command is ash::MENU_NEW_WINDOW.
event_generator.MoveMouseTo(
shelf_menu_model_adapter->root_for_testing()
->GetMenuItemByID(ash::APP_CONTEXT_MENU_NEW_WINDOW)
->GetBoundsInScreen()
.CenterPoint());
event_generator.ClickLeftButton();
// Verify that hotseat is hidden.
EXPECT_EQ(ash::HotseatState::kHidden,
controller->shelf()->shelf_layout_manager()->hotseat_state());
}
// Tests that launching and switching apps by tapping shelf buttons hides the
// hotseat.
IN_PROC_BROWSER_TEST_F(HotseatShelfAppBrowserTest,
TappingAppIconsHidesHotseat) {
ash::TabletModeControllerTestApi().EnterTabletMode();
// Create two apps, then extend the hotseat.
ash::ShelfID shortcut_id_1 = CreateShortcut("app1");
ash::ShelfID shortcut_id_2 = CreateShortcut("app2");
ExtendHotseat(browser());
// Launch app1, the hotseat should hide.
ash::RootWindowController* controller =
ash::Shell::GetRootWindowControllerWithDisplayId(
display::Screen::GetScreen()->GetPrimaryDisplay().id());
ash::ShelfView* shelf_view = controller->shelf()->GetShelfViewForTesting();
views::View* button_1 = shelf_view->GetShelfAppButton(shortcut_id_1);
ui::test::EventGenerator event_generator(controller->GetRootWindow());
event_generator.GestureTapAt(button_1->GetBoundsInScreen().CenterPoint());
EXPECT_EQ(ash::HotseatState::kHidden,
controller->shelf()->shelf_layout_manager()->hotseat_state());
// Show the hotseat again, and launch app2. The hotseat should hide again.
ExtendHotseat(browser());
views::View* button_2 = shelf_view->GetShelfAppButton(shortcut_id_2);
event_generator.GestureTapAt(button_2->GetBoundsInScreen().CenterPoint());
EXPECT_EQ(ash::HotseatState::kHidden,
controller->shelf()->shelf_layout_manager()->hotseat_state());
// Extend the hotseat and test that switching back to app1 results in a hidden
// hotseat.
ExtendHotseat(browser());
event_generator.GestureTapAt(button_1->GetBoundsInScreen().CenterPoint());
EXPECT_EQ(ash::HotseatState::kHidden,
controller->shelf()->shelf_layout_manager()->hotseat_state());
}
// Verify that the in-app shelf should be shown when the app icon receives
// the accessibility focus.
IN_PROC_BROWSER_TEST_F(HotseatShelfAppBrowserTest, EnableChromeVox) {
ash::TabletModeControllerTestApi().EnterTabletMode();
ash::test::SpeechMonitor speech_monitor;
// Enable ChromeVox.
ASSERT_FALSE(AccessibilityManager::Get()->IsSpokenFeedbackEnabled());
AccessibilityManager::Get()->EnableSpokenFeedback(true);
// Wait for ChromeVox to start reading anything.
speech_monitor.ExpectSpeechPattern("*");
speech_monitor.Call([this]() {
// Disable earcons (https://crbug.com/396507).
const std::string script(R"JS(
let module = await import('/chromevox/background/chromevox.js');
module.ChromeVox.earcons.playEarcon = function() {};
module = await import('/chromevox/background/chromevox_state.js');
let ChromeVoxState = module.ChromeVoxState;
module = await import('/chromevox/background/chromevox_range.js');
let ChromeVoxRange = module.ChromeVoxRange;
await ChromeVoxState.ready();
// Wait for ChromeVox to have a current range before the test starts
// traversal through shelf to ensure that the browser does not show
// mid shelf traversal, and causes the a11y focus to unexpectedly
// switch to the omnibox mid test.
if (!ChromeVoxRange.current) {
await new Promise(resolve => {
new (class {
constructor() {
ChromeVoxRange.addObserver(this);
}
onCurrentRangeChanged(newRange) {
if (newRange) {
ChromeVoxRange.removeObserver(this);
resolve();
}
}
})();
});
}
)JS");
ExecuteScriptInChromeVox(browser(), script);
});
ash::RootWindowController* controller =
ash::Shell::GetRootWindowControllerWithDisplayId(
display::Screen::GetScreen()->GetPrimaryDisplay().id());
ui::test::EventGenerator event_generator(controller->GetRootWindow());
auto* generator_ptr = &event_generator;
base::SimpleTestTickClock clock;
auto* clock_ptr = &clock;
ui::SetEventTickClockForTesting(clock_ptr);
views::View* home_button = ash::ShelfTestApi().GetHomeButton();
speech_monitor.Call([clock_ptr, generator_ptr, home_button]() {
// Hover touch over the come button.
const gfx::Point home_button_center =
home_button->GetBoundsInScreen().CenterPoint();
ui::TouchEvent touch_press(
ui::EventType::kTouchPressed, home_button_center,
base::TimeTicks::Now(),
ui::PointerDetails(ui::EventPointerType::kTouch, 0));
generator_ptr->Dispatch(&touch_press);
clock_ptr->Advance(base::Seconds(1));
ui::TouchEvent touch_move(
ui::EventType::kTouchMoved, home_button_center, base::TimeTicks::Now(),
ui::PointerDetails(ui::EventPointerType::kTouch, 0));
generator_ptr->Dispatch(&touch_move);
});
speech_monitor.ExpectSpeech("Launcher");
speech_monitor.ExpectSpeech("Button");
speech_monitor.ExpectSpeech("Shelf");
speech_monitor.ExpectSpeech("Tool bar");
speech_monitor.ExpectSpeech(", window");
speech_monitor.Call([controller]() {
// Hotseat is expected to be hidden (by default).
ASSERT_EQ(ash::HotseatState::kHidden,
controller->shelf()->shelf_layout_manager()->hotseat_state());
});
speech_monitor.Call([generator_ptr]() {
// Press the search + right. Expects that the browser icon receives the
// accessibility focus and the hotseat switches to kExtended state.
generator_ptr->PressKeyAndModifierKeys(ui::VKEY_RIGHT, ui::EF_COMMAND_DOWN);
});
const int browser_index =
ash::ShelfModel::Get()->GetItemIndexForType(ash::TYPE_BROWSER_SHORTCUT);
speech_monitor.ExpectSpeech(
base::UTF16ToASCII(ash::ShelfModel::Get()->items()[browser_index].title));
speech_monitor.Replay();
EXPECT_EQ(ash::HotseatState::kExtended,
controller->shelf()->shelf_layout_manager()->hotseat_state());
// Click on the home button. Expects that the hotseat is shown in
// kShownHomeLauncher state. Note that the home button should be shown in
// tablet mode with spoken feedback enabled.
event_generator.MoveMouseTo(home_button->GetBoundsInScreen().CenterPoint());
event_generator.ClickLeftButton();
EXPECT_EQ(ash::HotseatState::kShownHomeLauncher,
controller->shelf()->shelf_layout_manager()->hotseat_state());
}
using ShelfAppBrowserTestWithDesks = ShelfAppBrowserTest;
IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTestWithDesks, MultipleDesks) {
auto* desks_controller = ash::DesksController::Get();
desks_controller->NewDesk(ash::DesksCreationRemovalSource::kButton);
// Tests starts with an existing browser on desk_1.
EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
// Activate desk_2 and click on the browser's icon on the shelf while being on
// that desk. This should not switch back to desk_1, but rather create a new
// browser window.
ASSERT_EQ(2u, desks_controller->desks().size());
auto* desk_2 = desks_controller->desks()[1].get();
ash::ActivateDesk(desk_2);
const int browser_index = GetIndexOfShelfItemType(ash::TYPE_BROWSER_SHORTCUT);
ash::ShelfID browser_id = shelf_model()->items()[browser_index].id;
SelectItem(browser_id);
EXPECT_EQ(2u, chrome::GetTotalBrowserCount());
EXPECT_FALSE(desks_controller->AreDesksBeingModified());
EXPECT_TRUE(desk_2->is_active());
// The shelf context menu should show 2 items for both browsers. No new items
// should be created and existing window should not be minimized.
EXPECT_EQ(ash::ShelfAction::SHELF_ACTION_NONE,
SelectItem(browser_id, ui::EventType::kMousePressed,
display::kInvalidDisplayId, ash::LAUNCH_FROM_SHELF));
EXPECT_EQ(
2u, controller_
->GetAppMenuItemsForTesting(shelf_model()->items()[browser_index])
.size());
}
class PerDeskShelfAppBrowserTest : public ShelfAppBrowserTest,
public ::testing::WithParamInterface<bool> {
public:
PerDeskShelfAppBrowserTest() = default;
PerDeskShelfAppBrowserTest(const PerDeskShelfAppBrowserTest&) = delete;
PerDeskShelfAppBrowserTest& operator=(const PerDeskShelfAppBrowserTest&) =
delete;
~PerDeskShelfAppBrowserTest() override = default;
ash::ShelfView* shelf_view() { return shelf_view_; }
// ShelfAppBrowserTest:
void SetUp() override {
if (GetParam()) {
scoped_feature_list_.InitAndEnableFeature(ash::features::kPerDeskShelf);
} else {
scoped_feature_list_.InitAndDisableFeature(ash::features::kPerDeskShelf);
}
ShelfAppBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
ShelfAppBrowserTest::SetUpOnMainThread();
shelf_view_ = ash::Shell::GetPrimaryRootWindowController()
->shelf()
->GetShelfViewForTesting();
// Start tests with 2 desks.
ash::DesksController::Get()->NewDesk(
ash::DesksCreationRemovalSource::kButton);
ash::ShelfViewTestAPI test_api(shelf_view());
test_api.SetShelfContextMenuCallback(base::BindRepeating(
&PerDeskShelfAppBrowserTest::OnAppMenuShown, base::Unretained(this)));
}
void CreateTestBrowser() {
Browser* new_browser = CreateBrowser(browser()->profile());
new_browser->window()->Show();
new_browser->window()->Activate();
}
ash::ShelfID GetBrowserId() const {
const int browser_index =
GetIndexOfShelfItemType(ash::TYPE_BROWSER_SHORTCUT);
return controller_->shelf_model()->items()[browser_index].id;
}
ash::ShelfMenuModelAdapter* ClickBrowserShelfButtonAndGetMenu() {
views::View* browser_icon = shelf_view()->GetShelfAppButton(GetBrowserId());
run_loop_ = std::make_unique<base::RunLoop>();
ui::test::EventGenerator event_generator(
ash::Shell::GetPrimaryRootWindow());
event_generator.MoveMouseTo(
browser_icon->GetBoundsInScreen().CenterPoint());
event_generator.ClickLeftButton();
run_loop_->Run();
ash::ShelfMenuModelAdapter* shelf_menu_model_adapter =
shelf_view()->shelf_menu_model_adapter_for_testing();
EXPECT_TRUE(shelf_menu_model_adapter);
EXPECT_TRUE(shelf_menu_model_adapter->IsShowingMenu());
return shelf_menu_model_adapter;
}
private:
void OnAppMenuShown() {
if (run_loop_)
std::move(run_loop_)->Quit();
}
raw_ptr<ash::ShelfView, DanglingUntriaged> shelf_view_ = nullptr;
std::unique_ptr<base::RunLoop> run_loop_;
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_P(PerDeskShelfAppBrowserTest, AppMenus) {
// On desk_1, create 3 browsers. Note that the test starts with a default
// browser.
CreateTestBrowser();
CreateTestBrowser();
EXPECT_EQ(3u, chrome::GetTotalBrowserCount());
// Switch to desk_2, and create 2 more browsers.
auto* desks_controller = ash::DesksController::Get();
auto* desk_2 = desks_controller->desks()[1].get();
ash::ActivateDesk(desk_2);
CreateTestBrowser();
CreateTestBrowser();
EXPECT_EQ(5u, chrome::GetTotalBrowserCount());
// Click on the Browser icon on the shelf and expect the app items menu will
// show, and the number of items in the menu will depend on whether the
// per-desk shelf feature is enabled or not.
auto* model_adapter = ClickBrowserShelfButtonAndGetMenu();
const bool is_per_desk_shelf_enabled = GetParam();
constexpr size_t kTitleAndSeparatorCount = 2;
if (is_per_desk_shelf_enabled) {
EXPECT_EQ(2 + kTitleAndSeparatorCount,
model_adapter->model()->GetItemCount());
} else {
EXPECT_EQ(5 + kTitleAndSeparatorCount,
model_adapter->model()->GetItemCount());
}
// Switch to desk_1, and verify the app items count again.
auto* desk_1 = desks_controller->desks()[0].get();
ash::ActivateDesk(desk_1);
model_adapter = ClickBrowserShelfButtonAndGetMenu();
if (is_per_desk_shelf_enabled) {
EXPECT_EQ(3 + kTitleAndSeparatorCount,
model_adapter->model()->GetItemCount());
} else {
EXPECT_EQ(5 + kTitleAndSeparatorCount,
model_adapter->model()->GetItemCount());
}
}
// The Browsertest verifying Files System Web App features.
class FilesSystemWebAppPinnedTest : public ShelfPlatformAppBrowserTest {
public:
void SetUp() override { ShelfPlatformAppBrowserTest::SetUp(); }
};
IN_PROC_BROWSER_TEST_F(FilesSystemWebAppPinnedTest, EnterpriseMigration) {
// Setup: the customer pins Files Chrome App (ID:hhaomji...).
base::Value::Dict entry;
entry.Set(ChromeShelfPrefs::kPinnedAppsPrefAppIDKey,
file_manager::kFileManagerAppId);
base::Value::List policy_value;
policy_value.Append(std::move(entry));
profile()->GetPrefs()->SetList(prefs::kPolicyPinnedLauncherApps,
std::move(policy_value));
// Ensure shelf is updated.
ash::SystemWebAppManager::Get(browser()->profile())
->InstallSystemAppsForTesting();
web_app::WebAppProvider::GetForTest(browser()->profile())
->install_manager()
.NotifyWebAppInstalledWithOsHooks(file_manager::kFileManagerSwaAppId);
// Expected results: Files SWA App (ID:fkiggjm...) is force-pinned.
ash::ShelfID swa_shelf_id(file_manager::kFileManagerSwaAppId);
EXPECT_TRUE(ChromeShelfController::instance()->IsPinned(swa_shelf_id));
ash::ShelfID extension_shelf_id(file_manager::kFileManagerAppId);
EXPECT_FALSE(ChromeShelfController::instance()->IsPinned(extension_shelf_id));
EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
GetPinnableForAppID(file_manager::kFileManagerSwaAppId, profile()));
}
INSTANTIATE_TEST_SUITE_P(All, PerDeskShelfAppBrowserTest, ::testing::Bool());